8

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

数か月前にこの質問を見つけて、答えが見つかったようですが、RubyMotion で書かれていて、それがわかりません。Xcodeに変換する方法を試してみましたが、NSCacheも使ったことがないのでちょっと難しいです。そこのポスターは、ソリューションに加えて何かを実装することについても指摘しましたが、そのコードをどこに置くべきかわかりません。おそらく、最初の質問のコードを理解していないためです。

誰かがこれを 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

最初の質問の質問者が問題を解決したと言った2番目の質問のコードは次のとおりです。

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

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

繰り返しますが、それを実装する方法/場所がわかりません。

どうもありがとう。

4

3 に答える 3

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;
}
于 2013-04-03T23:02:53.490 に答える
8

一般に、UICollectionViews または UITableViews の不適切なスクロール動作は、セルがデキューされ、iOS によってメイン スレッドで構築されるために発生します。セルをプリキャッシュしたり、バックグラウンド スレッドでセルを構築したりする自由はほとんどありません。代わりに、UI をブロックしてスクロールすると、キューから取り出されて構築されます。(個人的には、潜在的なスレッド化の問題を認識する必要がないため、問題は単純化されますが、Apple によるこの悪い設計はすべて見つかります。UICollectionViewCell/UITableViewCell プールのカスタム実装を提供するために、フックを提供する必要があったと思います。セルのデキュー/再利用を処理できます。)

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

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

私はスムーズ スクロールに非常にうるさいので (セルを初めて使用する場合でも)、UINib をサブクラス化してセルをプリキャッシュするためのフレームワーク全体を構築しました (これは基本的に、iOS で使用されるデキュー プロセスに入る唯一のフックです)。しかし、それはあなたのニーズを超えているかもしれません。

于 2015-01-29T12:42:17.077 に答える
1

UICollectionViewスクロールに問題がありました。

私にとって (ほとんど) 魅力的だったのは、セルに 90x90 の png サムネイルを設定したことです。ほとんどの場合、最初の完全なスクロールはそれほどスムーズではありませんが、クラッシュすることはありません。

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

以前は多くの元の png サイズがありましたが、png の元のサイズが ~1000x1000 を超えると非常に不安定でした (最初のスクロールで多くのクラッシュが発生しました)。

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

于 2013-12-21T00:33:43.283 に答える