How to make ui responsive all the time and do background updating? - iphone

I am creating a application which displays 8 thumbnails per page and it can have n pages. Each of these thumbnails are UIViews and are added to UIScrollView. However i have implemented Paging using the Apple sample code.
The prob:
Each thumbnail(UIView) takes 150
millisecs to be created and added to
scroll view
Hence for 3 pages it takes awful
huge time to be created and added to
the UI Scrollview.
At this point the scroll view is not very respsonsive and it is very jerky and gives a bad user experience
How can i create the thumbnails and add them to UIScrollview without affecting the touch responsiveness? I want them to run independent of the main thread which is resposible for handling touch events (i suppose).
Also i would like to mention that when a thumbnail is created i trigger a Async download of the image and the delegate method is called when download is complete.
Let me know the the options i have to make this more responsive and update UI without affecting the touch operations. The page control works fine with lazy loading of the grid of thumbnails.
TIA,
Praveen S

Grand Central Dispatch is easy to use for background loading. But GCD is only for after iOS4. If you have to support iOS3, performSelectorInBackground/performSelectorOnMainThread or NSOperationQueue are helpful.
And, be careful almost UIKit classes are not thread-safe except drawing to a graphics context. For example, UIScrollView is not thread-safe, UIImage imageNamed: is not thread-safe, but UIImage imageWithContentsOfFile: is thread-safe.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
dispatch_apply([thumbnails count], concurrentQueue, ^(size_t index) {
Thumbnail *thumbnail = [thumbnails objectAtIndex:index];
thumbnail.image = [UIImage imageWithContentsOfFile:thumbnail.url];
dispatch_sync(mainQueue, ^{
/* update UIScrollView using thumbnail. It is safe because this block is on main thread. */
});
}
/* dispatch_apply waits until all blocks are done */
dispatch_async(mainQueue, ^{
/* do for all done. */
});
}

I was having a similar problem.
What i did was at an instance i kept only 3 pages in the memory and cleared remaining all.
If suppose there are 3 screens s1, s2, s3. And the user is viewing s2. Whenever he scrolls to s3 i will remove s1 and load a new page s4.
So that the users will have a better experience. And less memory will be occupied.

Whether you are using a subview or a separate ViewController for each "page" or element of the Scrollview, the jerkiness or poor performance can be helped by changing the location of your code.
Specifically the apple sample code for a scrollview with pagecontrol has something like this:
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
However, that code appears in their sample in the method "scrollViewDidScroll". It's trying to do multiple heavy lifting by both scrolling and loading at the same time. Even if your images are local this is nasty.
If you move this and related code including a reference to the current page to "scrollViewDidEndDecelerating" the jerkiness of the interface is resolved because the loading happens while the scrollview is no longer moving.

Related

setImage for UIImageView in uitableviewcell lags scrolling in iOS

I have a gallery view of photos that are downloaded from the internet so I used Enormego's EGOImageView. I noticed that when i scrolled down my tableview after the images were in the cache, the scrolling would lag. I immediately found that when the image was retrieved from the hard drive with return [UIImage imageWithContentsOfFile:cachePathForKey(key)]; it was working on the main thread so I added the operation to an NSOperationQueue. This reduced the lag by half but the scrolling still stuttered. After going through the code, I noticed that in the success method
- (void)imageLoaderDidLoad:(NSNotification*)notification {
if(![[[notification userInfo] objectForKey:#"imageURL"] isEqual:self.imageURL]) return;
UIImage* anImage = [[notification userInfo] objectForKey:#"image"];
self.image = anImage;
[self setNeedsDisplay];
if([self.delegate respondsToSelector:#selector(imageViewLoadedImage:)]) {
[self.delegate imageViewLoadedImage:self];
}
}
commenting out the self.image = anImage; got rid of the lag completely (but obviously I get no image). And as far as I can tell, if I want to alter the UI, it must be done in the main thread. Is there a way to set the image for the EGOImageView without it lagging the scrolling?
Note: the JPGs are around 50kB
Thanks
Subclass uitableViewCell and do your own drowing in drawContentView.
Resize images in background thread and then present them in cells.
P.S: if the code found on git hub is not good enough for you, try to write your own that suits your problem.
From what I understand, all the steps required to initiate an asynchronous disk query takes a couple milliseconds on the mainthread, and that is enough time for the scrolling to look like it stutters, so I decided to completely remove hard drive caching for large images and instead create an NSMutableDictionary which holds the UIImage as an object, and the NSURL.absoluteString as the key. This works seamlessly but obviously has the disadvantage of being a memory hog. I checked out the memory usage of some photo-sharing apps and I've been able to get the memory usage for the app to over 100MB so it seems everybody else is doing this.

Trouble with Displaying an Activity Indicator while Data Loads

I feel as though there is a really simple solution to my problem, but thus far I have had little success... I want to load my initial .xib file (exiting the default.png splash screen early), so that I may display an activity indicator while loading my html data and setting the text label fields created by my xib file.
Unfortunately, when I execute the following code below, I display my default.png image until all of the data is loaded from each website... What may I change in order to first display my mainView, then start the activity indicator, load my html data, and set the text labels in my mainView?
#implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
[activityIndicator startAnimating];
[self runTimer];
}
- (void)viewDidAppearBOOL)animated {
[super viewDidAppear:animated];
[self loadHTMLData1];
[self loadHTMLData2];
[self loadHTMLData3];
[self loadHTMLData4];
[activityIndicator stopAnimating];
}
...
It's all to do with how iOS updates the ui. When you call
[activityIndicator startAnimating];
it doesn't mean start animating immediately, it means you're telling the ui the next time you are updating the display, start animating.
All of this updating happens on the main thread (if you haven't made a thread, you're already on the main thread) so if you do something else that takes a long time, it will do this before updating the display.
There are a few ways to fix this and they all involve making another thread that runs in the background.
Take a look at NSOperation (and NSOperationQueue) - this will let you queue up individual tasks that iOS will run in the background for you. then when they are complete you can update your display again and turn off your activity indicator.
There's NSOperationQueue tutorials all over google :)
Hope that helps.

ScrollView with large images scrolls too slowly

When I am scrolling images frequently in a UIScrollView then after some images, the next image takes time to load... it's not taking too much time but looks odd.
Suppose I have 27 images in a scrollView. When I start to scroll these images, for 1 or 2 images it scrolls smoothly but when I scroll again to see the 3rd image it takes time to load. Then when I start the images scrolling again from the 3rd image, it behaves like before.
I can't load all 27 images at a time or my app crashes.
When I slowly scroll the scrollview then I don't have this problem.
My code is below:
//Taking image view for 27 images;
int x=0;
for(int i = 1; i<=27; i++) {
imageView = [[UIImageView alloc] init];
imageView .frame = CGRectMake(x,0,768,1024);
imageView.tag=i;
imageView.image=nil;
imageView.userInteractionEnabled=YES;
[contentView addSubview:imageView];
x+=768;
}
//setContentOffset of the scrollView -->ContentView
[contentView setContentOffset: CGPointMake((imageNumber-1)*768, 0) animated: YES];
//desire image which i want to see from the start of the scrollview
pageNumber=imageNumber;
int pageBefore=pageNumber-1;
int pageAfter=pageNumber+1;
//Views for image
for( UIImageView * views in [contentView subviews]){
if(views.tag==pageNumber){
if(views.image==nil){
NSLog(#"entering");
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageNumber]];
[views.image release];
}
}
if(views.tag==pageBefore){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageBefore]];
[views.image release];
}
}
if(views.tag==pageAfter){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageAfter]];
[views.image release];
}
}
My alarm bells rang when I saw this;
imageView .frame = CGRectMake(x,0,768,1024);
Apart from the space before .frame, are you saying that your images are 768x1024? That's HUGE and I suspect your problems are memory ones rather than code ones.
Be aware that in particular, using UIImage imageNamed: is likely to cause grief with such large images as that method caches the images in memory. You may wish to consider using alternative methods that load the image from a file each time.
You should try use the EGOImageView, it has caching build in which might help with your performance issues. You can implement a placeholder image to show the user that an image is being prepared for viewing. The image will load in another thread before being displayed, giving you smoother scrolling performance. The EGOImageView is part of the EGOImageLoading library.
https://github.com/tastefulworks/EGOImageLoading
As an alternative you could create your own lazy loading mechanism to increase scrolling performance. E.g. once a user stops scrolling for a second, start loading the image, otherwise display placeholder image if not yet the correct image is cached.
Edit: when thinking more about this issue, I realize caching won't help much (since you already load image from disk), but the asynchronous loading of images should help with the scroll performance, so make use of NSThread or NSOperation to load the image in a background thread, then notify the main thread that the image is loaded and ready for display.

How to update ui in background without losing ui responsiveness? [duplicate]

I am creating a application which displays 8 thumbnails per page and it can have n pages. Each of these thumbnails are UIViews and are added to UIScrollView. However i have implemented Paging using the Apple sample code.
The prob:
Each thumbnail(UIView) takes 150
millisecs to be created and added to
scroll view
Hence for 3 pages it takes awful
huge time to be created and added to
the UI Scrollview.
At this point the scroll view is not very respsonsive and it is very jerky and gives a bad user experience
How can i create the thumbnails and add them to UIScrollview without affecting the touch responsiveness? I want them to run independent of the main thread which is resposible for handling touch events (i suppose).
Also i would like to mention that when a thumbnail is created i trigger a Async download of the image and the delegate method is called when download is complete.
Let me know the the options i have to make this more responsive and update UI without affecting the touch operations. The page control works fine with lazy loading of the grid of thumbnails.
TIA,
Praveen S
Grand Central Dispatch is easy to use for background loading. But GCD is only for after iOS4. If you have to support iOS3, performSelectorInBackground/performSelectorOnMainThread or NSOperationQueue are helpful.
And, be careful almost UIKit classes are not thread-safe except drawing to a graphics context. For example, UIScrollView is not thread-safe, UIImage imageNamed: is not thread-safe, but UIImage imageWithContentsOfFile: is thread-safe.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
dispatch_apply([thumbnails count], concurrentQueue, ^(size_t index) {
Thumbnail *thumbnail = [thumbnails objectAtIndex:index];
thumbnail.image = [UIImage imageWithContentsOfFile:thumbnail.url];
dispatch_sync(mainQueue, ^{
/* update UIScrollView using thumbnail. It is safe because this block is on main thread. */
});
}
/* dispatch_apply waits until all blocks are done */
dispatch_async(mainQueue, ^{
/* do for all done. */
});
}
I was having a similar problem.
What i did was at an instance i kept only 3 pages in the memory and cleared remaining all.
If suppose there are 3 screens s1, s2, s3. And the user is viewing s2. Whenever he scrolls to s3 i will remove s1 and load a new page s4.
So that the users will have a better experience. And less memory will be occupied.
Whether you are using a subview or a separate ViewController for each "page" or element of the Scrollview, the jerkiness or poor performance can be helped by changing the location of your code.
Specifically the apple sample code for a scrollview with pagecontrol has something like this:
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
However, that code appears in their sample in the method "scrollViewDidScroll". It's trying to do multiple heavy lifting by both scrolling and loading at the same time. Even if your images are local this is nasty.
If you move this and related code including a reference to the current page to "scrollViewDidEndDecelerating" the jerkiness of the interface is resolved because the loading happens while the scrollview is no longer moving.

AssetsLibrary and ImageView -setImage Slowness

So this one is pretty odd ad I'm not sure if the trouble is with the AssetsLibrary API, but I can't figure out what else might be happening.
I am loading an array with ALAssets using the -enumerateAssetsUsingBlock method on ALAssetsGroup. When it completes, I am loading a custom image scroller. As the scroller finishes scrolling, I use NSInvocationOperations to load the images for the currently visible views (pages) from the photo library on disk. Once the image is loaded and is cached, it notifies the delegate which then grabs the image from the cache and displays it in an image view in the scroller.
Everything works fine, but the time it takes from when -setImage: actually gets called to the time it actually shows up visibly on the screen is unbearable--sometimes 10 seconds or more to actually show up.
I have tried it both with and without image resizing which adds almost nothing to the processing time when I do the resizing. As I said, the slowdown is somewhere after I call -setImage on the image view. Is anyone aware of some sort of aspect of the AssetLibrary API that might cause this?
Here's some relevant code:
- (void)setImagesForVisiblePages;
{
for (MomentImageView *page in visiblePages)
{
int index = [page index];
ALAsset *asset = [photos objectAtIndex:index];
UIImage *image = [assetImagesDictionary objectForKey:[self idForAsset:asset]];
// If the image has already been cached, load it into the
// image view. Otherwise, request the image be loaded from disk.
if (image)
{
[[page imageView] setImage:image];
}
else {
[self requestLoadImageForAsset:asset];
[[page imageView] setImage:nil];
}
}
}
This will probably mess up any web searches looking to solve problems with the AssetsLibrary, so for that I apologize. It turns out that the problem wasn't the AssetsLibrary at all, but rather my use of multi-threading. Once the image finished loading, I was posting a notification using the default NSNotificationCenter. It was posting it on the background thread which was then updating (or trying to update, at least) the UIImageView with -setImage. Once I changed it to use -performSelectorOnMainThread and had that selector set the image instead, all was well.
Seems no matter how familiar I get with multi-threading, I still forget the little gotchas from time to time.