7

私のアプリケーションにはUICollectionViewがあり、各セルはUIImageViewといくつかのテキストラベルです。問題は、UIImageViewsで画像を表示しているときに、スクロールのパフォーマンスがひどいことです。 UITableViewのスクロールエクスペリエンスやUIImageViewのない同じUICollectionViewのスムーズさにはほど遠いところです。UICollectionViewのスクロールパフォーマンスがUIImageで

私は数ヶ月前からthis questionを見つけました。答えが見つかったようですが、それはRubyMotionで書かれていて、私はそれを理解できません。私はそれをXcodeに変換する方法を見てみましたが、NSCacheを一度も使用していないので、少し難しいです。そこのポスターにも、ソリューションに加えて何かを実装することについてhereが指摘されていましたが、そのコードをどこに入れるかはわかりません。おそらく私はfirst questionのコードを理解していないためです。

誰かがこれをXcodeに翻訳するのに役立つだろうか?私はそれを実現するためにどこか/わからないんだけど、ここでも

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

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

:ここ

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 

first questionのアスカーは、問題を解決し前記 second questionからコードです。

多くのありがとうございます。

+0

コードを投稿してください。何を試しましたか?あなたのアプリのパフォーマンスが遅いのはなぜですか?何が起こっているかを見るためにそれをプロファイリングした結果は何ですか? –

+0

パフォーマンスが低下しているのは私のアプリ全体ではなく、UICollectionView内のセルをスクロールするだけです。アプリケーションの残りの部分はとてもうまく動作します。現在、 'cell.cardImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfFile:tempCard]];'を使って画像を設定しています。使用されている画像はアプリバンドルの一部ではありませんが、通常はダウンロードしてCachesフォルダに保存します。 – Nick

答えて

19

次のパターンは次のとおりです。常にasynchをロードし、結果をキャッシュします。非同期ロードが終了したときのビューの状態について前提はしません。

// 
// 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

+0

実際のウェブアドレスで例として使用したそのひどい文字列を置き換えましたか? – danh

+0

はい、私はそれを置き換えました。しかし、Webアドレスではなくキャッシュディレクトリを指すローカルURLを使用しています。しかし、それは問題ではないはずですよね?コアデータは、NSURLを使用してアプリのストアをローカルにポイントします。 – Nick

0

私はUICollectionViewスクロールに関する問題を持っていた私は、次のように負荷を簡素化するクラスを持っています。

私にとって魅力的な(ほとんど)魅力的なもの:私は、セルに90x90のpngサムネイルを挿入しました。私は、最初の完全なスクロールがそれほど滑らかではないが、もう決してクラッシュしなかったので、ほとんど言う。

私の場合、セルのサイズは90x90です。

以前のPNGサイズは多く、元のサイズが1000x1000を超えると非常に荒れていました(最初のスクロールでは多くのクラッシュ)。

したがって、UICollectionViewで90x90(または同等のもの)を選択し、元のpng(サイズに関係なく)を表示します。それが他人を助けるかもしれないと願っ

5

一般に、iOSによってメインスレッド内でセルがデキューされて構築されるため、UICollectionViewsまたはUITableViewsのスクロール操作が正しく行われません。セルを事前処理するか、バックグラウンドスレッドで構築するのはほとんど自由です。代わりに、UIをブロックするときにデキューされ、構築されます。 UICollectionViewCell/UITableViewCellプールのカスタム実装を提供するには、フックを与えておくべきだったと思います。(これは個人的には、Appleによってこの悪いデザインが見つかっていますが、問題を単純化しています。セルのデキュー/再利用を処理できます。)

パフォーマンスの低下のための最も重要な原因は、実際に画像データに関連し、()大きさの順に減少しているが、私の経験では、次のとおりです。

  • 同期は、画像データをダウンロードするために呼び出します。常に非同期的にこれを行うと、コール[UIImageView setImage:]メインスレッドの準備が整ったら、構築されたイメージと一緒に
  • ローカルファイルシステム上のデータまたは他のシリアル化されたデータからイメージを構築するための同期呼び出し。 (例:[UIImage imageWithContentsOfFile:]、[UIImage imageWithData:]など)。
  • [UIImage imageNamed:]を呼び出す:この画像が初めて読み込まれると、ファイルシステムから提供されます。実際には、セルが実際にメモリから提供される前に、[UIImage imageNamed:]をロードするだけで、イメージをプリキャッシュすることができます。
  • [UIImageView setImage:]を呼び出す方法は最速ではありませんが、静的画像を使用しない限り避けることはできません静的画像の場合、同じ画像ビューで画像を変更するのではなく、表示する必要があるかどうかによって、異なる画像表示を使用する方が時間がかかります。
  • 最初にセルがデキューされるのは、Nibからロードされるか、alloc-initで構築され、いくつかの初期レイアウトやプロパティが設定されます(使用した場合はおそらくイメージです)。

私はスムーズなスクロール(セルが初めて使用されるだけであっても)を非常にうまく扱っているため、UINibをサブクラス化することでセルをあらかじめキャッシュするフレームワークを構築しました(これは基本的に、 iOS)。しかしそれはあなたのニーズを超えているかもしれません。

関連する問題