iOS UIScrollView性能
我正在尝试提高我的UIScrollView
的滚动性能。 我有很多UIButtons
(它们可能是数百个):每个按钮都有一个png图像设置为背景。
如果我尝试在出现时加载整个滚动,则需要花费太多时间。 在网上搜索,我找到了一种优化它的方法(滚动时加载和卸载页面),但每次我必须加载新页面时滚动有一点暂停。
你有什么建议让它顺利滚动吗?
您可以在下面找到我的代码。
- (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.yoffset.y && view.frame.origin.y>pageToRemove*322){ [view removeFromSuperview]; } } } } if(((lastContentOffsetoffset.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)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; }
以下是一些建议:
1)首先,了解scrollViewDidScroll:
将在用户滚动时连续调用。 每页不只是一次。 因此,我会确保您拥有逻辑,确保每个页面只触发一次加载所涉及的实际工作。
通常,我会像int lastPage
一样保留一个类ivar 。 然后,当scrollViewDidScroll:
,我计算新的当前页面 。 只有当它与ivar不同时才会触发加载。 当然,您需要在ivar中保存动态计算的索引(代码中的currentPage
)。
2)另一件事是我尽量不在scrollViewDidScroll:
方法中完成所有密集工作。 我只在那里触发它。
因此,例如,如果您将大部分代码放在一个名为loadAndReleasePages
的方法中,那么您可以在scrollViewDidScroll:
方法中执行此操作,该方法将执行推迟到scrollViewDidScroll:
完成之后:
- (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]; } }
这是我从iOS早期版本开始使用的代码,所以你当然可以用异步GCD方法调用替换performSelector:
call。 关键是不要在滚动视图委托回调中进行。
3)最后,您可能想尝试稍微不同的算法来计算滚动视图实际滚动到何时加载和释放内容的时间。 您目前使用:
int currentPage=(int)(offset.y / 322.0f);
这将产生基于/
运算符的方式的整数页码,以及float
到int
cast的工作方式。 那可能没事。 但是,您可能会发现需要稍微不同的算法,以稍微不同的点触发加载。 例如,您可能希望触发内容加载,因为页面已从一个页面滚动到下一个页面的50%。 或者你可能只想在你几乎完全脱离第一页(可能是90%)时触发它。
我相信我编写的一个滚动密集型应用程序确实需要我在进行大量资源加载时调整页面滚动中的精确时刻。 所以,我使用了稍微不同的舍入函数来确定当前页面何时发生了变化。
你也可以玩弄它。
编辑:在看了一下你的代码之后,我也看到你正在做的工作是加载和缩放图像。 这实际上也是后台线程的候选者。 您可以从文件系统加载UIImage
,并在后台线程上进行缩放,并使用GCD最终设置按钮的背景图像(到加载的图像)并在UI线程上更改其框架。
UIImage
iOS 4.0开始, UIImage
在后台线程中使用是安全的。
在您进行分析之前,请勿触摸一行代码。 Xcode包含用于此目的的出色工具。
-
首先,在Xcode中,确保构建到真实设备,而不是模拟器
-
在Xcode中,从“产品”菜单中选择“配置文件”
-
仪器打开后,选择Core Animation仪器
-
在您的应用中,在滚动视图中滚动您要查看的个人资料
您将在顶部看到实时FPS,在底部,您将看到基于总运行时间的所有函数和方法调用的细分。 开始向下钻取最高时间,直到您在自己的代码中点击方法。 按Command + E查看右侧的面板,它将显示您单击的每个函数和方法调用的完整堆栈跟踪。
现在,您所要做的就是消除或优化对“最昂贵”function和方法的调用,并validation您的更高FPS。
这样你就不会浪费时间来优化盲目,并且可能会做出对性能没有实际影响的变化。
我的答案实际上是一种改进滚动视图和表视图性能的更通用的方法。 为了解决您的一些特殊问题,我强烈建议您使用高级滚动视图观看此WWDCvideo: https : //developer.apple.com/videos/wwdc/2011/includes/advanced-scrollview-techniques.html#advanced-scrollview -techniques
可能会破坏您的表现的那条线是:
addButton.layer.cornerRadius = 10.0;
为什么? 原来,cornerRadius的表现非常棒! 拿出来…保证巨大的加速。
编辑:这个答案总结了你应该做的很清楚。
我最常见的解决方案是栅格化视图:
_backgroundView.layer.shouldRasterize = YES; _backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
但它不适用于所有情况..试试吧