7

我在我的应用程序中有一个UICollectionView,每个单元格都是UIImageView和一些文本标签。问题是,当我有UIImageViews显示他们的图像时,滚动性能是可怕的。它远不如UITableView的滚动体验,甚至没有UIImageView的UICollectionView。可怜的UICollectionView使用UIImage滚动性能

我发现this question从几个月前,似乎找到了答案,但它是用RubyMotion编写的,我不明白这一点。我试图看看如何将其转换为Xcode,但由于我从未使用过NSCache,所以有点难。那里的海报也指出here有关除了他们的解决方案之外的其他实现,但我不知道该把代码放在哪里。可能是因为我不明白first question的代码。

有人能帮助翻译成Xcode吗?

def viewDidLoad 
    ... 
    @images_cache = NSCache.alloc.init 
    @image_loading_queue = NSOperationQueue.alloc.init 
    @image_loading_queue.maxConcurrentOperationCount = 3 
    ... 
end 

def collectionView(collection_view, cellForItemAtIndexPath: index_path) 
    cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path) 
    image_path = @image_paths[index_path.row] 

    if cached_image = @images_cache.objectForKey(image_path) 
    cell.image = cached_image 
    else 
    @operation = NSBlockOperation.blockOperationWithBlock lambda { 
     @image = UIImage.imageWithContentsOfFile(image_path) 
     Dispatch::Queue.main.async do 
     return unless collectionView.indexPathsForVisibleItems.containsObject(index_path) 
     @images_cache.setObject(@image, forKey: image_path) 
     cell = collectionView.cellForItemAtIndexPath(index_path) 
     cell.image = @image 
     end 
    } 
    @image_loading_queue.addOperation(@operation) 
    end 
end 

下面是从second question代码的first question的提问者说解决了这个问题:

UIImage *productImage = [[UIImage alloc] initWithContentsOfFile:path]; 

CGSize imageSize = productImage.size; 
UIGraphicsBeginImageContext(imageSize); 
[productImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)]; 
productImage = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 

同样,我不知道如何/在哪里实现这一点。

非常感谢。

+0

发布您的代码!你有什么尝试?为什么你的应用程序的性能变慢?分析它看看发生了什么的结果是什么? – 2013-04-03 22:49:55

+0

这不是我的整个应用程序性能低下,它只是滚动UICollectionView中的单元格。该应用程序的其余部分表现非常好。我目前使用'cell.cardImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfFile:tempCard]];'设置图像。正在使用的图像不是应用程序包的一部分,但通常会下载并存储在Caches文件夹中。 – Nick 2013-04-04 02:11:48

回答

19

下面是我遵循的模式。始终加载异步并缓存结果。在异步加载完成时不要假定视图的状态。我有一个类,它简化了负载如下:

// 
// ImageRequest.h 

// This class keeps track of in-flight instances, creating only one NSURLConnection for 
// multiple matching requests (requests with matching URLs). It also uses NSCache to cache 
// retrieved images. Set the cache count limit with the macro in this file. 

#define kIMAGE_REQUEST_CACHE_LIMIT 100 
typedef void (^CompletionBlock) (UIImage *, NSError *); 

@interface ImageRequest : NSMutableURLRequest 

- (UIImage *)cachedResult; 
- (void)startWithCompletion:(CompletionBlock)completion; 

@end 

// 
// ImageRequest.m 

#import "ImageRequest.h" 

NSMutableDictionary *_inflight; 
NSCache *_imageCache; 

@implementation ImageRequest 

- (NSMutableDictionary *)inflight { 

    if (!_inflight) { 
     _inflight = [NSMutableDictionary dictionary]; 
    } 
    return _inflight; 
} 

- (NSCache *)imageCache { 

    if (!_imageCache) { 
     _imageCache = [[NSCache alloc] init]; 
     _imageCache.countLimit = kIMAGE_REQUEST_CACHE_LIMIT; 
    } 
    return _imageCache; 
} 

- (UIImage *)cachedResult { 

    return [self.imageCache objectForKey:self]; 
} 

- (void)startWithCompletion:(CompletionBlock)completion { 

    UIImage *image = [self cachedResult]; 
    if (image) return completion(image, nil); 

    NSMutableArray *inflightCompletionBlocks = [self.inflight objectForKey:self]; 
    if (inflightCompletionBlocks) { 
     // a matching request is in flight, keep the completion block to run when we're finished 
     [inflightCompletionBlocks addObject:completion]; 
    } else { 
     [self.inflight setObject:[NSMutableArray arrayWithObject:completion] forKey:self]; 

     [NSURLConnection sendAsynchronousRequest:self queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { 
      if (!error) { 
       // build an image, cache the result and run completion blocks for this request 
       UIImage *image = [UIImage imageWithData:data]; 
       [self.imageCache setObject:image forKey:self]; 

       id value = [self.inflight objectForKey:self]; 
       [self.inflight removeObjectForKey:self]; 

       for (CompletionBlock block in (NSMutableArray *)value) { 
        block(image, nil); 
       } 
      } else { 
       [self.inflight removeObjectForKey:self]; 
       completion(nil, error); 
      } 
     }]; 
    } 
} 

@end 

现在小区(集合或表)的更新是相当简单:

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

    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; 

    NSURL *url = [NSURL URLWithString:@"http:// some url from your model"]; 
    // note that this can be a web url or file url 

    ImageRequest *request = [[ImageRequest alloc] initWithURL:url]; 

    UIImage *image = [request cachedResult]; 
    if (image) { 
     UIImageView *imageView = (UIImageView *)[cell viewWithTag:127]; 
     imageView.image = image; 
    } else { 
     [request startWithCompletion:^(UIImage *image, NSError *error) { 
      if (image && [[collectionView indexPathsForVisibleItems] containsObject:indexPath]) { 
       [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
      } 
     }]; 
    } 
    return cell; 
} 
+0

谢谢你。我试图把它放在我的应用程序中,并且'url'总是出现'nil'。任何想法? – Nick 2013-04-04 02:19:04

+0

您是否用我用作真实网址的示例替换了那个糟糕的字符串? – danh 2013-04-04 02:32:41

+0

是的,我没有取代它。但是,我使用指向Caches目录的本地URL而不是Web地址。那应该不是问题,对吧?核心数据使用一个'NSURL'指向应用程序商店本地。 – Nick 2013-04-04 12:49:40

0

我对UICollectionView滚动的问题。

对我来说,什么工作(几乎)像一个魅力:我用png缩略图90x90填充单元格。我之所以这样说,几乎是因为第一张完整的卷轴并不那么光滑,但从未崩溃。

在我的情况下,单元尺寸是90x90。

我以前有过很多原始png大小,当png原始大小大于〜1000x1000时(第一次滚动时很多崩溃),它非常不稳定。

因此,我选择了UICollectionView上的90x90(或类似)并显示原始PNG(不管大小)。希望它可以帮助别人。

5

一般来说UICollectionViews或UITableViews出现坏的滚动行为是因为单元在iOS主线程中出队和构造的。在后台线程中预处理单元格或构建它们的自由度很小,相反,当您滚动阻止UI时,它们会被取出并构建。 (个人而言,我发现苹果公司的这个糟糕的设计虽然它确实简化了一些事情,因为你不必了解潜在的线程问题,我认为他们应该给一个钩子,以便为UICollectionViewCell/UITableViewCell池提供一个自定义实现可以处理单元的出队/重用。)

性能下降最重要的原因确实与图像数据(按递减量级)是我的经验:

  • 同步调用下载图像数据:总是这样做异步和呼叫[UIImageView setImage:]在主线程中准备就绪时使用构造图像
  • 同步调用以从本地文件系统或其他序列化数据上的数据构建图像:同样也是异步执行此操作。 (例如[UIImage imageWithContentsOfFile:],[UIImage imageWithData:]等)。
  • 调用[UIImage imageNamed:]:首次加载该图像时,它将从文件系统中提供。您可能需要预先缓存图像(只需在单元格实际构建之前加载[UIImage imageNamed:],以便可以立即从内存中为它们提供服务]
  • 调用[UIImageView setImage:]不是最快的方法,但可以除非您使用静态图像,否则通常不会避免,对于静态图像,使用不同的图像视图有时会更快,您可以使用不同的图像视图,这取决于是否应显示它们,而不是在同一图像视图中更改图像。第一次一个单元出队时,它是从Nib加载的,或者是用alloc-init构造的,并且设置了一些初始的布局或属性(如果你使用它们,可能也是图像),这会在第一次使用单元格时导致不良的滚动行为。

因为我对平滑滚动非常挑剔(即使它只是第一次使用单元格),我构建了一个整体框架来通过继承UINib来预细化单元(这基本上是您进入出列过程的唯一钩子) iOS版)。但那可能超出了你的需求。