web-dev-qa-db-fra.com

Performances UICollectionView - _updateVisibleCellsNow

Je travaille sur un UICollectionViewLayout personnalisé qui affiche les cellules organisées par jour/semaine/mois.

Il ne défile pas en douceur et il semble que le décalage soit provoqué par [UICollectionView _updateVisibleCellsNow] étant appelé sur chaque boucle de rendu.

Les performances sont bonnes pour <30 éléments, mais à environ 100 ou plus, c'est terriblement lent. Est-ce une limitation de UICollectionView et des dispositions personnalisées, ou ne donne-t-on pas suffisamment d'informations à la vue pour qu'elle fonctionne correctement?

Source ici: https://github.com/oskarhagberg/calendarcollection

Mise en page: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m

Source de données et délégué: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m

Profil horaire: Time profile - Custom layout

Mise à jour

Peut-être que c'est inutile? Certains tests avec un UICollectionViewController simple avec un UICollectionViewFlowLayout auquel on donne approximativement la même quantité de cellules/écran donnent un profil temporel similaire.

Time profile - Standard flow layout

Je pense qu'il devrait être capable de gérer ~ 100 cellules opaques simples à la fois sans la gigue. Ai-je tort?

52
Oskar

N'oubliez pas non plus d'essayer de pixelliser la couche de la cellule:

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

J'avais 10 fps sans ça, et boom 55fps avec! Je ne suis pas vraiment familier avec le GPU et le modèle de composition, donc que fait-il exactement n'est pas entièrement clair pour moi, mais fondamentalement, il aplatit le rendu de toutes les sous-vues en un seul bitmap (au lieu d'un bitmap par sous-vue?). Quoi qu'il en soit, je ne sais pas si cela a des inconvénients, mais c'est beaucoup plus rapide!

93
Altimac

J'ai fait une enquête considérable sur les performances d'UICollectionView. La conclusion est simple. Les performances d'un grand nombre de cellules sont médiocres.



EDIT: Toutes mes excuses, relisez simplement votre message, le nombre de cellules que vous avez devrait être OK (voir le reste de mon commentaire), donc la complexité des cellules peut également être un problème.

Si votre conception le prend en charge, vérifiez:

  1. Chaque cellule est opaque.

  2. Le contenu des cellules se clipse aux limites.

  3. Les positions des coordonnées de cellule ne contiennent pas de valeurs fractionnaires (par exemple, toujours calculer pour être des pixels entiers)

  4. Essayez d'éviter les cellules qui se chevauchent.

  5. Essayez d'éviter les ombres portées.



La raison en est assez simple. Beaucoup de gens ne comprennent pas cela, mais cela vaut la peine d'être compris: UIScrollViews n'utilise pas Core Animation pour faire défiler. Ma croyance naïve était qu'ils impliquaient une "sauce" d'animation de défilement secrète et demandaient simplement de temps en temps des mises à jour aux délégués pour vérifier le statut. Mais en fait, les vues de défilement sont des objets qui n'appliquent aucun comportement de dessin directement. Tout ce qu'ils sont vraiment, c'est une classe qui applique une fonction mathématique abstraite du placement des coordonnées des UIViews qu'ils contiennent, de sorte que les coordonnées des vues sont traitées comme relatives à un plan contentView abstrait plutôt que par rapport à l'origine de la vue contenant. Une vue de défilement mettra à jour la position du plan de défilement abstrait en accord avec les entrées de l'utilisateur (par exemple, le balayage) et bien sûr, il existe également un algorithme physique qui donne un "momentum" aux positions de coordonnées traduites.

Maintenant, si vous deviez produire votre propre objet de disposition de vue de collection, en théorie, vous pourriez en produire un qui inverserait à 100% la traduction mathématique appliquée par la vue de défilement sous-jacente. Ce serait intéressant mais inutile, car il semblerait alors que les cellules ne bougent pas du tout lorsque vous glissez. Mais je soulève cette possibilité car elle illustre que l'objet de présentation de la vue de collection qui fonctionne avec l'objet de vue de collection lui-même effectue une opération très similaire à la vue de défilement sous-jacente. Par exemple. il fournit simplement une opportunité d'appliquer une traduction mathématique supplémentaire image par image des attributs des vues à afficher, et dans l'ensemble, ce sera simplement une traduction des attributs de position.

Ce n'est que lorsque de nouvelles cellules sont insérées ou supprimées déplacées ou rechargées que CoreAnimation est impliquée; le plus souvent en appelant:

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

UICollectionView demande des attributs de disposition de cellule pour chaque cadre de défilement et chaque vue visible est disposée pour chaque cadre. Les UIView sont des objets riches optimisés pour plus de flexibilité que de performances. Chaque fois qu'un plan est présenté, le système effectue un certain nombre de tests pour vérifier ses attributs alpha, zIndex, subViews, écrêtage, etc. La liste est longue. Ces vérifications et toute modification de la vue qui en résulte sont effectuées pour chaque élément de vue de collection pour chaque image.

Pour garantir de bonnes performances, toutes les opérations image par image doivent être effectuées dans les 17 ms. [Avec le nombre de cellules que vous avez, cela ne va tout simplement pas arriver] entre crochets cette clause parce que j'ai relu votre message et je me rends compte que je l'avais mal lu. Avec le nombre de cellules dont vous disposez, il ne devrait pas y avoir de problème de performances. J'ai personnellement trouvé avec un test simplifié avec des cellules Vanilla, ne contenant qu'une seule image en cache, la limite testée sur un iPad 3 est d'environ 784 cellules à l'écran avant que les performances commencent à descendre en dessous de 50 images par seconde.

En pratique, vous devrez le garder moins que cela.

Personnellement, j'utilise mon propre objet de présentation personnalisé et j'ai besoin de performances supérieures à celles fournies par UICollectionView. Malheureusement, je n'ai pas exécuté le test simple ci-dessus jusqu'à un certain point sur le chemin du développement et j'ai réalisé qu'il y avait des problèmes de performances. Je suis donc je vais retravailler le back-port open source de UICollectionView, PSTCollectionView. Je pense qu'il existe une solution de contournement qui peut être implémentée donc, juste pour le défilement général, la couche de chaque élément de cellule est écrite en utilisant une opération qui contourne la disposition de chaque cellule UIView. Cela fonctionnera pour moi car j'ai mon propre objet de disposition, et je sais quand la disposition est requise et j'ai une astuce intéressante qui permettra à PSTCollectionView de revenir à son mode de fonctionnement normal à ce moment. J'ai vérifié la séquence d'appels et elle ne semble pas trop complexe, et elle ne semble pas du tout irréalisable. Mais c'est sûr que ce n'est pas anodin et que d'autres tests doivent être effectués avant que je puisse confirmer que cela fonctionnera.

17
TheBasicMind

Quelques observations supplémentaires qui pourraient être utiles:

Je suis capable de reproduire le problème, en utilisant la mise en page de flux avec environ 175 éléments visibles à la fois: il défile en douceur dans le simulateur mais traîne comme l'enfer sur l'iPhone 5. Assurez-vous qu'ils sont opaques, etc.

enter image description here

Ce qui finit par prendre le plus de temps semble être de travailler avec un NSDictionary mutable à l'intérieur _updateVisibleCellsNow. Copier le dictionnaire, mais aussi rechercher des éléments par clé. Les clés semblent être UICollectionViewItemKey et le [UICollectionViewItemKey isEqual:] méthode est la méthode la plus longue de tous. UICollectionViewItemKey contient au moins type, identifier et indexPath propriétés, et la propriété contenue indexPath comparaison [NSIndexPath isEqual:] prend le plus de temps.

De là, je suppose que la fonction de hachage de UICollectionViewItemKey pourrait faire défaut depuis isEqual: est appelé si souvent lors de la recherche dans le dictionnaire. De nombreux éléments peuvent se retrouver avec le même hachage (ou dans le même compartiment de hachage, vous ne savez pas comment NSDictionary fonctionne).

Pour une raison quelconque, il est plus rapide avec tous les articles dans 1 section, par rapport à de nombreuses sections avec 7 articles dans chacun. Probablement parce qu'il passe tellement de temps dans NSIndexPath isEqual et c'est plus rapide si la ligne diffère en premier, ou peut-être que UICollectionViewItemKey obtient un meilleur hachage.

Honnêtement, cela semble vraiment bizarre que UICollectionView fasse ce travail de dictionnaire lourd chaque trame de défilement, comme mentionné avant chaque mise à jour de trame doit être <16 ms pour éviter le décalage. Je me demande si ce nombre de recherches de dictionnaire est:

  • Vraiment nécessaire pour le fonctionnement général d'UICollectionView
  • Là pour prendre en charge certains cas Edge rarement utilisés et pourraient être désactivés pour la plupart des mises en page
  • Un code interne non optimisé qui n'a pas encore été corrigé
  • Une erreur de notre côté

J'espère que nous verrons une amélioration cet été pendant la WWDC, ou si quelqu'un d'autre ici peut trouver une solution.

8

Voici la réponse d'Altimac convertie en Swift 3:

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

En outre, il convient de noter que ce code va dans votre méthode déléguée collectionView pour cellForItemAtIndexPath.

Encore une astuce - pour voir les images d'une application par seconde (FPS), ouvrez Core Animation sous Instruments (voir image).

enter image description here

7
Gene Loparco

Bravo aux deux réponses ci-dessus! Voici une autre chose que vous pouvez essayer: j'ai amélioré considérablement les performances de UICollectionView en désactivant la disposition automatique. Bien que vous deviez ajouter du code supplémentaire pour mettre en page l'intérieur des cellules, le code personnalisé semble être considérablement plus rapide que la mise en page automatique.

6
NSSplendid

Le problème n'est pas le nombre de cellules que vous affichez dans le total de la vue de la collection, c'est le nombre de cellules qui sont à l'écran à la fois. Étant donné que la taille des cellules est très petite (22 x 22), vous avez 154 cellules visibles à l'écran à la fois. Le rendu de chacun d'eux ralentit votre interface. Vous pouvez le prouver en augmentant la taille des cellules dans votre Storyboard et en réexécutant l'application.

Malheureusement, vous ne pouvez pas faire grand-chose. Je recommande d'atténuer le problème en évitant de couper dans les limites et en essayant de ne pas implémenter drawRect:, car c'est lent.

6
Ash Furrow

Dans certains cas, cela est dû à la mise en page automatique dans UICollectionViewCell. Désactivez-le (si vous pouvez vous en passer) et le défilement deviendra fluide :) C'est un problème iOS, qu'ils n'ont pas résolu depuis des lustres.

4
cirronimbo

Tout comme @Altimac, j'ai également utilisé ce code pour résoudre un problème de défilement de l'iPad sur UICollectionView:

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

J'ai aussi eu 6-10fps, et j'ai maintenant 57fps

3
iFoul

Si vous implémentez une disposition de grille, vous pouvez contourner cela en utilisant un seul UICollectionViewCell pour chaque row et ajouter des UIView imbriqués dans le cell. En fait, j'ai sous-classé UICollectionViewCell et UICollectionReusableView et j'ai remplacé la méthode prepareForReuse pour supprimer toutes les sous-vues. Dans collectionView:cellForItemAtIndexPath: J'ajoute toutes les subviews qui étaient à l'origine des cellules définissant leur cadre sur la coordonnée x utilisée dans l'implémentation d'origine, mais en ajustant sa coordonnée y pour qu'elle soit à l'intérieur du cell. En utilisant cette méthode, j'ai pu encore utiliser certaines des subtilités de UICollectionView telles que targetContentOffsetForProposedContentOffset:withScrollingVelocity: pour bien s'aligner sur les côtés supérieur et gauche d'une cellule. Je suis passé de 4-6 FPS à un bon 60 FPS.

3
CWitty

Outre les réponses répertoriées (pixellisation, mise en page automatique, etc.), vous pouvez également rechercher d'autres raisons susceptibles de ralentir les performances.

Dans mon cas, chacune de mes UICollectionViewCell contient une autre UICollectionView (avec environ 25 cellules chacune), lorsque chacune des cellules est en cours de chargement, j'appelle l'UICollectionView.reloadData () interne, ce qui réduit considérablement les performances.

Ensuite, j'ai mis le reloadData dans la file d'attente principale de l'interface utilisateur, le problème a disparu:

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

Examiner attentivement des raisons comme celles-ci pourrait également aider. Merci! :)

3
RainCast

Je pensais donner rapidement ma solution, car je faisais face à un problème très similaire - UICollectionView basé sur l'image.

Dans le projet dans lequel je travaillais, je récupérais des images via le réseau, je les mettais en cache localement sur l'appareil, puis je rechargeais l'image mise en cache pendant le défilement.

Mon défaut était que je ne chargeais pas d'images mises en cache dans un thread d'arrière-plan.

Une fois que j'ai mis mon [UIImage imageWithContentsOfFile:imageLocation]; dans un fil d'arrière-plan (puis appliqué à mon imageView via mon fil principal), mon FPS et mon défilement étaient bien meilleurs.

Si vous ne l'avez pas encore essayé, essayez définitivement.

0
roycable