3

我正在尝试提高我的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.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;
}
4

4 回答 4

4

这里有一些建议:

1)首先,了解scrollViewDidScroll:随着用户滚动,它将被连续调用。不只是每页一次。所以,我会确保你有逻辑来确保你的加载所涉及的实际工作每页只触发一次。

通常,我会保留一个类ivar,例如int lastPage. 然后,正如scrollViewDidScroll:所谓的,我计算新的当前页面。只有当它与 ivar 不同时,我才会触发加载。当然,那么您需要将动态计算的索引(currentPage在您的代码中)保存在您的 ivar 中。

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 版本开始使用的代码,因此您当然也可以将performSelector:调用替换为异步 GCD 方法调用。关键是不要滚动视图委托回调中执行此操作。

3)最后,您可能希望尝试使用稍微不同的算法来计算滚动视图何时实际滚动到您想要加载和释放内容的程度。您目前使用:

int currentPage=(int)(offset.y / 322.0f);

这将根据操作员的方式产生整数页码/,以及floatto intcast 的工作方式。那可能没问题。但是,您可能会发现您需要一个稍微不同的算法,以在稍微不同的点触发加载。例如,您可能希望在页面从一个页面滚动到下一个页面时恰好滚动 50% 时触发内容加载。或者您可能只想在您几乎完全离开第一页(可能是 90%)时才触发它。

我相信我编写的一个滚动密集型应用程序确实需要我在加载大量资源时调整页面滚动的精确时刻。因此,我使用了一个稍微不同的舍入函数来确定当前页面何时发生了变化。

你也可以玩弄它。

编辑:在查看您的代码多一点之后,我还看到您正在做的工作是加载和缩放图像。这实际上也是一个后台线程的候选者。您可以UIImage从文件系统加载,并在后台线程上进行缩放,然后使用 GCD 最终设置按钮的背景图像(加载的图像)并在 UI 线程上将其框架更改回。

UIImage自 iOS 4.0 起,在后台线程中使用是安全的。

于 2012-12-12T22:18:49.720 回答
3

在您进行概要分析之前,请勿触摸一行代码。Xcode 包含用于此目的的优秀工具。

  1. 首先,在 Xcode 中,确保您构建的是真实设备,而不是模拟器

  2. 在 Xcode 中,从 Product 菜单中选择 Profile

  3. 仪器打开后,选择核心动画仪器

  4. 在您的应用中,在您要配置的滚动视图中滚动

您将在顶部看到实时 FPS,在底部,您将看到基于总运行时间的所有函数和方法调用的细分。开始向下钻取最高次数,直到您在自己的代码中找到方法。点击 Command + E 以查看右侧的面板,它将显示您单击的每个函数和方法调用的完整堆栈跟踪。

现在您所要做的就是消除或优化对最“昂贵”的函数和方法的调用,并验证您的更高 FPS。

这样一来,您就不会浪费时间盲目优化,也不会进行对性能没有实际影响的更改。


我的回答确实是提高滚动视图和表格视图性能的更通用的方法。为了解决您的一些特殊问题,我强烈建议您观看有关高级滚动视图使用的 WWDC 视频:https ://developer.apple.com/videos/wwdc/2011/includes/advanced-scrollview-techniques.html#advanced-scrollview -技术

于 2012-12-12T22:43:59.117 回答
1

可能会影响您的表现的行是:

addButton.layer.cornerRadius=10.0;

为什么?原来cornerRadius 的表现太糟糕了!把它拿出来......保证巨大的加速。

编辑:这个答案很清楚地总结了你应该做什么。

https://stackoverflow.com/a/6254531/537213

于 2012-12-12T22:51:27.853 回答
1

我最常见的解决方案是栅格化视图:

_backgroundView.layer.shouldRasterize = YES;
_backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];

但它并非在所有情况下都有效.. 试试吧

于 2013-09-25T06:01:41.517 回答