iOS - Not allocating too much memory at once - ios

Trying to get around a crash that is happening on some iOS devices, in conjunction with advice from Apple to "not cause allocation spikes". How can I change this code to not happen all at once?
for (Item *item in self.items) {
ItemView *itemView = [[ItemView alloc] initWithFrame:CGRectMake(xPos, kYItemOffsetIphone, kItemWidthIphone, kItemHeightIphone) ];
itemView.delegate = self;
[itemView layoutWithData:item]; //this just adds an imageView and button
[self.scrollView addSubview:itemView];
xPos += kXItemSpacingIphone;
}
There are around 20 objects in the self.items array, which are used to build the 20 ItemViews. Again, is there some way to make this code less "allocation intensive"?

I personally do something along the lines of:
Make my view controller the delegate of the scroll view (if you do this in code, you have to modify your view controller's .h to say that it conforms to UIScrollViewDelegate).
Define a scrollViewDidScroll method that (a) determines the frame of the visible portion of the scroll view; (b) determine which of the subviews intersect with that visible portion; (c) load the items that are visible, and unload the ones that aren't.
So, for example, it might look something like the following:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// Determine the frame of the visible portion of the scrollview.
CGRect visibleScrollViewFrame = scrollView.bounds;
visibleScrollViewFrame.origin = scrollView.contentOffset;
// Now iterate through the various items, remove the ones that are not visible,
// and show the ones that are.
for (Item *itemObject in self.itemCollection)
{
// Determine the frame within the scrollview that the object does (or
// should) occupy.
CGRect itemObjectFrame = [self getItemObjectFrame:itemObject];
// see if those two frames intersect
if (CGRectIntersectsRect(visibleScrollViewFrame, itemObjectFrame))
{
// If it's visible, then load it (if it's not already).
// Personally, I have my object have a boolean property that
// tells me whether it's loaded or not. You can do this any
// way you want.
if (!itemObject.loaded)
[itemObject loadItem];
}
else
{
// If not, go ahead and unload it (if it's loaded) to conserve memory.
if (itemObject.loaded)
[itemObject unloadItem];
}
}
}
That's the basic idea. You can certainly optimize this logic based upon your app's particular design, but this is how I generally do it.

Related

Perfecting subview resizing in UICollectionView layout transition

I'm developing an app that has a UICollectionView - the collection view's job is to display data from a web service.
One feature of the app I am trying to implement is enabling the user to change the layout of this UICollectionView from a grid view to a table view.
I spent a lot of time trying to perfect this and I managed to get it to work. However there are some issues. The transition between the two layout doesn't look good and sometimes it breaks between switching views and my app is left with a view in an unexpected state. That only happens if the user switches between grid and table view very quickly (pressing the changeLayoutButton) continuously.
So, obviously there are some problems and I feel the code is a little fragile. I also need to fix the above mentioned issues.
I'll start off with how I implemented this view.
Implementation
Since I needed the two different cells (grideCell and tableViewCell) to show different things - I decided it would be better to subclass UICollectionViewFlowLayout since it does everything I need - all I need to do is change the cell sizes.
With that in mind I created two classes that subclassed UICollectionViewFlowLayout
This is how those two classes look:
BBTradeFeedTableViewLayout.m
#import "BBTradeFeedTableViewLayout.h"
#implementation BBTradeFeedTableViewLayout
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(320, 80);
self.minimumLineSpacing = 0.1f;
}
return self;
}
#end
BBTradeFeedGridViewLayout.m
#import "BBTradeFeedGridViewLayout.h"
#implementation BBTradeFeedGridViewLayout
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(159, 200);
self.minimumInteritemSpacing = 2;
self.minimumLineSpacing = 3;
}
return self;
}
#end
Very simple and as you can see - just changing the cell sizes.
Then in my viewControllerA class I implemented the UICollectionView like so:
Created the properties:
#property (strong, nonatomic) BBTradeFeedTableViewLayout *tableViewLayout;
#property (strong, nonatomic) BBTradeFeedGridViewLayout *grideLayout;
in viewDidLoad
/* Register the cells that need to be loaded for the layouts used */
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"BBItemTableViewCell" bundle:nil] forCellWithReuseIdentifier:#"TableItemCell"];
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"BBItemGridViewCell" bundle:nil] forCellWithReuseIdentifier:#"GridItemCell"];
The user taps a button to change between layouts:
-(void)changeViewLayoutButtonPressed
I use a BOOL to determine which layout is currently active and based on that I make the switch with this code:
[self.collectionView performBatchUpdates:^{
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:self.grideLayout animated:YES];
} completion:^(BOOL finished) {
}];
In cellForItemAtIndexPath
I determine which cells I should use (grid or tableView) and the load the data - that code looks like this:
if (self.gridLayoutActive == NO){
self.switchToTableLayout = NO;
BBItemTableViewCell *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath];
if ([self.searchArray count] > 0){
self.switchToTableLayout = NO;
tableItemCell.gridView = NO;
tableItemCell.backgroundColor = [UIColor whiteColor];
tableItemCell.item = self.searchArray[indexPath.row];
}
return tableItemCell;
}else
{
BBItemTableViewCell *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath];
if ([self.searchArray count] > 0){
self.switchToTableLayout = YES;
gridItemCell.gridView = YES;
gridItemCell.backgroundColor = [UIColor whiteColor];
gridItemCell.item = self.searchArray[indexPath.row];
}
return gridItemCell;
}
Lastly in the two cell classes - I just use the data to set the image / text as I need.
Also in grid cell - the image is bigger and I remove text I don't want - which was the primary reason for uses two cells.
I'd be interested in how to make this view look a little more fluid and less buggy in the UI. The look I am going for is just like eBays iOS app - they switch between three different views. I just need to switch between two different views.
#jrturton's answer is helpful, however unless I'm missing something it is really overcomplicating something very simple. I'll start with the points we agree on...
Prevent interaction while changing layouts
First off, I agree with the approach of disabling user interaction at the start of the layout transition & reenabling at the end (in the completion block) using [[UIApplication sharedApplication] begin/endIgnoringInteractionEvents] - this is much better than trying cancel an in-progress transition animation & immediately begin the reverse transition from the current state.
Simplify the layout transition by using a single cell class
Also, I very much agree with the suggestion to use the same cell class for each layout. Register a single cell class in viewDidLoad, and simplify your collectionView:cellForItemAtIndexPath: method to just dequeue a cell and set its data:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
BBItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
if ([self.searchArray count] > 0) {
cell.backgroundColor = [UIColor whiteColor];
cell.item = self.searchArray[indexPath.row];
}
return cell;
}
(Notice that the cell itself shouldn't (in all but exceptional cases) need to be aware of anything to do with what layout is currently in use, whether layouts are transitioning, or what the current transition progress is)
Then when you call setCollectionViewLayout:animated:completion: the collection view doesn't need to reload any new cells, it just sets up an animation block to change each cell's layout attributes (you don't need to call this method from inside an performBatchUpdates: block, nor do you need to invalidate the layout manually).
Animating the cell subviews
However as pointed out, you will notice that subviews of the cell jump immediately to their new layout's frames. The solution is to simply force immediate layout of the cells subviews when the layout attributes are updated:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
[self layoutIfNeeded];
}
(No need to create special layout attributes just for the transition)
Why does this work? When the collection view changes layouts, applyLayoutAttributes: is called for each cell as the collection view is setting up the animation block for that transition. But the layout of the cell's subviews is not done immediately - it is deferred to a later run loop - resulting in the actual subview layout changes not being incorporated into the animation block, so the subviews jump to their final positions immediately. Calling layoutIfNeeded means that we are telling the cell that we want the subview layout to happen immediately, so the layout is done within the animation block, and the subviews' frames are animated along with the cell itself.
It is true that using the standard setCollectionViewLayout:... API does restrict control of the animation timing. If you want to apply a custom easing animation curve then solutions like TLLayoutTransitioning demonstrate a handy way of taking advantage of interactive UICollectionViewTransitionLayout objects to take control of the animation timing. However, as long as only a linear animation of subviews is required I think most people will be satisfied with the default animation, especially given the one-line simplicity of implementing it.
For the record, I'm not keen on the lack of control of this animation myself, so implemented something similar to TLLayoutTransitioning. If this applies to you too, then please ignore my harsh reproval of #jrturton's otherwise great answer, and look into TLLayoutTransitioning or UICollectionViewTransitionLayouts implemented with timers :)
Grid / table transitions aren't as easy as a trivial demo would have you believe. They work fine when you've got a single label in the middle of the cell and a solid background, but once you have any real content in there, it falls over. This is why:
You have no control over the timing and nature of the animation.
While the frames of the cells in the layout are animated from one value to the next, the cells themselves (particularly if you are using two separate cells) don't seem to perform internal layout for each step of the animation so it seems to "flick" from one layout to the next inside each cell - your grid cell looks wrong in table size, or vice versa.
There are many different solutions. It's hard to recommend anything specific without seeing your cell's contents, but I've had success with the following:
take control of the animation using techniques like those shown here. You could also check out Facebook Pop to get better control over the transition but I haven't looked into that in any detail.
use the same cell for both layouts. Within layoutSubviews, calculate a transition distance from one layout to the other and use this to fade out or in unused elements, and to calculate nice transitional frames for your other elements. This prevents a jarring switch from one cell class to the other.
That's the approach I used here to fairly good effect.
It's harder work that relying on resizing masks or Autolayout but it's the extra work that makes things look good.
As for the issue when the user can toggle between the layouts too quickly - just disable the button when the transition starts, and re- enable it when you're done.
As a more practical example, here's a sample of the layout change (some of it is omitted) from the app linked above. Note that interaction is disabled while the transition occurs, I am using the transition layout from the project linked above, and there is a completion handler:
-(void)toggleLayout:(UIButton*)sender
{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
HMNNewsLayout newLayoutType = self.layoutType == HMNNewsLayoutTable ? HMNNewsLayoutGrid : HMNNewsLayoutTable;
UICollectionViewLayout *newLayout = [HMNNewsCollectionViewController collectionViewLayoutForType:newLayoutType];
HMNTransitionLayout *transitionLayout = (HMNTransitionLayout *)[self.collectionView transitionToCollectionViewLayout:newLayout duration:0.5 easing:QuarticEaseInOut completion:^(BOOL completed, BOOL finish)
{
[[NSUserDefaults standardUserDefaults] setInteger:newLayoutType forKey:HMNNewsLayoutTypeKey];
self.layoutType = newLayoutType;
sender.selected = !sender.selected;
for (HMNNewsCell *cell in self.collectionView.visibleCells)
{
cell.layoutType = newLayoutType;
}
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}];
[transitionLayout setUpdateLayoutAttributes:^UICollectionViewLayoutAttributes *(UICollectionViewLayoutAttributes *layoutAttributes, UICollectionViewLayoutAttributes *fromAttributes, UICollectionViewLayoutAttributes *toAttributes, CGFloat progress)
{
HMNTransitionLayoutAttributes *attributes = (HMNTransitionLayoutAttributes *)layoutAttributes;
attributes.progress = progress;
attributes.destinationLayoutType = newLayoutType;
return attributes;
}];
}
Inside the cell, which is the same cell for either layout, I have an image view and a label container. The label container holds all the labels and lays them out internally using auto layout. There are constant frame variables for the image view and the label container in each layout.
The layout attributes from the transition layout are a custom subclass which include a transition progress property, set in the update layout attributes block above. This is passed into the cell using the applyLayoutAttributes method (some other code omitted):
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
self.transitionProgress = 0;
if ([layoutAttributes isKindOfClass:[HMNTransitionLayoutAttributes class]])
{
HMNTransitionLayoutAttributes *attributes = (HMNTransitionLayoutAttributes *)layoutAttributes;
self.transitionProgress = attributes.progress;
}
[super applyLayoutAttributes:layoutAttributes];
}
layoutSubviews in the cell subclass does the hard work of interpolating between the two frames for the images and labels, if a transition is in progress:
-(void)layoutSubviews
{
[super layoutSubviews];
if (!self.transitionProgress)
{
switch (self.layoutType)
{
case HMNNewsLayoutTable:
self.imageView.frame = imageViewTableFrame;
self.labelContainer.frame = labelContainerTableFrame;
break;
case HMNNewsLayoutGrid:
self.imageView.frame = imageViewGridFrame;
self.labelContainer.frame = self.originalGridLabelFrame;
break;
}
}
else
{
CGRect fromImageFrame,toImageFrame,fromLabelFrame,toLabelFrame;
if (self.layoutType == HMNNewsLayoutTable)
{
fromImageFrame = imageViewTableFrame;
toImageFrame = imageViewGridFrame;
fromLabelFrame = labelContainerTableFrame;
toLabelFrame = self.originalGridLabelFrame;
}
else
{
fromImageFrame = imageViewGridFrame;
toImageFrame = imageViewTableFrame;
fromLabelFrame = self.originalGridLabelFrame;
toLabelFrame = labelContainerTableFrame;
}
CGFloat from = 1.0 - self.transitionProgress;
CGFloat to = self.transitionProgress;
self.imageView.frame = (CGRect)
{
.origin.x = from * fromImageFrame.origin.x + to * toImageFrame.origin.x,
.origin.y = from * fromImageFrame.origin.y + to * toImageFrame.origin.y,
.size.width = from * fromImageFrame.size.width + to * toImageFrame.size.width,
.size.height = from * fromImageFrame.size.height + to * toImageFrame.size.height
};
self.labelContainer.frame = (CGRect)
{
.origin.x = from * fromLabelFrame.origin.x + to * toLabelFrame.origin.x,
.origin.y = from * fromLabelFrame.origin.y + to * toLabelFrame.origin.y,
.size.width = from * fromLabelFrame.size.width + to * toLabelFrame.size.width,
.size.height = from * fromLabelFrame.size.height + to * toLabelFrame.size.height
};
}
self.headlineLabel.preferredMaxLayoutWidth = self.labelContainer.frame.size.width;
}
And that's about it. Basically you need a way of telling the cell how far through the transition it is, which you need the layout transitioning library (or, as I say, Facebook pop might do this) for, and then you need to make sure you get nice values for layout when transitioning between the two.

UIScrollView taking too much time

am trying to place a ScrollView in my app that has 1000,000 record, this scrollView will load when the app launches, so the app is not running until the million 1000 000 record which takes a lot of time, i was wondering is there any way to show the app and the scrollView while records are loading (show the scrollView while adding its records), below the code am using:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self loadIt];
}
- (void)loadIt{
float startX = 0;
float startY = 0;
[_bigScroll setContentSize:CGSizeMake(320, 312500)];
_bigScroll.pagingEnabled = NO;
for (counter=0; counter<999999; counter++)
{
UIButton *tester=[[UIButton alloc] initWithFrame:CGRectMake(startX, startY, 10, 10)];
if (counter % 2 == 0) {
[tester setBackgroundColor:[UIColor whiteColor]];
}
else
{
[tester setBackgroundColor:[UIColor grayColor]];
}
[_bigScroll addSubview:tester];
[tester release];
if (startX == 320) {
startX = 0;
startY += 10;
}
else
startX += 10;
NSLog(#"counter = %d", counter);
}
}
Please advice.
Is there any way to show the app and the scrollView while records are loading ?
Try to use [self performSelector:#selector(loadIt) withObject:nil]; or
[self performSelector:#selector(loadIt) withObject:nil afterDelay:0.2];
It will not block your UI until the execution of this method.
You are loading lots of records. Actually you should not load all records at at time. You should use mechanism something like tableview is using i.e.load only those record which are in visible area of scrollview. Don't load new rows until the scroll and you should reuse row or views so speedup the scrolling.
Apple's documentation for UIScrollView is very clear that the scrolled view should be tiled, with your application providing tiles as the view scrolls.
The object that manages the drawing of content displayed in a scroll view should tile the content’s subviews so that no view exceeds the size of the screen. As users scroll in the scroll view, this object should add and remove subviews as necessary.
This is necessary both for performance and memory usage: the scrollable view is backed by a CALayer, which in turn is backed by a bitmap. The same is true for each of the UIButton objects created.
Whilst it is not surprising that this takes a long time, it's more of a mystery that your app hasn't been terminated for using too much memory.
Both UITableView and UICollectionView are examples of views that tile their content. You may find you can use one of these to implement you requirements, and if not, follow the model they use.
You don't need to create 1000,000 views . You can create views dynamically and remove the previous views those are not visible at the screen space. So at the time of scrolling you can create new views and remove the views those are out of visible area of screen.
This will help you to save memory otherwise whether you are using ARC in your project if you load that much number of views in memory there will surely a chance of crash , ARC will not help you in that case.
once try this Change the code in the
-viewdidload()
{
[self loadIt];//change this to
[self performSelectorInBackground:#selector(loadIt) withObject:nil];
}

iOS UIScrollView performance

I'm trying to increase the scrolling performance of my UIScrollView. I have a lot of UIButtons on it (they could be hundreds): every button has a png image set as background.
If I try to load the entire scroll when it appears, it takes too much time. Searching on the web, I've found a way to optimize it (loading and unloading pages while scrolling), but there's a little pause in scrolling everytime I have to load a new page.
Do you have any advice to make it scroll smoothly?
Below you can find my code.
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage=(int)(offset.y / 322.0f);
if(lastContentOffset>offset.y){
pageToRemove = currentPage+3;
pageToAdd = currentPage-3;
}
else{
pageToRemove = currentPage-3;
pageToAdd = currentPage+3;
}
//remove the buttons outside the range of the visible pages
if(pageToRemove>=0 && pageToRemove<=numberOfPages && currentPage<=numberOfPages){
for (UIView *view in scrollView.subviews)
{
if ([view isKindOfClass:[UIButton class]]){
if(lastContentOffset<offset.y && view.frame.origin.y<pageToRemove*322){
[view removeFromSuperview];
}
else if(lastContentOffset>offset.y && view.frame.origin.y>pageToRemove*322){
[view removeFromSuperview];
}
}
}
}
if(((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd) || (lastContentOffset>offset.y && lastPageToAdd-1==pageToAdd)) && pageToAdd>=0 && pageToAdd<=numberOfPages){
int tmpPage=0;
if((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd)){
tmpPage=pageToAdd-1;
}
else{
tmpPage=pageToAdd;
}
//the images are inside the application folder
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for(int i=0;i<4;i++){
UIButton* addButton=[[UIButton alloc] init];
addButton.layer.cornerRadius=10.0;
if(i + (tmpPage*4)<[imagesCatalogList count]){
UIImage* image=[UIImage imageWithContentsOfFile:[NSString stringWithFormat: #"%#/%#",docDir,[imagesCatalogList objectAtIndex:i + (tmpPage*4)]]];
if(image.size.width>image.size.height){
image=[image scaleToSize:CGSizeMake(image.size.width/(image.size.height/200), 200.0)];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2,(image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else if(image.size.width<image.size.height){
image=[image scaleToSize:CGSizeMake(200.0, image.size.height/(image.size.width/200))];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2, (image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else{
image=[image scaleToSize:CGSizeMake(159.5, 159.5)];
}
[addButton setBackgroundImage:image forState:UIControlStateNormal];
image=nil;
addButton.frame=CGRectMake(width, height, 159.5, 159.5);
NSLog(#"width %i height %i", width, height);
addButton.tag=i + (tmpPage*4);
[addButton addTarget:self action:#selector(modifyImage:) forControlEvents:UIControlEventTouchUpInside];
[tmpScrollView addSubview:addButton];
addButton=nil;
photos++;
}
}
}
lastPageToAdd=pageToAdd;
lastContentOffset=offset.y;
}
Here's a few recommendations:
1) First, understand that scrollViewDidScroll: will get called continuously, as the user scrolls. Not just once per page. So, I would make sure that you have logic that ensures that the real work involved in your loading is only triggered once per page.
Typically, I will keep a class ivar like int lastPage. Then, as scrollViewDidScroll: is called, I calculate the new current page. Only if it differs from the ivar do I trigger loading. Of course, then you need to save the dynamically calculated index (currentPage in your code) in your ivar.
2) The other thing is that I try not to do all the intensive work in the scrollViewDidScroll: method. I only trigger it there.
So, for example, if you take most of the code you posted and put it in a method called loadAndReleasePages, then you could do this in the scrollViewDidScroll: method, which defers the execution until after scrollViewDidScroll: finishes:
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage = (int)(offset.y / 322.0f);
if (currentPage != lastPage) {
lastPage = currentPage;
// we've changed pages, so load and release new content ...
// defer execution to keep scrolling responsive
[self performSelector: #selector(loadAndReleasePages) withObject: nil afterDelay:0];
}
}
This is code that I've used since early iOS versions, so you can certainly replace the performSelector: call with an asynchronous GCD method call, too. The point is not to do it inside the scroll view delegate callback.
3) Finally, you might want to experiment with slightly different algorithms for calculating when the scroll view has actually scrolled far enough that you want to load and release content. You currently use:
int currentPage=(int)(offset.y / 322.0f);
which will yield integer page numbers based on the way the / operator, and the float to int cast works. That may be fine. However, you might find that you want a slightly different algorithm, to trigger the loading at a slightly different point. For example, you might want to trigger the content load as the page has scrolled exactly 50% from one page to the next. Or you might want to trigger it only when you're almost completely off the first page (maybe 90%).
I believe that one scrolling intensive app I wrote actually did require me to tune the precise moment in the page scroll when I did the heavy resource loading. So, I used a slightly different rounding function to determine when the current page has changed.
You might play around with that, too.
Edit: after looking at your code a little more, I also see that the work you're doing is loading and scaling images. This is actually also a candidate for a background thread. You can load the UIImage from the filesystem, and do your scaling, on the background thread, and use GCD to finally set the button's background image (to the loaded image) and change its frame back on the UI thread.
UIImage is safe to use in background threads since iOS 4.0.
Don't touch a line of code until you've profiled. Xcode includes excellent tools for exactly this purpose.
First, in Xcode, make sure you are building to a real device, not the simulator
In Xcode, choose Profile from the Product menu
Once Instruments opens, choose the Core Animation instrument
In your app, scroll around in the scroll view you're looking to profile
You'll see the real time FPS at the top, and in the bottom, you'll see a breakdown of all function and method calls based on total time ran. Start drilling down the highest times until you hit methods in your own code. Hit Command + E to see the panel on the right, which will show you full stack traces for each function and method call you click on.
Now all you have to do is eliminate or optimize the calls to the most "expensive" functions and methods and verify your higher FPS.
That way you don't waste time optimizing blind, and potentially making changes that have no real effect on the performance.
My answer is really a more general approach to improving scroll view and table view performance. To address some of your particular concerns, I highly recommend watching this WWDC video on advanced scroll view use: https://developer.apple.com/videos/wwdc/2011/includes/advanced-scrollview-techniques.html#advanced-scrollview-techniques
The line that is likely killing your performance is:
addButton.layer.cornerRadius=10.0;
Why? Turns out the performance for cornerRadius is AWFUL! Take it out... guaranteed huge speedup.
Edit: This answer sums up what you should do quite clearly.
https://stackoverflow.com/a/6254531/537213
My most common solution is to rasterize the Views:
_backgroundView.layer.shouldRasterize = YES;
_backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
But it works not in every situation.. Just try it

Demand loading a UIScrollView

I would like to implement an app using a UIScrollView with paging, similar to the apple weather app.
But I am a little concerned about performance. The example implementation I have been using loads all of the views then the application launches. After a certain point, once this prove slow?
I wonder how Apple's camera roll is dealing with this, where a user may have 100+ photos that can be scrolled through. Should I try to figure out a way to build the view only when it is needed? Or maybe there is a way to replicate the dequeue reusable cell technique from a UITableView, only for horizontal view loading, since each view will have the same layout.
By far the most efficient solution (and this is used in many photo-browsing apps such as Facebook, and probably the native Photos app too) is going to be to load the content on-demand, just as UITableView does. Apple's StreetScroller sample project should get you on the right track.
A very efficient solution, is to make sure to reuse any views whenever possible. If you are going to be simply displaying images, you could use a subclass of UIScrollView, and layout these reusable views within layoutSubviews. Here you could detect what views are visible and not visible and create the subviews as needed.
An example dequeuing function may look like:
- (UIImageView *)dequeueReusableTileWithFrame:(CGRect) frame andImage:(UIImage *) image
{
UIImageView *tile = [reusableTiles anyObject];
if (tile) {
[reusableTiles removeObject:tile];
tile.frame = frame;
}
else {
tile = [[UIImageView alloc] initWithFrame:frame];
}
tile.image = image;
return tile;
}
Where reusableTiles is just an iVar of NSMutableSet type. You could then use this to load fetch any currently offscreen image views and quickly and easily bring them back into view.
Your layoutSubviews may look something like:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect visibleBounds = [self bounds];
CGPoint contentArea = [self contentOffset];
//recycle all tiles that are not visible
for (GSVLineTileView *tile in [self subviews]) {
if (! CGRectIntersectsRect([tile frame], visibleBounds)) {
[reusableTiles addObject:tile];
[tile removeFromSuperview];
}
}
int col = firstVisibleColumn = floorf(CGRectGetMinX(visibleBounds)/tileSize.width);
lastVisibleColumn = floorf(CGRectGetMaxX(visibleBounds)/tileSize.width) ;
int row = firstVisibleRow = floorf(CGRectGetMinY(visibleBounds)/tileSize.height);
lastVisibleRow = floorf(CGRectGetMaxY(visibleBounds)/tileSize.height);
while(row <= lastVisibleRow)
{
col = firstVisibleColumn;
while (col <= lastVisibleColumn)
{
if(row < firstDisplayedRow || row > lastDisplayedRow || col < firstDisplayedColumn || col >lastDisplayedColumn)
{
UImageView* tile = [self dequeueReusableTileWithFrame:CGRectMake(tileSize.width*col, tileSize.height*row, tileSize.width, tileSize.height) andImage:YourImage];
[self addSubview:tile];
}
++col;
}
++row;
}
firstDisplayedColumn = firstVisibleColumn;
lastDisplayedColumn = lastVisibleColumn;
firstDisplayedRow = firstVisibleRow;
lastDisplayedRow = lastVisibleRow;
}
I used something similar to this to tile in areas of a line when I was working with an exceptionally large area of a scroll view and it seemed to work quite well. Sorry for any typos that I may have created when updating this for an image view instead of my custom tileView class.

new created UIImageViews do not show up when added as subview

upon a tap, I want to let a few UIImageView objects appear on an underlaying UIView.
So I created the following action:
- (IBAction)startStars: (id) sender
{
UIView* that = (UIView*) sender; // an UIButton, invisible, but reacting to Touch Down events
int tag = that.tag;
UIView* page = [self getViewByTag:mPages index:tag]; // getting a page currently being viewed
if ( page != nil )
{
int i;
UIImage* image = [UIImage imageNamed:#"some.png"];
for ( i = 0 ; i < 10 ; ++i )
{
// create a new UIImageView at the position of the tapped UIView
UIImageView* zap = [[UIImageView alloc] initWithFrame:that.frame];
// assigning the UIImage
zap.image = image;
// make a modification to the position so we can see all instances
CGPoint start = that.layer.frame.origin;
start.x += i*20;
zap.layer.position = start;
[page addSubview:zap]; // add to the UIView
[zap setNeedsDisplay]; // please please redraw
[zap release]; // release, since page will retain zap
}
[image release];
}
}
Unfortunately, nothing shows up. The code gets called, the objects created, the image is loaded, even the properties are as expected.
Page itself is a real basic UIView, created with interface builder to contain other views and controls.
Still, nothing of this can be seen....
Has anyone an idea what I am doing wrong? Do I need to set the alpha property (or others)?
Thanks
Zuppa
A couple of things I would check:
Logging to make sure this section of code is actually being called.
Use the designated initializer for UIImageView:
[[UIImageView alloc] initWithImage:image];
This will ensure that your new view has the correct size for the image - what are the relative sizes of zap and your image? It could be out of the frame.
Ensure that the image actually exists and is created
Try not adjusting the layer properties, but setting the center of the image view instead. In fact, in the first instance don't adjust it at all and just see if you can see anything. I'm not sure but I think position might be moving the layer within the view so could be moving your image out of sight. Try:
CGPoint newCenter = that.frame.origin;
newCenter.y += zap.bounds.size.height/2;
newCenter.x += i*20;
zap.center = origin;
setNeedsDisplay is not required. Obviously from your comments it was an act of desparation!
Is your UIImage nil?
You don't need to have the .png extension for imageNamed: to work. It might not even work correctly if you put it in, I'm not sure. You're also overreleasing your image. imageNamed: returns an autoreleased image so there is no reason to call release on the image unless you're also calling retain on it somewhere.
I see a potential error: on second invocation you will re-add another copy, so take care about removing subviews before adding anew copy. Yu can alternatively move a previous view.
99% of my cases about non-visible views with images are wrong names, so as suggested, load using a temp var:
UIImage img = [UIImage imageNamed..];
and test img.

Resources