我正在尝试提高 UIScrollView
的滚动性能.我上面有很多 UIButtons
(可能有数百个):每个按钮都有一个 png 图像设置为背景.
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;
}
这里有一些建议:
1) 首先,了解 scrollViewDidScroll:
将在用户滚动时连续调用.不只是每个页面一次.因此,我会确保您的逻辑能够确保您的加载所涉及的实际工作仅在每页触发一次.
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.
通常,我会保留一个类 ivar,例如 int lastPage
.然后,当调用 scrollViewDidScroll:
时,我计算新的 当前页面.只有当它与 ivar 不同时,我才会触发加载.当然,那你需要将动态计算的索引(currentPage
in your code)保存在你的ivar中.
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) 另一件事是我尽量不做 scrollViewDidScroll:
方法中的所有密集工作.我只在那里触发它.
2) The other thing is that I try not to do all the intensive work in the scrollViewDidScroll:
method. I only trigger it there.
因此,例如,如果您将发布的大部分代码放入名为 loadAndReleasePages
的方法中,那么您可以在 scrollViewDidScroll:
方法中执行此操作,将执行推迟到 scrollViewDidScroll:
完成后:
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];
}
}
这是我从早期 iOS 版本开始使用的代码,因此您当然也可以将 performSelector:
调用替换为异步 GCD 方法调用.关键是不要在内部滚动视图委托回调.
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) 最后,您可能希望尝试使用略有不同的算法来计算滚动视图何时实际滚动到您想要加载和释放内容的程度.您目前使用:
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);
这将根据 /
运算符和 float
到 int
转换的方式产生整数页码.那可能没问题.但是,您可能会发现您需要一个稍微不同的算法,以在稍微不同的点触发加载.例如,您可能希望在页面从一个页面滚动到下一个页面时恰好滚动 50% 时触发内容加载.或者,您可能只想在您几乎完全离开第一页(可能 90%)时才触发它.
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.
你也可以玩弄它.
再查看您的代码后,我还看到您正在做的工作是加载和缩放图像.这实际上也是一个后台线程的候选者.您可以从文件系统加载 UIImage
,并在后台线程上进行缩放,然后使用 GCD 最终设置按钮的背景图像(加载的图像)并将其框架更改回 UI线程.
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
自 iOS 4.0 起在后台线程中使用是安全的.
UIImage
is safe to use in background threads since iOS 4.0.
这篇关于iOS UIScrollView 性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!