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版)。但那可能超出了你的需求。