I have a UITableView that displays a lot of images in the cells, and i am not quit happy about the scroll performance. My UITableView is something like the photo app on the iphone. Does anybody knows why the iphone foto app scrolls so damn fast as if their's nothing on the screen.
And does anybody has some tips/tricks to increase my performance/scrolling speed?
You should precache your images and not do it lazily. As you scroll your table the UITableViewDataSource:cellForRowAtIndexPath: method is called, and if you're loading your images in there then you'll see it requesting your cell contents as your scroll, creating latency in your application. Try putting making your cellForRowAtIndexPath: something like this:
NSDate *date = [NSDate date];
... your cell loading code ...
NSLog( #"Elapsed time to generate cell %.2d", [date timeIntervalSinceNow] );
You'll see how long you're spending for getting each cell.
To get around this, you can be as complex as you need to - if you have a lot of images, you're going to have to be more and more clever. You can do paged loading, where you keep track of the last cell requested's NSIndexPath, and determine if the scroll is going up or down and use +NSImage:imageNamed: to fetch at once some page worth of images forward (ie, 5 images forward of your current position), or whatever works out for you (taking advantage of the fact that people have to return their finger down to the bottom of the table to swipe again, and so the consumption of table elements has pauses - you can make your page size large enough to fill a swipe). This is probably still not great, though, because you'll just be suffering all your impact at once instead of a jittery load for every cell.
You can return control to the UI rapidly and allow the system to schedule your prefetched page of images using NSRunLoop:performSelector:target:argument:order:mode: off the main runloop using NSImage:imageNamed:, and then when the cell is requested if you're fetching far enough ahead it'll be available to display.
You need to be painfully aware of memory concerns though. If you're finding this to be an issue, use NSImage:initWithContentsOfFile:, which will clean up image caches in low memory situations. Depending on the strategy used by the cache invalidation algorithm these situations may cause a "stutter" as you purge caches and have to reload your invalidated prefetches.
Great scrolling performance results have been reported by subclassing UITableViewCell and drawing the contents of each cell directly. See the accepted answer for this question for more details and links to code samples.
The problem here is memory, you are loading too many high resolution pictures at once, the foto app does not use a tableview it uses a scroll view and it only loads a max of 3 pictures at a time , so memory is not a concern, if u are trying to do something similar to the foto app use a scroll view
Related
I'm experiencing long delays (1-3 seconds) between the calls to viewWillAppear and viewDidAppear.
This happens after loading a large view, sometimes printing wait_fences as well.
No connection to UIAlertView or any of the other causes I see in related questions.
There isn't anything going on in viewWillAppear, calling super and not performing any animations.
What could be the reason for this long delay?
I've experienced exactly the same thing.
I had a tableview with an image in each cell.
I preload the images into an array but was still having this weird delay between these calls.
Making the images smaller (from full camera size to 36X36) when put into the array did the trick.
I think the delay is just the UI loading all the elements.
Make your elements more efficient.
I am processing several large RSS feeds and displaying results in a TableView. The processing starts after the user clicks on the relevant tab. All works well, but as it takes a couple of seconds to process, the NIB and Table don't load until the processing finishes and it looks like the iPhone has seized up. I have added an Activity indicator to the NIB, but because it doesn't load until the table is ready to display, it appears too late to be of any use.
Does anyone have any ideas how to display a message to a user while the table builds/loads? I have tried loading a UIView first and adding the Table as a subview but, again, both seem to load only after the table is ready.
Guidance appreciated.
It's kind of hard to guess what's going on from your description but it looks like your calls aren't asynchronous. Here's what you should be doing in your code:
Make all calls asynchronous. You said your phone is seizing up. Makes it sound like your requests and responses are happening on the main thread. There are many libraries you could use to handle asynchronous calls. ASIHTTPRequest for one example....
Don't wait for the data to come in before displaying the tableView. It's a design principle that you load as much of the UI as possible so that the user has something to look at while your data loads up in the background. What you should be doing is initializing an NSMutableArray to hold the data. Initially this array will contain no objects. This is the array that you use in your data source methods: Use array size for numberOfRowsInSection and use the array objects in cellForRowAtIndexPath. Once your RSS feed XML comes in and is parsed, store that in your arrays and call [tableView reloadData]. This way you don't leave your users looking at a blank screen. (Another good practice is when the array size is zero, show one cell in your tableview that says "data is loading" or something).
When you first initialize and load up your table and then fire off those RSS feed requests, that's where you show an activity indicator view on the tableView. Stop animating the indicator when the RSS data comes in and your tableView reloads.
These are the standard steps you should follow while showing non local data in a tableview. Makes for a smooth user experience.
Like I said before, it seems from your question that your calls are not asynchronous. If I'm wrong, correct me and let's take it from there...
I have a UIScrollView which scrolls horizontally, I have lot of data to be displayed on that UIScrollView, when i create the uiscrollview I know exactly the size of the content that I would like to display so I create the frame accordingly.
I get the data from the server that i have to populate in the uiscrollview. I would like to retrieve one page of data at a time from the server, also I would like to retrieve the second page only when user has scrolled through the end of the first page, that way I will avoid pulling unnecessary data from the server.
Any suggestions on how to do this?
You should use the protocoled method - (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y == _currentOffsetHeight)
{
// Do what you want
}
}
See: iOS Developer Library: PageControl Sample Code
When you detect scrolls via the delegate methods, you can start loading data for the next views into memory. Also, be sure to keep the previously viewed view in memory. So for example (if the 5th view is on the screen):
0: not in memory
1: not in memory
2: not in memory
3: not in memory
4: in memory
5: in memory
6: in memory
7: not in memory
8: not in memory
Once you detect the user has arrived at page 5, for example, you start loading the next page. You might even want to consider loading +2 pages ahead, in case the scrolling occurs faster, or the user scrolls past a view. The scrollview also bounces, so if you're missing content on one side it might look bad.
Good luck!
I'm building a UITableView similar to iPod.app's album browsing view:
I'm importing all the artists and album artworks from the iPod library on first launch. Saving everything to CoreData and getting it back into an NSFetchedResultsController. I'm reusing cell identifiers and in my cellForRowAtIndexPath: method I have this code:
Artist *artist = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *identifier = #"bigCell";
SWArtistViewCell *cell = (SWArtistViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
cell = [[[SWArtistViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
cell.artistName = artist.artist_name;
cell.artworkImage = [UIImage imageWithData:artist.image];
[cell setNeedsDisplay];
return cell;
My SWArtistViewCell cell implements the drawRect: method to draw both the string and image:
[artworkImage drawInRect:CGRectMake(0,1,44,44)]
[artistName drawAtPoint:CGPointMake(54, 13) forWidth:200 withFont:[UIFont boldSystemFontOfSize:20] lineBreakMode:UILineBreakModeClip];
Scrolling is still choppy and I just can't figure out why. Apps like iPod and Twitter have butter smooth scrolling and yet they both draw some small image in the cell as I do.
All my views are opaque. What am I missing?
EDIT: here's what Shark says:
I'm not familiar with Shark. Any pointer as of what are these symbols related to? When I look at the trace of these, they all point to my drawRect: method, specifically the UIImage drawing.
Would it point to something else if the chokehold was the file reading? Is it definitely the drawing?
EDIT: retaining the image
I've done as pothibo suggested and added an artworkImage method to my Artist class that retains the image created with imageWithData:
- (UIImage*)artworkImage {
if(artworkImage == nil)
artworkImage = [[UIImage imageWithData:self.image] retain];
return artworkImage;
}
So now I can directly set the retained image to my TableViewCell as follow:
cell.artworkImage = artist.artworkImage;
I also set my setNeedsDisplay inside the setArtworkImage: method of my tableViewCell class. Scrolling is still laggy and Shark shows exactly the same results.
Your profiling data strongly suggests that the bottleneck is in the unpacking of your PNG images. My guess is that 58.5 % of your presented CPU time is spent unpacking PNG data (i.e. if the memcpy call is also included in the loading). Probably even more of the time is spent there, but hard to say without more data. My suggestions:
As stated before, keep loaded images in UIImage, not in NSData. This way you won't have to PNG-unpack every time you display an image.
Put the loading of your images in a worker thread, to not affect the responsiveness of the main thread (as much). Creating a worker is real easy:
[self performSelectorOnMainThread:#selector(preloadThreadEntry:) withObject:nil waitUntilDone:NO];
Preload images far ahead, like 100 rows or more (like 70 in the direction you're scrolling, keep 30 in the opposite direction). If all your images need to be 88x88 pixels on retina, 100 images would require no more than two MB.
When you profile more the calls to stuff named "png", "gz", "inflate" and so forth might not go way down your list, but they will certainly not affect the feeling of the application in such a bad way.
Only if you still have performance problems after this, I would recommend you look into scaling, and for instance loading "...#2x.png" images for retina. Good luck!
[UIImage imageWithData:] doesn't cache.
This means that CoreGraphic uncompress and process your image every single time you pass in that dataSource method.
I would change your artist's object to hold on a UIImage instead of NSData. You can always flush the image on memoryWarning if you get them a lot.
Also, I would not recommend using setNeedsDisplay inside the dataSource call, I would use that from within your cell.
SetNeedsDisplay is not a direct call to drawRect:
It only tells the os to draw the UIVIew again at the end of the runloop. You can call setNeedsDisplay 100 times in the same runloop and the OS will only call your drawRect method once.
If the delay's happening in your -drawRect, then you might want to take a look at this article: Tweetie's developer explains pretty thoroughly the method he used to get that smooth scrolling you're after. This has become a bit easier since then, though: CALayer has a shouldRasterize property that basically flattens its sublayers into a bitmap, which can then—as long as nothing changes inside the layer—give you much better performance when animating the layer around, as UITableView does when you scroll it. In this case, you'd probably apply that property to your individual UITableViewCells' layers.
My guess is that the delay is from storing images in Core Data. Core Data is usually not a good way to store large blobs of data.
A better solution would be to store the images as individual files on disk, using an album id to identify each image. Then you would setup an in memory cache to store the images in RAM for fast loading into your UIImageViews. The loading of the images from disk to RAM would ideally need to be done on a background thread (e.g. try performSelectorOnBackgroundThread) so that the I/O doesn't bog down the main thread (which will impact on your scrolling performance).
Addendum 1
If you're only calling -drawRect: once per cell load (as you should be), then the problem might be the scaling of the images. Scaling an image by drawing it in code using drawInRect will use CPU time, so an alternative approach is to scale the images as you receive them from the iPod library (if you need them in multiple sizes, save a version in each size you require). You may need to do this on a background thread when you import the data to avoid blocking the main (UI) thread.
One alternative thing to consider is that UIImageView may do it's scaling using Core Animation which would mean it is hardware accelerated (I'm not sure if this is actually the case, I'm just guessing). Switching to a UIImageView for the image would therefore get rid of the CPU burden of image scaling. You would have a slight increase in compositing overhead, but it might be the easiest way to get closer to "optimum" scrolling performance.
At this point your best bet is to use Instruments (previously Shark) to try and find bottlenecks in your code.
I'm using the three20 project for my iPhone app. I've narrowed my problem down and I'm now just trying to re-create the 'Web Images in Table' example that comes with the project. I've copied the code exactly as in the project, with the exception that I do not use the TTNavigator (which the example does) but I am adding my TTTableViewController manually to a tabBar.
The problem is as follows; the images in the table should load automatically from the web, like in the example. But they only load after I scroll the table up and down.
In the console it clearly says it is downloading the images, and you see the activity indicator spinning like forever.. And unless I scroll up and down once, the images will never appear.
Anyone? Thanks in advance.
P.S:
If I'm using this code in any random UIView, It also doesn't work (only shows a black square):
TTImageView* imageView = [[[TTImageView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)] autorelease];
imageView.autoresizesToImage = YES;
imageView.URL = #"http://webpimp.nl/logo.png";
[self.view addSubview:imageView];
If I put this code in my AppDelegate (right onto the window), it does work .. strange?
POSSIBLE SOLUTION:
Although I stopped using TTImageView for this purpose, I do think I found out what the problem was; threading (hence accepting the answer of Deniz Mert Edincik). If I started the asynchronous download (because basically that is all the TTImageView is, an asynchronous download) from anywhere BUT the main thread, it would not start. If I started the download on the main thread, it would start immediately..
Sounds like a threading problem to me, are you creating TTImageView in the main runloop?
I find one interesting thing. When I use combination TTTableViewController, TTTableViewDataSource and TTModel I have same problem with loading TTImageView. My problem was, that my implementation of Model methods 'isLoading' and 'isLoaded' don't return proper values after initialization of model. That forces me to call reload on model manualy in 'viewDidAppear' method and that causes image loading problem. So I repair my 'isLoading' and 'isLoaded' methods to both return 'NO' after Model init, and everything is fine.
When an image finishes loading try sending a reloadData message to the table view. This forces the table to recalculate the size of the rows and redraw the table. Just be careful that you don't start downloading the image again in response to this message.
I've written something similar to this where an image view will load its own image from the web.
Im my experience, when the image had loaded successfully but was not shown in its view, it was a case that the cell needed to be told to redraw.
When you scroll the table view, the cells are set to redraw when the come onscreen, which is why they appear when you scroll.
When the image loads, tell the cell that it is sitting in to redraw by sending it the message setNeedsDisplay.
That way, when the image finishes downloading, the cell its sitting in (and only that cell) will redraw itself to show the new image.
It's possible that you might not need to redraw the entire cell and might be able to get away with simply redrawing the image view using the same method call. In my experience, my table cells view hierarchy was flattened, so I had to redraw the whole cell.
I don't have an answer for what you want to do, but I will say that this is considered a feature, and the expected behavior. You use TTImageView in UITableView when you want to do lazy loading of images. TTImageView will only load the images whose frames are visible on the screen. That way, the device uses its network resources to download images that the user has in front of them, rather than a bunch of images that the user isn't even trying to look at.
Consider having a long list that may contain a couple hundred thumbnail images (like a list of friends). I know from experience that if you kick off 100+ image requests on older devices, the memory usage will go through the roof, and your app will likely crash. TTImageView solves this problem.
This is a thread problem. You can load the images by including the line:
[TTURLRequestQueue mainQueue].suspended = NO;
in - (void)didLoadModel:(BOOL)firstTime.