UICollectionView 滚动缓慢

8
我刚刚在应用程序中创建了一个UICollectionView,通过这个功能用户可以将手机中的图片添加到相册中。我将这些图片保存在文档目录的子目录中,所以用户可以随时添加或删除图片。但是,当我在集合视图中上下滚动时,会有很大的延迟。
如何使页面滑动更加流畅?
我的代码:前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;
}

时间分析器给了我以下内容

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

1
视图中有多少张图片?根据我的经验,过多的图片可能会降低帧率。 - LAMBORGHINI
1
这只会发生在从目录加载图像的单元格中...查看上面的代码...我刚刚更新了它@CodeBandits - Phil Scarf
你在CollectionCell中进行过任何自定义绘制吗? - rog
仪器中的时间分析器告诉你什么? - jrturton
刚刚进行了一次编辑 @jrturton - Phil Scarf
@rog 我创建了一个自定义单元格,其中包含背景图像和数组索引对应的给定图片。我在viewdidload中使用以下代码://注册nib UINib *nib = [UINib nibWithNibName:@"CollectionCell" bundle:nil]; [_collectionView registerNib:nib forCellWithReuseIdentifier:@"Custom"]; - Phil Scarf
4个回答

4
你需要像在表格视图中一样制定一种方法,需要重复使用视图,就像在表格视图中重复使用单元格一样。
一个非常好的教程是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 - Phil Scarf
我对给你的答案进行了编辑,提供了一种可能的方法。 - ggrana
这使它稍微改善了一些,但仍然非常卡顿 @ggrana - Phil Scarf
现在你需要有创意,也许你的图像太大了,也许代码的其他部分导致了延迟,也许你需要使用一个库来加载这个图像。如果你提供更多的代码,我可能可以提供更多的帮助。 - ggrana

4
@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 的最后一分钟检查解决了这个问题(基本上是“这个索引路径仍然可见吗?如果不是,则不要更新图像”。)。 - Ben Packard

3

经过一些尝试,我发现问题是基于几个因素引起的。

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


如果您没有很多图片,也就是说,如果普通用户在浏览您的集合视图时滚动到底部,最终会使用相同的内存。因此,如果图像数组更快,请选择这种方式。 - Burak SARICA
你需要这个 https://dev59.com/tYPba4cB1Zd3GeqPv7av#26034382 和这个 http://stackoverflow.com/a/25604378/294884。 - Fattie

1
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];

通常问题是下载速度慢,因为每个单元格都要出队。
  1. 在视图出现之前调用downloadForPerformance函数。
  2. 这将填充名为imageArray的全局变量数组。
  3. 然后在cellForItem函数中使用该数组。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接