UICollectionView 滚动很慢

Posted

技术标签:

【中文标题】UICollectionView 滚动很慢【英文标题】:UICollectionView scrolling is slow 【发布时间】:2013-05-25 01:43:17 【问题描述】:

我刚刚创建了一个UICollectionView,用户可以在其中将手机中的图像添加到应用程序的相册功能中。我将图像保存到文档目录中的 a 子目录中,以便可以添加和删除更多图像。但是,当我向上和向下滚动集合视图时,它非常滞后。

我怎样才能使滚动流畅?

我的代码:前 16 张图片是预设图片,之后的所有图片都来自文档目录中的子目录

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Custom" forIndexPath:indexPath];
    //Current index number
    int index=indexPath.section * noOfSection + indexPath.row;
    //Check if its the preset photos
    if(index<16)
        NSString *name=[recipePhotos objectAtIndex:indexPath.section * noOfSection + indexPath.row];
        cell.imageView.image=[UIImage imageNamed:name];
    

//not preset photos, so retrieve the photos the user added
    else 
        NSData *data= [NSData dataWithContentsOfFile:[recipePhotos objectAtIndex:index]];
        UIImage *theImage=[UIImage imageWithData:data];

        cell.imageView.image=theImage;
        data=nil;
    

    return cell;

Time Profiler 给了我这个

Running Time    Self        Symbol Name
568.0ms   63.1% 0.0     Main Thread  0x4048
320.0ms   35.5% 0.0     _pthread_start  0x405e
320.0ms   35.5% 0.0      thread_start
320.0ms   35.5% 0.0       _pthread_start
320.0ms   35.5% 0.0        0x1084be960
310.0ms   34.4% 1.0         0x1084be6f0
7.0ms    0.7%   0.0         mach_msg
2.0ms    0.2%   2.0         objc_msgSend
1.0ms    0.1%   1.0         -[NSAutoreleasePool release]
4.0ms    0.4%   0.0     _dispatch_mgr_thread  0x4052
4.0ms    0.4%   0.0      _dispatch_mgr_thread
4.0ms    0.4%   0.0       _dispatch_mgr_invoke
4.0ms    0.4%   4.0        kevent
3.0ms    0.3%   0.0     _dispatch_worker_thread2  0x62b24
3.0ms    0.3%   1.0      start_wqthread
3.0ms    0.3%   0.0     _dispatch_worker_thread2  0x62a84
3.0ms    0.3%   0.0      start_wqthread
3.0ms    0.3%   0.0       _pthread_wqthread
3.0ms    0.3%   0.0        _dispatch_worker_thread2
3.0ms    0.3%   0.0         _dispatch_queue_invoke
3.0ms    0.3%   0.0          _dispatch_queue_drain
3.0ms    0.3%   0.0           _dispatch_client_callout
2.0ms    0.2%   0.0            my_io_execute_passive_block
1.0ms    0.1%   0.0             __86-[NSPersistentUIManager writePublicPlistWithOpenWindowIDs:optionallyWaitingUntilDone:]_block_invoke_0835
1.0ms    0.1%   0.0              -[NSPersistentUIManager writePublicPlistData:]
1.0ms    0.1%   0.0               -[NSURL(NSURLPathUtilities) URLByAppendingPathComponent:]
1.0ms    0.1%   0.0                -[NSURL getResourceValue:forKey:error:]
1.0ms    0.1%   0.0                 CFURLCopyResourcePropertyForKey
1.0ms    0.1%   0.0             __block_global_2
1.0ms    0.1%   0.0              -[NSPersistentUIManager writeRecords:withWindowInfos:flushingStaleData:]
1.0ms    0.1%   0.0            _dispatch_call_block_and_release
1.0ms    0.1%   0.0             0x1084b8580
1.0ms    0.1%   0.0              mach_msg_send
1.0ms    0.1%   0.0               mach_msg
1.0ms    0.1%   1.0                mach_msg_trap
1.0ms    0.1%   0.0     _pthread_struct_init  0x62a83
1.0ms    0.1%   0.0      start_wqthread
1.0ms    0.1%   0.0       _pthread_wqthread
1.0ms    0.1%   1.0        _pthread_struct_init
1.0ms    0.1%   0.0     start_wqthread  0x62a7f

【问题讨论】:

视图中有多少张图片?根据我的经验,过多的图像往往会降低帧速率。 它只发生在从目录加载图像的单元格中。查看我上面的代码。我刚刚更新了它@CodeBandits 你在CollectionCell做任何自定义绘图吗? 仪器中的时间分析器告诉你什么? 刚刚编辑@jrturton 【参考方案1】:

您需要制定一种方法,就像您在表格视图中需要做的那样,您需要重用视图,就像您在表格视图中重用您的单元格一样。

Ray Wenderlich 提供的一个非常好的教程:

在第一部分你有基本的,在第二部分他们谈论可重用的视图,你看一下链接:

http://www.raywenderlich.com/22417/beginning-uicollectionview-in-ios-6-part-22

编辑

异步加载图像的示例:

例如,在您的单元格中创建一个方法loadImageFromFile,它接收您将以这种方式调用它的路径:

[cell loadImageFromFile:[recipePhotos objectAtIndex:index]];

然后看起来像(也许你需要适应一些东西......):

- (void) loadImageFromFile:(NSString*)path
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^
        NSData *data= [NSData dataWithContentsOfFile:path];
        UIImage *theImage=[UIImage imageWithData:data];

        dispatch_async(dispatch_get_main_queue(), ^
            cell.imageView.image=theImage;
    );
);

【讨论】:

您需要在后台加载图片,加载后您需要更新单元格中的图片,否则您的滚动将等待图片返回继续缓慢。 好的,在后台处理它的最佳方法是什么。我必须与主线程分开吗?如果是这样@ggrana 我对答案进行了编辑,为您提供了一种可能的方法 这稍微改进了一点,但它仍然非常滞后@ggrana 好吧,现在你需要有创意,也许你的图片太大了,也许你的代码的其他部分造成了延迟,也许你需要使用一个库来加载这个图片,如果你给我更多的代码也许我可以帮助你更多【参考方案2】:

@ggrana 有正确的想法。加载异步肯定会有所帮助。但是,如果您每次都从文件加载,您仍然在做多余的工作。要考虑的一件事是使用NSCache 增加异步加载。它基本上是一个NSDictionary,但它自己进行内存管理并在内存压力时转储数据。

因此,如果您有内存预算,您实际上可以即时制作缩略图(因此您不必硬编码大小)并将它们存储在缓存中。这样,您的图像只会在第一次弹出。每次之后,它们都会立即加载。

你可以这样使用它:

@implementation ...

    NSCache * _johnny;  // get it?


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    [cell setImage:nil]; // since they are reused, prevents showing an old image
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
         UIImage * sticker = [self thumbnailOfSize:CGSizeMake(desiredImageWidth, desiredImageHeight) 
                                          forIndex:[indexPath row]];
         dispatch_async(dispatch_get_main_queue(), ^
             [cell setImage:sticker];
         );
    );


// load image from disk and cache thumbnail version
- (UIImage*) thumbnailOfSize:(CGSize)size forIndex:(NSInteger)index

    NSString * cacheKey = [NSString stringWithFormat:@"%@ %d", NSStringFromCGSize(size), index];
    UIImage * image = [_johnny objectForKey:cacheKey];

    if (!image) 
        image = [UIImage imageWithContentsOfFile:_imagePaths[index]];

        float desiredWidth = size.width;
        float desiredHeight = size.height;
        float actualHeight = image.size.height;
        float actualWidth = image.size.width;
        float imgRatio = actualWidth / actualHeight;
        float maxRatio = desiredWidth / desiredHeight;

        if(imgRatio != maxRatio) 
            if(imgRatio < maxRatio) 
                imgRatio = desiredHeight / actualHeight;
                actualWidth = imgRatio * actualWidth;
                actualHeight = desiredHeight;
             else 
                imgRatio = desiredWidth / actualWidth;
                actualHeight = imgRatio * actualHeight;
                actualWidth = desiredWidth;
            
        

        CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
        UIGraphicsBeginImageContextWithOptions(rect.size, FALSE, 0); // do right thing for retina
        [image drawInRect:rect];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        // here's johnny
        [_johnny setObject:image forKey:cacheKey];
    

    return image;

【讨论】:

这种方法效果很好。几点:1)你错过了cellForItem...中的单元格创建/出队行2)如果用户很快滚动到长集合视图的底部,底部的单元格可能会出现一系列循环图像作为一堆异步调用该重用单元最终更新图像。最后一分钟检查 `indexPathsForVisibleCells' 解决了这个问题(基本上“这个索引路径是否仍然可见?如果没有,请不要更新图像)。【参考方案3】:

所以经过一番折腾,我发现问题出在几个因素上。

One- 缩略图的图像太大,所以我做了一个单独的图像数组,图像尺寸较小,适合单元格。 二- 在@ggrana 的帮助下,打开一个单独的线程加快了进程并减少了延迟。 三 - 我还发现拥有一组图像而不是图像位置更快 - 唯一的问题是它占用更多内存。

【讨论】:

如果您没有很多图像,我的意思是如果普通用户滚动浏览您的收藏视图,他们最终将使用相同的内存。所以如果图像数组更快,那就走吧。 你需要这个***.com/a/26034382/294884和这个***.com/a/25604378/294884【参考方案4】:
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];

通常问题是下载速度慢,因为每个单元格都在出队。

    在视图出现之前调用 downloadForPerformance 函数。 这将填充一个名为 imageArray 的数组作为全局变量 然后在 cellForItem 函数中使用该数组

【讨论】:

以上是关于UICollectionView 滚动很慢的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionView

UICollectionView 单元格在可见时不断重新加载

大 UICollectionViewCell 在滚动时消失

通过捏合手势缩放 UICollectionView

UICollectionView 未注册单元格

UiCollectionView 间距问题 - 快速