I wonder if anyone can speculate or better yet provide a piece of code as for the implementation of the lengthy friends list in the Facebook iPhone app.
when you open the app and go strait to the friends list, you get the list almost in an instant, at least for me with ~500 friends.
when I try it in my own app it takes lots of precious seconds to populate the table view with the same data, so how does Facebook accomplished such a quick response time ?
upon looking at the tableview in the facebook app you notice there is no scroll bar usually found in such tableview, could that be one sign of the neat trick facebook is utilizing to achieve this rapid rows insert ? could it be they implemented some sort of a virtual tableview with only holds a few dozen rows but rotates them ?
any thoughts ?
the UITableView will let you do this. There are a number of examples on the internet with UITableView and Custom Cell's
Essentially, you load your images in the background, and you reuse the Cells that are in the tableview
EDIT Added example code to demonstrate how this is accomplished.
IMPORTANT NOTE
This code was not tested and may or may not actually function as is.
It was pasted with some editing for length. I did a lot more then this in my app, but in the interest of keeping with the example requested I omitted a lot.
On with the example:
Here is where I get the cell, load it with the items that are readily available. And send it to the background thread to load the rest.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"OfferCell";
static NSString *CellNib = #"OfferItem";
OfferCell* cell = (OfferCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellNib owner:self options:nil];
cell = (OfferCell*)[nib objectAtIndex:0];
}
NSDictionary* couponPackage = [self.jsonOfferData valueForKey:#"result"];
NSArray *couponList = [couponPackage valueForKey:#"offers"];
if ([couponList count] >= indexPath.row )
{
NSDictionary* couponData = [couponList objectAtIndex:indexPath.row];
Coupon *coupon = [[Coupon alloc] initWithDictionary:couponData];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:cell,#"cell",coupon,#"coupon", nil];
//Right here you would try to load any cached imaged from disk.
//Then send a Thread to the background to load the image.
[self performSelectorInBackground:#selector(loadTableViewCellData:) withObject:params];
//Load up the rest of the custom info into the custom cell.
[cell.captionLabel setText:coupon.name];
[cell.subTextLabel setText:coupon.subText];
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
[cell setCommand:coupon.command];
[cell setParameter:coupon.commandArgs];
[cell setImageURL:coupon.imageURL];
[cell setImageAltURL:coupon.imageAltURL];
[cell setRegistrationCode:coupon.registrationCode];
[coupon release];
}
return cell;
}
as you can see, i call a background thread before i even load the custom content in the cell.
- (void) loadTableViewCellData:(NSDictionary*) objectData
{
OfferCell *cell = [objectData objectForKey:#"cell"];
Coupon *coupon = [objectData objectForKey:#"coupon"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[coupon iconURL]]]];
[objectData setValue:image forKey:#"image"];
self performSelectorOnMainThread:#selector(setImageOnMainThread:) withObject:objectData
}
after downloading the image, i send a Main thread request to update the Image that is in the cell object.
- (void) setImageOnMainThread:(NSDictionary*) objectData
{
OfferCell *cell = [objectData objectForKey:#"cell"];
Coupon *coupon = [objectData objectForKey:#"coupon"];
UIImage *image = [objectData objectForKey:#"image"];
cell.icon.image = image;
}
##AGAIN This May not Actually Function. ##
I did not copy all of my code for this. this is a hammer out so you can get the idea.
play with the code and test it. but the fundamentals are.
Dequeue the cell that will fit your needs (Reuse Identifier)
Use the cell if it can be dequeue'd or create a new one with a reuse identifier (my example uses a xib file named OfferItem.xib)
Send a thread to the background that will load the image data from disk or url (a combination of both is recommended)
Send a thread back to the UI when you are ready to load the image into the View (Updating the UI must be done on the main thread)
if you can do that, then your friends list (or in this case offers) will be loaded up as fast as possible. and the Images will pop on the screen as soon as they download.
Also if you use a Caching technique it will be faster for subsequent loads because in the the first method {tableView:cellForRowAtIndexPath:} you would load up the cached image immediately.
Aside from that, this should load your cell's pretty fast.
They obviously load the data from a local resource (plist, ManagedObject, ...)
Have a look at some sample code to draw a TableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellIdentifier = #"MyCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kCellIdentifier] autorelease];
}
return cell;
}
The dequeueReusableCellWithIdentifier: thing is one reason why TableViews in iOS can draw quickly. It works somehow like this:
1)You provide an identifier for a cell you're creating.
2)Cells that are visible at first get alloced (with identifier)
3)When a Cell is moved off the screen it gets put on a pile MyCellIdentifier
4)Whenever the system needs to draw a cell of identifier:MyCellIdentifier it first looks whether there are any cells currently unused on the MyCellIdentifier pile. If that's the case it picks one off the pile and thus doesn't have to alloc a new one. That way expensive allocing can be kept at a minimum.
I hope this answers your question :)
Related
My tableview consists of images loading from server, this slow down my tableview scrolling. Below is my code . any idea, please help me.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomTblViewCellFacetoface *cell = (CustomTblViewCellFacetoface *) [tableView dequeueReusableCellWithIdentifier:#"cellA"];
if (cell == nil)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomTblViewCellFacetofaceNib" owner:Nil options:nil];
for (id currentObject in topLevelObjects)
{
if ([currentObject isKindOfClass:[UITableViewCell class]])
{
cell = (CustomTblViewCellFacetoface *) currentObject;
break;
}
}
}
// configure cell
IStructFacetofaceRequests *objappointmentdetails = [M_ArrFacetofaceRequests objectAtIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor clearColor];
cell.m_CtrllblName.text = objappointmentdetails.m_strUsername;
cell.m_CtrllblVenue.text = [NSString stringWithFormat:#"Venue : %#",objappointmentdetails.m_strVenue];
cell.m_CtrllblDate.text = [NSString stringWithFormat:#"%# - %#",objappointmentdetails.m_strStartDate,objappointmentdetails.m_strEndDate];
[cell.m_CtrllblName setTextColor:[UIColor colorWithRed:(113/255.f) green:(113/255.f) blue:(113/255.f) alpha:1.0f]];
[cell.m_CtrllblVenue setTextColor:[UIColor colorWithRed:(113/255.f) green:(113/255.f) blue:(113/255.f) alpha:1.0f]];
[cell.m_CtrllblDate setTextColor:[UIColor colorWithRed:(113/255.f) green:(113/255.f) blue:(113/255.f) alpha:1.0f]];
cell.m_CtrllblName.font=[UIFont fontWithName:#"Arial-BoldMT" size:16];
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: objappointmentdetails.m_strImageurl]];
cell.m_CtrlImgViewUser.image=[UIImage imageWithData: imageData];
[cell.m_CtrlBtnView addTarget:self action:#selector(MoveToNextView:) forControlEvents:UIControlEventTouchUpInside];
cell.m_CtrlBtnView.tag=indexPath.row;
return cell;
}
this is the code i used in cellfor row at index path.
You can solve that by using Asynchronous loading..
please go through this tutorial..
http://www.markj.net/iphone-asynchronous-table-image/
You can use UITableView lazy loading .
here an sample from apple
and u can see this project at github
Yes, UITableView is designed around the assumption of lazy loading. When you make a UITableView, you do not load any of the entries at all. Instead you wait for the system framework to call your cellForRowAtIndexPath method. That method gets called once for every cell that needs to be loaded, and that is just for the cells that are visible at the time. As the user scrolls the table view, new cells come into view, and your cellforRowAtIndexPath method gets called again for each new cell coming into view.
Now that is the general principle. But if your cells are being populated by data from a server, then there are some additional considerations. You could structure your cellForRowAtIndexPath to be as lazy as possible and call the server for each and every cell that comes into view. But the network delays would make the user experience really awful. So it is to your advantage to buffer up a number of cells worth of data ahead of time in a data structure other than the cells in a UITableView. That way, when cellForRowAtIndexPath gets called, you will be able to quickly supply the contents of the cell by constructing it from the buffered data. Exactly how much data to buffer depends on how large each data element is, and what else your application is doing in the way of memory allocation. Offhand, I see nothing wrong with buffering 500 to 1000 cells worth of data in your app. But don't mix up your buffering of the data with the UITableView's queuing and reusing of cells. There is no reason to maintain a large number of cells ready to go - just the data that goes into those cells.
This is the code that i am using to display my tableview custom cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"CustomCell";
CustomeCellHome *cell = (CustomeCellHome *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomeCellHome" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (CustomeCellHome *) currentObject;
break;
}
}
}
NSString *strImgUrl = [[[[myarray valueForKey:#"mykey1"]objectAtIndex:indexPath.row] valueForKey:#"url"] objectAtIndex:0];
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:cell.imgView];
cell.imgView.imageURL = [NSURL URLWithString:strImgUrl];
cell.lbl1.text = [[myarray valueForKey:#"mykey2"]objectAtIndex:indexPath.row];
cell.lbl2.text = [[myarray valueForKey:#"mykey3"]objectAtIndex:indexPath.row];
cell.lbl3.text = [[myarray valueForKey:#"mykey3"]objectAtIndex:indexPath.row];
cell.lbl4.text = [[myarray valueForKey:#"mykey4"]objectAtIndex:indexPath.row];
cell.lbl5.text = [[myarray valueForKey:#"mykey5"]objectAtIndex:indexPath.row];
cell.lbl6.text = [[myarray valueForKey:#"mykey6"]objectAtIndex:indexPath.row];
}
CustomeCellHome is sub class of UITableViewCell and i am using Nib to display it. CustomeCellHome have all the IBOutlet and connected to Nib. All IBOutlet is property and synthesize and i have released that in dealloc method.
I am displaying 50 records in tableview at a time. If user want to see more record he can see that by clicking on next button and also view the previous record by clicking on previous button.
When user presses next button i am calling web service and getting next 50 record from the server. I am keeping my previous 50 records in the array and adding this new records to that array. But in "myarray" i am keeping only 50 at a time to display in tableview (Means i am displaying 50 records only at a time).
When i reach to view the 850 to 900 or near to that record my table view is getting stretched and it will stopped and displaying after some time.
Also after viewing 1450 to 1500 or near to that record i am receiving memory warning and my app getting crashed. I am not getting this. Why is it so ?
I have checked with memory tool i didn't get any memory leak. Am i using wrong to display the custom cell or my web service call causes more memory ?
Please any one can guide me for this. Thanks in advance.
It sounds like you are consuming too much memory. You don't need to leak memory to have your app shot by the watchdog.
I would profile the memory consumption of your app as you load more and more data. You'll probably want to respond to a memoryWarning message and delete all of your objects except for the current set. You could (de)serialize the objects to disc to preserve user's bandwidth.
You don't show the lifespan of your objects, but assuming they are fetched/stored in your UI(Table)ViewController you'll want to implement code in - (void)didReceiveMemoryWarning or - (void)viewDidUnload (iOS5 vs. iOS6) to clear out the older data.
SideNote: is myarray actually a dictionary? It's good practice to give your variables descriptive names to make your code easier to understand (for others, and yourself in 2 months when you need to fix a bug).
I have a UITableView, and custom cells on it. On cell I have a UILabel, but before I set text to UILabel I did really hard work on text...like find the text in another text, highlight some words on it, and only then I set it to label. So when I scroll my list, it has delay because of this hard work. Any idea how to improve performance ? Maybe to do all hard work in another thread ??
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath {
static NSString *customCellIdentifier =
#"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
customCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomTableRow"
owner:self options:nil];
if (nib.count > 0) {
cell = self.customTableRow;
}
}
self.myLabel.text = [self giveMeTheTextThatINeed];
return cell;
}
[self giveMeTheTextThatINeed] - did a hard work on text that takes some time.
Make a new thread for every cell. this thread calls [self giveMeTheTextThatINeed:indexPath], and resets the label(s) in the cell. I'm assuming you can't get your data any faster, so you want to maintain the scrolling in the table and spin the hard work out to the thread. When the thread is finished, update the cell. You see this a lot in cells with a thumbnail image where the thumbnail only gets uploaded after a while, and is blank or has a placeholder there first.
Any way for you to precompute the values you'll need? In other words, start doing your "hard work" (in another thread) when the app starts, and store it somewhere so that, if it's ready, you can just grab it when the table view asks for it. It's hard to answer without more detail about what the hard work is and how much data we're talking about.
I don't know about just doing the hard work on another thread as you suggested, since you still have to give something to tableView:cellForRowAtIndexPath:. I suppose you could return some kind of template cell at first, and then update it with reloadRowsAtIndexPaths:withRowAnimation: when the hard work is done.
I believe the method giveMeTheTextThatINeed needs to take the current cell as one of the parameters (or another parameter dependent on the cell content), e.g.: [self giveMeTheTextThatINeed:indexPath]. Otherwise you could store the text as an instance variable and set it in all cells from that variable.
So, with that in mind, the easiest way is to store the result of the computation in an additional dictionary, where indexPath (or the other parameter) would be the key:
self.myLabel.text = [self->myDictionary objectForKey:indexPath];
Now, you could either pre-populate that dictionary before the cells are drawn (e.g. in viewWillAppear), or cache them once they are calculated so that they are not recalculated when the cells are scrolled, e.g.:
NSString* calculatedText = [self->myDictionary objectForKey:indexPath];
if(calculatedText == nil)
{
calculatedText = [self giveMeTheTextThatINeed:indexPath];
[self->myDicationary setValue:calculatedText forKey:indexPath];
}
self.myLabel.text = calculatedText;
I am creating an application which uses a web service.And retrieves a list of users and there details like images, user id and there names.I displayed all the information related to users in table view, Thus each of the cell in the table view has an image with the for tables in it. I am using a Custom cell class to create the individual cells. selecting any row in table presents a detail view (using navigation) which show a larger image and all the details related to the particular user i selected.
in customcell and detail view class i am using web view to display image.
But when i run the app the images gets a bit of delay to display and same happens with the detail view.
Is there any alternative so that i can improve the performance of the table view so that i can have smooth scrolling of table view with out any delay in image loading and detail view??
here is the code...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CustomCellIdentifier = #"CustomCellIdentifier ";
CustomCell *cell = (CustomCell *)[tableView
dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil ];
//cell = [[CustomCell alloc] initwi ];
for (id oneObject in nib)
if ([oneObject isKindOfClass:[CustomCell class]])
cell = (CustomCell *)oneObject;
}
NSUInteger row = [indexPath row];
NSString *imageName = [imgArray objectAtIndex:row];
NSString *completeImageUrl = [[NSString alloc] initWithFormat:#"http://122.160.153.166:201/%#", imageName];
NSURL *url = [NSURL URLWithString:completeImageUrl];
//NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData]; /// check to see if we are getting all the arrays such as image array and userId array and name array of same size..
if(image == nil)
{
}
cell.imgView.image = image; /// other wise an execption of out out array range will be shown
[image release];
[imageName release];
//[cell.webView initWithFrame:CGRectZero];
//[cell.webView loadRequest:requestObj];
//cell.webView.frame = CGRectMake(20, 0, 80.0, 64);
cell.userIdLabel.text = (NSString *)[userId objectAtIndex:row];
cell.nameLabel.text = (NSString *)[userName objectAtIndex:row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}`
i think problem can be in imgArray array as i am setting it from other class. Where i request the web service and fetched all the data about users .
Any help is greatly appreciated . Thanks in advance
I got the same problem. For that I used EGOImageCache and some thing.
Find this Url for EGOImage
Download files those names starts with "EGO", and add those to your project.
and write the following code where ever you want to put the image:
EGOImageView *imgView = [[EGOImageView alloc] initWithFrame:CGRectMake(50,50,220,360)];
NSString *imageName = [imgArray objectAtIndex:row];
NSString *completeImageUrl = [[NSString alloc] initWithFormat:#"http://122.160.153.166:201/%#", imageName];
NSURL *url = [NSURL URLWithString:completeImageUrl];
imgView.imageUrl = url;
[self.view addSubView:imgView];
Thats it. Use the similar code, where ever you want the image.
At the first time while loading it will take some time to download the image. But later it will cache the image and use it the next time.
Is there any alternative so that i can improve the performance of the table view so that i can have smooth scrolling of table view with out any delay in image loading
For this the answer is Lazy Loading of images in cells
You can implement lazy loading on webviewDidLoad which shows that webview has loaded completely
Using a UIWebView to display a single image is overkill. You should be using NSURLConnection (or one of many alternative HTTP wrappers/libraries) to load the image data and UIImageView to display it in each of your table cells. In my experience, there is no way (or at least no straightforward way) to eliminate the rendering delay when using UIWebView.
Why are you using the webview for displaying the images. Imageview should be used instead. If you are getting the images from the server then you should get those in separate thread and after you receive the images you should reload that particular row.
Some code snippet is like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
...
...
UIImage *img = (UIImage *)[imagesArray objectAtIndex:indexPath.row];
if(img == nil){
[imgView setImage:temporaryImage];
[NSThread detachNewThreadSelector:#selector(getTheThumnbails:) toTarget:self withObject:indexPath];
}
else
[imgView setImage:img];
for further assistance look at this
Hope this helps.
Thanks,
Madhup
okay finally i removed the uiweb view from my app and used Imaged view and succeed.
i used the link that was given by the developer of the tweetie . here it is link text
i used the ABTableViewCell class and created the whole cell using the code.
The scrolling was very jerky when i executed the app. after applying almost 4 hours and with the help of lazy loading i was able to run my app smoothly. I stored the data which is retrieved from url into an array then applied the concept in tableView:cellForRowAtIndexPath:
method..
Thanks you guys for helping me ...
Three20 has a rich-text table view cell class (TTStyledTextTableCell I think) - should render significantly faster than an embedded UIWebView. You could also roll your own, though that would take a lot longer.
I'm developing a demo RSS reader for iPhone. I obviously have a tableview to display the feeds, and then a detailed view. Some of this feeds have a thumbnail that I want to display on cell.imageview of the table, and some don't.
The problem is that when scrolling the table, loaded thumbnails start repeating on other cells, and I end up with a thumbnail on every cell.
Here's a piece of my code. I may upload screenshots later
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: #"rssItemCell"];
if(nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"rssItemCell"]autorelease];
}
BlogRss *item = [[[self rssParser]rssItems]objectAtIndex:indexPath.row];
cell.textLabel.text = [item title];
cell.detailTextLabel.text = [item description];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// Thumbnail if exists
if(noticia.imagePath != nil){
NSData* imageData;
#try {
imageData = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:item.imagePath]];
}
#catch (NSException * e) {
//Some error while downloading data
}
#finally {
item.image = [[UIImage alloc] initWithData:imageData];
[imageData release];
}
}
if(item.image != nil){
[[cell imageView] setImage:item.image];
}
return cell;
}
Any help will be very appreciated.
Easy one.
Cells are reused. Just ensure you clear the cell.imageView.image each time you fill the cell. You may just need to remove the if(item.image!=nil) line.
For a production application you probably also want to fetch the images in the background and implement a simple cache. There are plenty of examples of how to do that knocking around.
EDIT
RickiG makes a lot of good points : cellForRowAtIndexPath should be displaying the data from the model and as little as possible else!
The concept of just supplying a view-ready model is kind of good (I do it with ASP.NET MVC), but needs to be balanced against the JustInTime memory minimization techniques of iPhone and lets face it, you are not committing the real sin of trying to read back data from the controls on your tableview - that really doesn't work!
The worst thing you are doing is reading web data on cellForRowAtIndexPath as that will block the UK. Instead you should display a blank or placeholder image and trigger a background fetch that will update the model with the data, and then trigger a reload - preferably of the specific cell.
The UITableView is made to reflect a "model", so never try to
change data/views on the cell is self after you build it or reference the cell based on an index number or the like, make an array of your data, I usually build a separate CellViewEntity object that holds all the data I need on the cell, like title, detailtext etc. but also behavior stuff like, is it expanded, has it got special view visible etc.
I then build a UIView CellView that I populate with the graphics and methods that I need, e.g. - (void) shouldDisplayCheckmark:(BOOL) value and so on.
I set the tag of the CellVIew object - [cellView setTag:15] and release the CellView. Now I can reference it later without retaining it and let the UITableVIew decide what should be released/retained.
cellForRowAtIndexPath is called constantly by the SDK, when scrolling when updating, when a cell enter or leave the screen. So don't put heavy instantiations, web call etc. into this function.
In this if block.
if(nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"rssItemCell"]autorelease];
}
I instantiate a cell and a CellView and add the CellView object to the cells view.
(with the tag)
outside the if(cell == nil) I only update the CellVIew with the data from the model array.
This means that if the cell exists it will be reused and have its properties updated from the array, if not it is created and attached to the cell.
Outside the if statement I reference my CellView like this:
[(CellView*)[cell viewWithTag:15] updateValuesAccordingToModelArray:[array objectAtIndes:indexPath.row]]; //this will be the only code executed for a already existing cell.
I guess it might seem a bit over the limit, but you can not reference cells like this [uitableView cellAtRow:14] in a consistent way, because the UITableView caches cell that are off screen and changes the indexPath accordingly. This approach you can do with your array.
Will stop rambling now :) ... separate all data in a nice tableView friendly package and feed it to the table view - one way only.