web-dev-qa-db-ja.com

UICollectionViewのパフォーマンス-_updateVisibleCellsNow

日/週/月ごとに整理されたセルを表示するカスタムUICollectionViewLayoutに取り組んでいます。

スムーズにスクロールしていません。各レンダリングループで[UICollectionView _updateVisibleCellsNow]が呼び出されているため、遅延が発生しているようです。

30個未満のアイテムでもパフォーマンスは問題ありませんが、約100個以上では非常に遅くなります。これはUICollectionViewとカスタムレイアウトの制限ですか、それとも正しく実行するのに十分な情報をビューに与えていませんか?

ここのソース: https://github.com/oskarhagberg/calendarcollection

レイアウト: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m

データソースとデリゲート: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m

時間プロファイル: Time profile - Custom layout

更新

たぶんその無駄は?ほぼ同じ量のセル/画面が与えられたUICollectionViewControllerを使用したプレーンUICollectionViewFlowLayoutでのテストの結果は、同様の時間プロファイルになります。

Time profile - Standard flow layout

私は、ジッタなしで一度に〜100個の単純な不透明なセルを処理できる必要があると感じています。私が間違っている?

52
Oskar

また、セルのレイヤーをラスタライズすることを忘れないでください:

cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

私はそれなしで10 fpsで、55 fpsでブームになりました!私はGPUと合成モデルにあまり精通していないので、それが正確に何をするかは完全に明確ではありませんが、基本的には1つのビットマップのみですべてのサブビューのレンダリングを平坦化します(サブビューごとに1つのビットマップではなく?)。とにかく、いくつかの短所があるかどうかはわかりませんが、劇的に高速です!

93
Altimac

私はUICollectionViewのパフォーマンスについてかなりの調査を行ってきました。結論は簡単です。多数のセルのパフォーマンスが低下します。



編集:申し訳ありませんが、投稿を読み直してください。あなたが持っているセルの数は問題ないはずです(私のコメントの残りを参照)ので、セルの複雑さも問題になるかもしれません。

設計でサポートされている場合は、次を確認してください。

  1. 各セルは不透明です。

  2. セルコンテンツは境界にクリップします。

  3. セル座標位置には小数値が含まれていません(たとえば、常にピクセル全体になるように計算します)

  4. セルが重複しないようにしてください。

  5. ドロップシャドウを避けるようにしてください。



この理由は実際には非常に簡単です。多くの人はこれを理解していませんが、理解する価値があります。UIScrollViewsはコアアニメーションを使用してスクロールしません。私の素朴な信念は、彼らはいくつかの秘密のスクロールアニメーション「ソース」を含み、状況を確認するために時々時々デリゲートから時々更新を要求したというものでした。しかし、実際には、スクロールビューは描画動作をまったく適用しないオブジェクトです。実際に含まれるのは、含まれるUIViewの座標配置を抽象化する数学関数を適用するクラスのみです。そのため、Views座標は、含まれるビューのOriginに関連するのではなく、抽象contentViewプレーンに関連するものとして扱われます。スクロールビューは、ユーザー入力(スワイプなど)に応じて抽象的なスクロールプレーンの位置を更新します。もちろん、変換された座標位置に「運動量」を与える物理アルゴリズムもあります。

理論的には、独自のコレクションビューレイアウトオブジェクトを作成する場合、理論的には、基になるスクロールビューによって適用される数学的な変換を100%逆にするオブジェクトを作成できます。スワイプしてもセルがまったく動いていないように見えるので、これは興味深いが役に立たないだろう。ただし、コレクションビューオブジェクト自体を操作するコレクションビューレイアウトオブジェクトが、基になるスクロールビューと非常によく似た操作を行うことを示しているため、この可能性を上げています。例えば。表示されるビューの属性をフレーム変換することにより、追加の数学的フレームを適用する機会を提供するだけであり、主にこれは単に位置属性の変換になります。

CoreAnimationが関与するのは、新しいセルが挿入または削除、移動、または再ロードされたときだけです。ほとんどの場合、以下を呼び出します。

- (void)performBatchUpdates:(void (^)(void))updates
                 completion:(void (^)(BOOL finished))completion

UICollectionViewは、スクロールフレームごとにセルlayoutAttributesを要求し、各表示ビューはフレームごとにレイアウトされます。 UIViewは、パフォーマンスよりも柔軟性のために最適化されたリッチオブジェクトです。レイアウトするたびに、システムがアルファ、zIndex、サブビュー、クリッピング属性などをチェックするために行う多くのテストがあります。リストは長いです。これらのチェックおよび結果として生じるビューの変更は、各フレームの各コレクションビューアイテムに対して実行されます。

良好なパフォーマンスを確保するには、すべてのフレームごとの操作を17ms以内に完了する必要があります。 [あなたが持っているセルの数で、それは単純に起こりません]私はあなたの投稿を読み直したので、この句を括弧で囲みました。セルの数が多ければ、パフォーマンスの問題はないはずです。私は個人的に、単一のキャッシュ画像のみを含むバニラセルを使用した簡単なテストで発見しました.iPad 3でテストされた制限は、パフォーマンスが50fps未満に低下する前に約784の画面上のセルです。

実際には、これよりも少なくする必要があります。

個人的には、独自のカスタムレイアウトオブジェクトを使用しており、UICollectionViewが提供するよりも高いパフォーマンスが必要です。残念ながら、上記の簡単なテストは、開発パスを何らかの方法で下るまで実行せず、パフォーマンスの問題があることに気付きました。私は、UICollectionView、PSTCollectionViewのオープンソースバックポートを作り直すつもりです。実装できる回避策があると思うので、一般的なスクロールのためだけに、各セル項目のレイヤーは、各UIViewセルのレイアウトを回避する操作を使用して記述されます。私は自分のレイアウトオブジェクトを持っているので、これはうまくいきます。レイアウトがいつ必要か、PSTCollectionViewをこの時点で通常の動作モードに戻すことができる巧妙なトリックがあります。呼び出しシーケンスを確認しましたが、複雑すぎないように見えますし、実行不可能でもありません。しかし、確かにそれは重要なことであり、動作することを確認する前に、さらにいくつかのテストを行う必要があります。

17
TheBasicMind

役に立つかもしれないいくつかの観察:

約175項目を一度に表示できるフローレイアウトを使用して、問題を再現できます。シミュレータではスムーズにスクロールしますが、iPhone 5では地獄のように遅れます。

enter image description here

最終的に最も時間がかかるのは、_updateVisibleCellsNow。両方とも辞書をコピーしますが、キーでアイテムを検索します。キーはUICollectionViewItemKeyと[UICollectionViewItemKey isEqual:] methodは、最も時間のかかる方法です。 UICollectionViewItemKeyには、少なくともtypeidentifierおよびindexPathプロパティ、および含まれるプロパティのindexPath比較[NSIndexPath isEqual:]が最も時間がかかります。

それから、UICollectionViewItemKeyのハッシュ関数はisEqual:は、辞書検索中に頻繁に呼び出されます。アイテムの多くは、同じハッシュで(または同じハッシュバケットで、NSDictionaryがどのように機能するかわからない)で終わる可能性があります。

何らかの理由で、各セクションに7つのアイテムがある多くのセクションと比較して、1つのセクションにあるすべてのアイテムの方が高速です。おそらく、NSIndexPath isEqualで非常に多くの時間を費やし、行が最初に差分をとる場合、またはUICollectionViewItemKeyがより良いハッシュを取得する場合に高速になるためです。

正直に言って、UICollectionViewは、各フレームの更新がラグを回避するために16ms未満である必要がある前に述べたように、重いスクロール辞書がすべてのスクロールフレームを処理することを本当に奇妙に感じます。その多くの辞書検索が次のいずれかであるのだろうか?

  • 一般的なUICollectionView操作に本当に必要
  • ほとんど使用されないエッジケースをサポートするためにあり、ほとんどのレイアウトでオフにすることができます。
  • まだ修正されていない最適化されていない内部コード
  • 私たちの側からのいくつかの間違い

今年の夏にWWDCで何らかの改善が見られるか、または他の誰かがそれを修正する方法を見つけられるかどうかを期待しています。

8

以下は、Altimacの回答をSwift 3:

cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale

また、このコードはcellForItemAtIndexPathのcollectionViewデリゲートメソッドに含まれることに注意してください。

もう1つのヒント-アプリの1秒あたりのフレーム数(FPS)を確認するには、InstrumentsでCore Animationを開きます(画像を参照)。

enter image description here

7
Gene Loparco

上記の2つの答えに大賛成です!もう1つ試してみてください。自動レイアウトを無効にすることで、UICollectionViewのパフォーマンスが大幅に向上しました。セル内部をレイアウトするには追加のコードを追加する必要がありますが、カスタムコードは自動レイアウトよりも非常に高速であるようです。

6
NSSplendid

問題は、コレクションビューの合計に表示されるセルの数ではなく、一度に画面に表示されるセルの数です。セルサイズは非常に小さい(22x22)ため、画面には154個のセルが一度に表示されます。これらのそれぞれをレンダリングすることが、インターフェースの速度を低下させています。ストーリーボードのセルサイズを増やしてアプリを再実行すると、これを証明できます。

残念ながら、できることはあまりありません。境界へのクリッピングを回避し、drawRect:を実装しないようにすることで問題を緩和することをお勧めします。遅いためです。

6
Ash Furrow

まれに、UICollectionViewCellの自動レイアウトが原因です。オフにすると(それなしでライブできる場合)、スクロールはバターのように滑らかになります。

4
cirronimbo

@Altimacと同じように、UICollectionViewでのiPadスクロールの問題を解決するためにもこのコードを使用しました。

    cell.layer.shouldRasterize = YES;
    cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

6〜10fpsもありましたが、57fpsになりました

3
iFoul

グリッドレイアウトを実装している場合は、各UICollectionViewCellに対して単一のrowを使用し、ネストされたUIViewcellに追加することにより、この問題を回避できます。実際にUICollectionViewCellUICollectionReusableViewをサブクラス化し、prepareForReuseメソッドをオーバーライドして、すべてのサブビューを削除しました。 collectionView:cellForItemAtIndexPath:では、元の実装で使用されるsubviews座標にフレームを設定するセルであったすべてのxを追加しますが、yの内部になるようにcell座標を調整します。この方法を使用すると、targetContentOffsetForProposedContentOffset:withScrollingVelocity:などのUICollectionViewの便利な機能を使用して、セルの上部と左側にうまく配置できました。 4-6 FPSからスムーズ60 FPSに変更しました。

3
CWitty

リストされた回答(ラスタライズ、自動レイアウトなど)のほかに、パフォーマンスを低下させる可能性のある他の理由を確認することもできます。

私の場合、各UICollectionViewCellには別のUICollectionView(それぞれ約25セル)が含まれており、各セルの読み込み時に、パフォーマンスを大幅に低下させる内部UICollectionView.reloadData()を呼び出します。

次に、メインUIキュー内にreloadDataを配置すると、問題はなくなりました。

DispatchQueue.main.async {
    self.innerCollectionView.reloadData()
}

これらのような理由を注意深く調べることも役立ちます。ありがとう! :)

3
RainCast

非常によく似た問題-イメージベースのUICollectionViewに直面したため、すぐに解決策を提供すると思いました。

私が取り組んでいたプロジェクトでは、ネットワーク経由で画像を取得し、デバイス上でローカルにキャッシュしてから、スクロール中にキャッシュされた画像を再ロードしていました。

私の欠点は、バックグラウンドスレッドでキャッシュされた画像をロードしていなかったことです。

_[UIImage imageWithContentsOfFile:imageLocation];バックグラウンドスレッドに(そしてメインスレッド経由でimageViewに適用します)、FPSとスクロールが非常に良くなりました。

まだ試していない場合は、ぜひ試してください。

0
roycable