PDA

View Full Version : Paging or lazy loading for UIScrollView?




mrl72
Jan 25, 2011, 09:27 PM
I'm in the middle of developing a photo gallery iPad app and have hit what looks like some memory issues. I am basically adding thumbnails (which are UIButtons) via add subview to the scrollview. Works fine until I hit about 100 or so buttons/images so I'm now trying to decide best user experience and whether to implement paging to flip through the pages of thumbnails (instead of scrolling) or use lazy loading to load/unload the thumbnails as the user scrolls up/down. From what I've read some devs have experienced some "jerkiness" with the latter method, is this normal behavior of lazy loading? I am potentially looking at anywhere between 1 and 1000 thumbnails (about 18 visible on one page) that could be loaded and note they are all cached to disk so not loading from URLs, and are pretty small in size.

Cheers.



North Bronson
Jan 25, 2011, 11:39 PM
First of all, make sure that the thumbnails are the same number of pixels that you plan on displaying on the device. Scaling is expensive, and it could definitely hurt your performance if the system has to scale each of these images up or down after reading them in.

You might want to try loading the images on a background thread. Doing IO work on the main thread could hurt your performance. You might not get a giant win from doing this asynchronously (unless they're coming from the cloud), so you have to compare the benefits against the extra development time involved. If this is professional work, you might not want to go through the trouble, but if you're using this to learn more, go for it; it could be a really good exercise.

PhoneyDeveloper
Jan 25, 2011, 11:59 PM
Why do you think you're hitting memory problems?

In terms of the user finding the image they want I would limit the number of images on screen at one time. 1000 sounds like a lot. Paging sounds like an obvious thing to try.

Also, consider a design like UITableView where images/cells are loaded on demand and only a few are required to exist at one time.

mrl72
Jan 26, 2011, 12:43 AM
First of all, make sure that the thumbnails are the same number of pixels that you plan on displaying on the device. Scaling is expensive, and it could definitely hurt your performance if the system has to scale each of these images up or down after reading them in.

You might want to try loading the images on a background thread. Doing IO work on the main thread could hurt your performance. You might not get a giant win from doing this asynchronously (unless they're coming from the cloud), so you have to compare the benefits against the extra development time involved. If this is professional work, you might not want to go through the trouble, but if you're using this to learn more, go for it; it could be a really good exercise.

Each thumbnail is 190px x 190px. The app does do some scaling currently but it only does that one time as the data is brought down from the server. It's actually pretty fast since the source images are only slightly larger. Saying that, as the images are added to the buttons I'm applying a mask to the image (a PNG which rounds the corners). Perhaps it's there that is causing the problems?

Why do you think you're hitting memory problems?

In terms of the user finding the image they want I would limit the number of images on screen at one time. 1000 sounds like a lot. Paging sounds like an obvious thing to try.

Also, consider a design like UITableView where images/cells are loaded on demand and only a few are required to exist at one time.

App works fine with about 100 images or so but as soon as I go 200+ it just crashes with a signal 0 error which I'm assuming is memory (although works fine on simulator). Since the images a presented in a specific order it's fine for the user to just scroll down. I say 1000 images but likelihood 200 is the average.

It's funny, I started out with a UITableView and discarded it because I wanted to implement a drag/drop feature so the user could move the thumbnails around. I no longer need that feature so a visit to the tableview again might be a good idea.

Thanks.

Edit: I did run Instruments but am not familiar enough with the results it shows me. I do recall seeing about 8MB in allocations and the leak counter constantly increasing (although I have no leaks according to build -> analyze).

mrl72
Jan 26, 2011, 01:56 PM
Why do you think you're hitting memory problems?


I'm getting "Received memory warning. Level=1" during the method that adds the buttons with images to the scrollview. This is with about 130 buttons all sized at 190x190. I'm looking at the tableview solution now..

Cheers.

PhoneyDeveloper
Jan 26, 2011, 02:51 PM
Few comments:

I wasn't suggesting that your picker LOOK like a tableview but that it ACT like a tableview. Tableviews don't require that all the data be in memory. They ask for only the data that are needed to display. However if you want to make it look like a tableview that's OK too.

I have a need for an image picker in an app I'm currently working on. The image needs to be displayed with the filename and a title for each image. I haven't decided yet for sure but I'm considering using a tableview for this rather than the usual grid display of image thumbnails.

Does your app get memory warnings?

Your images take up about 144K each in memory and 130 of them take up about 18MB. That is a lot of memory although not an extreme amount. However if your app is taking up that memory all at one time it's possible that a memory warning occurs but your app doesn't get it so it's killed.

Loading the images into memory as needed rather than all at once may make this better. Responding to memory warnings by releasing most or all the images will probably make this work.

mrl72
Jan 26, 2011, 03:34 PM
Ah ok. Funnily enough everything was in a tableview, I actually used the code from ZZGridView and modified it some. Then took it all out because I didn't want the buttons to be limited to the confines of a cell, of course in doing so has created these issues. This really has to be scalable and account for infinite # of images.

Question: Is it possible to use the touch events of a scroll view and load x images ahead and unload images behind WITHOUT getting jerky scrolling? Note all the thumbnails are already cached to disk. And does that mean as I scroll down I have to remove the buttons, or remove the images from the buttons? I can see it getting messy if I have to remove them and add them back in.

Running through Instruments: Allocations: Live bytes: 10MB, Overall: 60MB.

Is that bad?

Update: I switched to page control with lazy load and it works pretty good, no memory issues at all now. I think I'll stick with page control for the time being, it's not as bad as I thought and guess makes more sense from a user experience point of view rather than scrolling up/down through hundreds of thumbnails.

Cheers.

PhoneyDeveloper
Jan 27, 2011, 09:26 AM
There is an Apple sample called LazyTableImages. It loads images from the web that are displayed in a table. It's an example of how to load images lazily while not scrolling jerkily.

I think that jerky scrolling comes from slowness in creating the rows. If instead you create the rows and add the images later, only when they're available, you get smooth scrolling. You should be able to do the same with a grid of images as well.

60MB is at the high end of what's available. You can get more than that but you also will probably see memory warnings.

mrl72
Jan 27, 2011, 09:59 AM
Yeah you're right. The slowness comes from me applying the PNG mask as the view is being loaded off screen (when the user scrolls). I'm working on a method right now that passes the image loading to the background and so only loads up the buttons without images to start and then loads them in. I wouldn't mind a sanity check though, here's what I'm thinking:

When the page is scrolled by the user it calls the custom populateview method that always loads up 3 views, one behind (unless at page 0), current and one in front (as per Apple's pagecontrol sample which I've tweaked). Within this method about 20 buttons are added to the view, I plan on adding the buttons without the background image during this load. At the end of the method I'm going to run in the background another method that references an array of images that are needed for the particular view that is currently loaded. This background method will get the image from the disk, apply the mask and then reference the button by tag and set the background image to the new image.

Does that sound like it will work?

Cheers.

PhoneyDeveloper
Jan 27, 2011, 10:52 AM
Should work. There are some things that can't be done in a background thread so make sure you don't try to do them.

You can't access an imageView from the background. All CG functions can be accessed from the background. I'm not certain about accessing UIImages.

Some things have been made thread-safe in OS 4.0. Double-check with the docs if you're not sure.