web-dev-qa-db-ja.com

UITableViewのスクロールがスムーズでない

UIImageViewを含むUITableViewCellを使用して、UITableViewでスムーズスクロールの問題があります。同様の問題がStrackOverflowのいたるところに見られましたが、提案された解決策はどれも、ラグを完全に取り除くのに役立ちませんでした。

私のケースはかなり一般的です:

  1. 画像はアプリケーションストレージに保存されます(私のサンプルではApp Bundle)
  2. 画像のサイズが異なる可能性があります(500x500、1000x1000、1500x1500)
  3. UIImageViewのサイズが120x120(網膜)であるUITableViewでそれらの画像を表示する必要があります

私は複数の最適化のヒントに従い、スクロールを最適化することに成功しました。残念ながら、それはまだ完璧ではありません。これは私のシナリオです:

  1. まず、すべての画像の読み込み/処理/サイズ変更ロジックをバックグラウンドスレッドに移動しました
  2. UITableViewCellの再利用が有効になっています
  3. uITableViewCellが表示されたら、古い値(設定をnullに)をクリアし、バックグラウンドスレッドを開始して画像を読み込みます
  4. この時点ではバックグラウンドスレッドにあり、新しい画像を頻繁に設定しないように500ミリ秒の遅延を追加しています(高速でスクロールしている場合)(以下の説明を参照)。
  5. uIImageが静的画像キャッシュ(UIImageインスタンスを持つ通常の辞書)に存在する場合-その画像をフェッチして、ステップ9に進みます。
  6. そうでない場合-URLを使用してバンドル(imageWithName)から新しいイメージをアプリバンドルにロードします(実際のシナリオでは、イメージはバンドルではなくアプリケーションストレージに保存されます)
  7. 画像が読み込まれたら、グラフィックスコンテキストを使用して120x120にサイズ変更します
  8. サイズを変更した画像を静的画像キャッシュに保存する
  9. この時点で、UIImageのインスタンスがあり、プロセスはバックグラウンドスレッドにあります。ここから、指定された画像でUIスレッドに戻ります
  10. データコンテキストがクリアされた場合(たとえば、UITableViewCellが消えたか、別の画像を表示するために再利用された場合)、現在利用可能な画像の処理をスキップします。
  11. データコンテキストが同じ場合-アルファアニメーションを使用してUIImageをUIImageViewに割り当てます(UIView.Animate)
  12. uITableViewCellが非表示になると-データコンテキストをクリアします

もともとここで画像を取得するために新しいバックグラウンドスレッドを開始する前(ステップ1)は、バックグラウンドスレッドなしのUIImageキャッシュチェックでした。この場合、キャッシュ内に画像がある場合、その画像を即座に割り当てます。これにより、高速スクロール中に大きな遅延が生じます(画像を即座にフェッチする限り、頻繁に画像を割り当てます)。これらの行は、下記の私の例でコメントされています。

まだ2つの問題があります:

  1. スクロール中のある時点で、まだ少し遅れがあります(UIImageViewに新しいUIImageを割り当てるとき)。
  2. (これはもっと目立ちます)アイテムをタップして詳細から戻ると、戻るナビゲーションアニメーションが終了する直前に遅れがあります。

これら2つの問題に対処する方法、または私のシナリオを最適化する方法は高く評価されています

Xamarinで記述されたサンプルを考慮に入れてください。ただし、ObjectiveCで記述されたアプリでも同じ問題がある限り、Xamarinが問題の原因であるとは思いません。

スムーズスクロールテストアプリ

18
Alexey Strakh

TableViewに保存されている120x120の画像を1つだけBundleに入力しようとしましたか?この方法で、問題が発生したかどうかを確認できますImage rendering

すべての画像を120x120にサイズ変更してキャッシュに保存する代わりに、すべての画像のサムネイルを作成して使用することをお勧めします。あなたは何らかの形でこれをすでに行っていますが、これを数回行っています(スクロールするたびに、またはキャッシュがいっぱいの場合)。

前回のプロジェクトでは、本の表紙付きのUICollectionViewを用意しました。ほとんどのカバーは400-800kbの大きさで、スクロール中の感じは本当に悪かった。そこで、各画像のサムネイル(サムネイルは約40〜50kb)を作成し、実際のカバーの代わりにサムネイルを使用しました。魅力的な作品!サムネイル作成機能をつけました

- (BOOL) createThumbnailForImageAtFilePath:(NSString *)sourcePath withName:(NSString *)name {

    UIImage* sourceImage = [UIImage imageWithContentsOfFile:sourcePath];
    if (!sourceImage) {
        //...
        return NO;
    }

    CGSize thumbnailSize = CGSizeMake(128,198);

    float imgAspectRatio = sourceImage.size.height / sourceImage.size.width;
    float thumbnailAspectRatio = thumbnailSize.height/thumbnailSize.width;

    CGSize scaledSize = thumbnailSize;

    if(imgAspectRatio >= thumbnailAspectRatio){
         //image is higher than thumbnail
         scaledSize.width = scaledSize.height * thumbnailSize.width / thumbnailSize.height;
    }
    else{
        //image is broader than thumbnail
        scaledSize.height = scaledSize.width * imgAspectRatio;
    }

    UIGraphicsBeginImageContextWithOptions( scaledSize, NO, 0.0 );
    CGRect scaledImageRect = CGRectMake( 0.0, 0.0, scaledSize.width, scaledSize.height );
    [sourceImage drawInRect:scaledImageRect];
    UIImage* destImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    NSString* thumbnailFilePath = [[self SOMEDIRECTORY] stringByAppendingPathComponent:name];

   BOOL success = [UIImageJPEGRepresentation(destImage, 0.9) writeToFile:thumbnailFilePath atomically:NO];

return success;

}

11
longi

Facebookの非同期表示ライブラリを試してください。

https://github.com/facebook/AsyncDisplayKit

本当に使いやすい..彼らのガイドから: http://asyncdisplaykit.org/guide/

_imageNode = [[ASImageNode alloc] init];
_imageNode.backgroundColor = [UIColor lightGrayColor];
_imageNode.image = [UIImage imageNamed:@"hello"];
_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageNode.view];

これは、バックグラウンドスレッドでイメージをデコードします。

XamarinでiOSライブラリを簡単に使用できるかどうかはわかりませんが、簡単な場合は試してみてください。

3
Krys Jurgowski

Paul HegartyのCoreDataTableViewControllerをサブクラス化し、CoreDataTableViewで写真のサムネイルを使用します。

講義14の FlickrFetcher and Photomania というタイトルの例を探してください。また、同じリンクでCoreDataTableViewControllerをダウンロードする必要があります。

適切なタイトルでCoreDataエンティティを作成し、必要な属性(データ変数)を定義します。写真用とサムネイル用の2つの「変形可能」属性を定義する必要があります。

次に、サムネイルをCoreDataTableViewにロードします。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{NSArray * exceptions = [NSArray arrayWithObjects:@ "SCR"、@ "DNS"、@ "NT"、@ "ND"、@ "NH"、nil];

static NSString *CellIdentifier = @"resultsDisplayCell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

MarksFromMeets *athleteMarks = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* date = [ITrackHelperMethods dateToAbbreviatedString:athleteMarks.meetDate];
NSMutableString *title = [NSMutableString stringWithFormat:@"%@", athleteMarks.markInEvent];
NSMutableString *subTitle = [NSMutableString stringWithFormat:@"%@ - %@",date, athleteMarks.meetName];
[title replaceOccurrencesOfString:@"(null)"
                       withString:@""
                          options:0
                            range:NSMakeRange(0, [title length])];

// cell.imageView.image = athleteMarks.photoThumbNail; // Don't like image in front of record.

[cell.textLabel setFont:[UIFont
                         fontWithName:@"Helvetica Neue" size:18]];

[cell.detailTextLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:16]];
[cell.detailTextLabel setTextColor:[UIColor grayColor]];

// make selected items orange
if ([athleteMarks.eventPR integerValue] != 0
    && (![exceptions containsObject:athleteMarks.markInEvent])) {

    title = [NSMutableString stringWithFormat:@"%@      (PR)",title];
    [cell.textLabel setTextColor:[UIColor redColor]];
}
else if ([athleteMarks.eventSB integerValue] != 0
         && (![exceptions containsObject:athleteMarks.markInEvent])) {
    title = [NSMutableString stringWithFormat:@"%@      (SB)",title];

    [cell.textLabel setTextColor:[UIColor orangeColor]];
} else {
    [cell.textLabel setTextColor:[UIColor grayColor]];
}

cell.textLabel.text = title;
cell.detailTextLabel.text = subTitle;

cell.indentationLevel = indentationLevelOne;
cell.indentationWidth = indentationForCell;

return cell;

}

必要に応じて、エンティティのNSManagedObjectサブクラスのカテゴリの例をお送りします。このカテゴリは、写真とサムネイルをCoreDataエンティティにロードします。初めては遅くなります。ただし、その後、ユーザーはTableViewをスムーズにスクロールできるようになり、すべての更新された結果が自動的に読み込まれます。お知らせ下さい。

素晴らしい点の1つは、CoreDataがすべてのメモリ管理を処理することです。

幸運を!

2
PhillipOReilly

コメントするのに十分な担当者がいないので、テーブルビューのスクロールパフォーマンスに役立つ回答を次に示します。

  • テーブルビューの高さを表示可能なウィンドウよりも大きくします。セルは「オフスクリーン」で読み込まれ、スクロールの滑らかさを向上させます。
  • 次の方法で画像処理を実行します:-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

これらの2つのトリックによって、私のテーブルは本当に素晴らしいものになりました。 APIサービスから画像データを取得しています。AFNETWORKINGには素晴らしい画像ローダーがありますが、画像はバンドルに含まれているので必要ありません。

0
roninSTI

似たような問題がありましたが、スクロールがスムーズではありませんでした。内部にlabelViewsを持つ変数UIImageViewをテーブルに挿入しています。私がしたことは、estimatedHeightforRowAtIndexPathのメソッドHeightforRowAtIndexPathを変更し、スクロールがスムーズになったことです。

0
mapkiller

代わりにSDWebImageを試すこともできます。これはxamarin component でもあり、非同期イメージダウンローダー、非同期メモリ、および自動キャッシュ有効期限処理を備えたディスクイメージキャッシュを作成します。それを使用することはおそらく、多くのハードに書かれたコードを捨てることを意味します、しかしそれはそれの価値があるかもしれません-そしてあなたのコードはずっと単純になるでしょう。 iOSでは、コントローラーのviewDidLoad内にSDWebImageManagerを設定することもできます。

- (void)viewDidLoad{
...
SDWebImageManager *manager = [SDWebImageManager sharedManager];
manager.delegate = self;
...
}

ビューコントローラーをデリゲートとして設定します。次に、次のデリゲートメソッドが呼び出されたとき:

- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL

キャッシュする前に、画像を適切なサイズのつまみに拡大縮小できます。

お役に立てば幸いです。

0
Don Miguel