Performance navigating through several full screen images on iPad - performance

I need to let user navigate back and forward through different images (from 10 to 20) with a tap.
All the images are 1024x768 JPG's so I don't need to resize or transform them.
There are no animations between them (I'll switch them with a removeFromSuperView and addSubView).
All I want is avoid loading time or unresponding touch, so I actually was thinking about these possible solutions:
Load each image singularly on tap;
Load 3 images: previous, actual and next one;
Load an array or a uiviewimage with all the images and iterate through it;
I will avoid imageNamed and I'll use imageWithContentOfFile or imageWithData.
Which solution do you think is the best one?
Could solutions 1. and 3. bring some performance issue?

Method 1 will be good : iOS devices can load full screen images really fast. Especially if your images don't have alpha. It will depends on images but it takes around 0.05 seconds for a classic png. This means that users will not notice the waiting time if you have to change after a tap especially if you had a fade transition between images.
Things can get harder if user can scroll through images. In this case, I would advise to use UITableView. They behave like UISCrollView and they load/unload pages fastly and smoothly.
To get an horizontal table view, you can use this code which works perfectly : https://github.com/alekseyn/EasyTableView

If your upper limit for number of images is 20, just preload an array of UIImages, and set the UIImageView.image property on response to touch - don't worry about swapping views, reusing a single UIImageView will be fine.
I wouldn't worry about performance unless the upper limit rises much higher - if it does, a dynamic cache like option 2 would be a better choice, but more programming.
If you are concerned about performance in an iPad application destined for the app store, always remember to test on a first generation iPad, since there was a major performance jump after the original.

I have actually done this before. With large images, you are going to have to be careful with memory. I originally loaded all of my images into an NSArray but I experienced memory warning and crashes.
My implementation uses a UIScrollView with paging. I have to arrays, one contains all of the image names and the other is mutable and contains only a few UIImageViews. I record the current 'page' that the scroll view is on, and when I land on an image I ensure that the mutable array contains that image and two images on either side of it (and remove any other images from the array).
The problem with this implementation is that you keep having to read images from disk which will be slow on the main thread. Sooo when I initially create the UIImageViews I add a UIActivityIndicator to them. Then, I pass my array of UIImageViews to a method in the background that actually loads the UIImage and then makes the respective UIImageView set the image on the main thread like so:
// called before you try to load an image in a background thread
[imageView addObserver:self forKeyPath:#"image" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// called in the background thread after you load the image from disk
[imageView performSelectorOnMainThread:#selector(setImage:) withObject:fullImage waitUntilDone:NO];`
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
UIImageView *imageView = (UIImageView *)object;
[[imageView viewWithTag:1] removeFromSuperview]; // this is the activity indicator
[imageView removeObserver:self forKeyPath:#"image"];
}

Related

UICollectionView scroll lag

I have setup a collection view with 10 subviews in a cell.
The subviews are
-imageview with label on it
-text view
-imageview
-uilabel
-imageview
-uilabel
Initially the collection view have 15 cells displayed at the time on an iPad. No when I scroll the scroll pauses when it is time to replace the cells at the bottom or top(reusing cells). I removed the shadow but still the same issue.
So the problem happens when the old cell is reused causing a lag.
Btw, no images loaded via the network.
I had the answer to this long time ago but for the benefit of others and who may be in the same issue.
Apart from removing shadows, you also need to remove "clear color" backgrounds. Any additional drawing that will require additional processing should be removed or replaced with an alternative. Any heavy lifting, text formatting, date formatting should be done before even showing the collection or table views. Make sure you cell only does the presenting and not processing. If you can't avoid it do the processing at another thread.
To measure the rate of the scroll you will need to use the instruments > graphics > core animation tool to measure the frame rate.
Try it and you will notice a difference.
EDIT: No need to experiment with autoresizing masks, just read this short article about UICollectionView performance boost http://noxytrux.github.io/blog/2014/09/25/ios8-weirdness-part3-laggy-uicollectionview/
It is probably an autolayout overhead. Consider trying autoresizing masks instead.
You can just commit everything and make an experiment:
turn off autolayout on your cell xib file
run an app to test performance (don't worry about messed up layout)
setup autoresizing masks (and do layout in code if needed) instead of autolayout if the effect is noticeable
I fixed my UICollectionView performance problems this way. It helps most when you have a lot of visible cells at a time.
Also, if you have image views, please see this answer Setting image property of UIImageView causes major lag
Don't forget about Instruments: run Time Profiler and see what eats your main thread time most.
I guess the issue can be also because of ImageView.
Using Kingfisher 5 for prefetching, loading, showing and caching images will solve the issue.
let url = URL(fileURLWithPath: path)
let provider = LocalFileImageDataProvider(fileURL: url)
imageView.kf.setImage(with: provider)
https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet#image-from-local-file
fileprivate func downloadForPerformance()->void{
for i in urlToFetchImageArray {
var image: UIImage = dowloadImageFunc(link: i.urlString() );
imageArray.append( image );
}
}
fileprivate func dowloadImageFunc(link: String)->UIImage {
let url = URL(string: link)
let data = try? Data(contentsOf: url!)
return UIImage(data: data!) ?? UIImage(named: "default.png")!;
}
cell?.mainImage.image = self.imageArrayPerformance[indexPath.row];
Usually the problem is slow download as each cell is dequeue.
Call downloadForPerformance func before the view has appeared.
This will fill an array called imageArray as a global variable
Then use that array in cellForItem func
basically have an array of already downloaded images you will need, make this a [UIImage] array, then use theImageArray[indexPath.row]

Object 3D rotation using png's

I want to achieve an object 3D rotation in Xcode that instead of using openGL uses a set of prerendered pngs. This would allow for much more complex 3D animations in terms of polygons and light effects. So far I have achieved to build subclass of UIView that contains a UIScrollView and in the scrollViewDidScroll delegate method it scrubs through 360 png images depending on the content offset of the UIScrollView. This does exactly what I want to achieve except a few major problems.
I've tried three different methods to swap the images.
Method 1:
When the View is being initialized I put all UIImages in UIImageViews and those on screen with alpha = 0 and then set the respective imageView's alpha to 1 on scrollViewDidScroll
Method 2:
When the View is being initialized I put all UIImages in an array and put a single UIImageView on screen. In scrollViewDidScroll I set the respective image from the array as the image of the UIImageView
Method 3:
When the View is being initialized I save all imageNames in an Array and put a sing UIImageView on the screen. In scrollViewDidScroll I create a UIImage with the name from the array for the respective index and set this as the image for my UIImageView
All three are very memory consuming and will eventually cause memory warnings or crashes. While method three is slightly less memory expensive it's also a lot laggier.
Is there any method to do this memory efficient and fast without having to use openGL??
Edit: Theodore Gray achieved this in his Elements app in an awesome way and I can't find out how. See here: http://www.youtube.com/watch?v=nHiEqf5wb3g&feature=player_embedded

How to use sprite sheets effectively?

What I want to do:
I want to use sprite sheets to load all my enemies in the game. They would have to be removed once they are either destroyed by the good guy or when they go off screen. I have 6-7 enemies some of which are animations. I will be reusing them multiple times. I wan to load and unload them effectively from the memory.
What I am doing:
I first load the spritesheets:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"Obstacles.plist"];
CCSpriteBatchNode *obstaclesspriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"Obstacles.pvr.ccz"];
[self addChild:obstaclesspriteSheet];
I have a class called BadBoys which handles the bad guys. Every time I want to create a bad guy I create an instance of the class. Inside the class I create the sprite and add it to the layer.
baddies[x] = [[BadBoys alloc] init];
//INSIDE THE CLASS
baddie = [CCSprite spriteWithSpriteFrameName:#"cannonball-hd.png"];
I remove the sprite from the layer when it gets destroyed and then release the instance of the class.
[self removeChild:[baddies[x] getBaddie] cleanup:YES]; //getBaddie returns the sprite
[baddies[x] release];
I know this is a good way to do it. What I want to know is if this is the most efficient way of doing this? I thought of another way of loading the image:
Load the image asynchronously to the CCTextureCache.
Then create a sprite using the texture from the cache.
Add it to a NSMutableArray which will hold all enemies that are alive
Then when I dont need it anymore I can destroy it the following way:
CCTexture2D * texture = spriteName.texture;
[spriteName.parent removeChild:spriteName cleanup:YES];
[[CCTextureCache sharedTextureCache] removeTexture:texture];
[backgroundSprites removeObject:spriteName];
Is this a better method? Please share your views and suggestions. Thanks.
What you're doing is good, as you said.
Your alternative is a terrible idea. I'll tell you why: you will want to avoid loading textures into memory and removing them from memory frequently during gameplay. Loading a texture from flash memory is slow. Removing the texture from memory also takes a little time, and is rather pointless if you have enough free memory available anyway.
Furthermore if you're using a spritesheet and you're removing one obstacle but try to remove the obstacle's texture (which is Obstacles.pvr.ccz) from memory as well, then the texture cache won't remove the texture from memory anyway. Because it's still being used by the spritesheet.
Lastly: premature optimization is the root of all evil.

CATiledLayer blinks after zoom

I have UIScrollView with BIG subview with lots small CATiledLayers displaying images.
With zoomDidEnd I set new scale:
[tilesContainer setContentScaleFactor:scale];
cause redraw tilesContainer view with all it's layers with:
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
And I get perfect result: detailed images after zoom.
But there is a blink of all those layers (when loading new data).
Is it possible to hide it somehow?
I need to hold the old images in layers till load the new one.
Thanks a lot for any help!
subclass the CATiledLayer and return fadeDuration of 0 to disbale the "blink".
fadeDuration
The time, in seconds, that newly added images take to "fade-in" to the rendered representation of the tiled layer.
The default implementation returns 0.25 seconds.

UIImageView UIScrollView HighRes Image memory warning

I'm populating a UIScrollView on iPad with above 10 photos fullscreen in landscape mode (1024x768). I'm facing out a very big problem: I load all the UIImageView asynchronously using GDC, and the add all the view to the ScrollView as subview.
When I load a different gallery, first I remove all the View in the UIScrollVIew, and then reload asynchronously all the thing.
If I don't switch between the gallery fast, there's no problem; instead, if I switch between a gallery and other, I receive different memory warning and the application crash. I look with instruments, but there's no leak and the memory consumpion is intensive when I add all the view to subview, but it's not raising.
What can be the problem? Maybe I can use NSAutoreleasePool to force the purging of objects?
When you use max resolution photos inside scrollView you can face memory problems
If you have a small scrollView and you want to show thumbnails you can't use the full size photo for the thumbnail you should create in pixelmator, Photoshop etc. a small photo of your photos with this approach you won't face memory problems
If you have a full screen scrollView with full screen imageViews you can remove fromSuperView the images that aren't visible in the scrollView with this approach all your memory problems will disappear
I hope this help
Good luck

Resources