Même code comme celui-ci
collectionLayout.estimatedItemSize = CGSize(width: 50, height: 25)
collectionLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
collectionLayout.minimumInteritemSpacing = 10
for _ in 0 ..< 1000 {
let length = Int(arc4random() % 8)
let string = randomKeyByBitLength(length)
array.append(string!)
}
collectionView.reloadData()
contraintes de cellule:
quand je le lance sur iOS 12, c'est différent . le simulateur de gauche est iOS 11, et à droite, iOS 12:
Mais, lorsque je le ferai défiler, les cadres des cellules seront normaux.
Exemple de projet pour reproduire le problème: https://github.com/Coeur/StackOverflow51375566
Pour toutes les solutions, notez qu'il n'est pas nécessaire d'appeler explicitement reloadData
dans viewDidLoad
: cela se produira automatiquement.
Inspiré par Idée Samantha : invalidateLayout
de manière asynchrone dans viewDidLoad
.
override func viewDidLoad() {
super.viewDidLoad()
//[...]
for _ in 0 ..< 1000 {
array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
}
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
(imparfait, voir amélioration DHennessy13 dessus)
Basé sur Peter Lapisu répond . invalidateLayout
in viewWillLayoutSubviews
.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
Comme l'a noté DHennessy13, cette solution actuelle avec viewWillLayoutSubviews
est imparfaite, car elle invalidera Layout lors de la rotation de l'écran.
Vous pouvez suivre amélioration DHennessy13 concernant cette solution.
Basé sur une combinaison de Tyler Sheaffer répond , Le port de Shawn Aukstak à Swift et l’idée de Samantha. Sous-classez votre CollectionView pour exécuter invalidateLayout
sur layoutSubviews
.
class AutoLayoutCollectionView: UICollectionView {
private var shouldInvalidateLayout = false
override func layoutSubviews() {
super.layoutSubviews()
if shouldInvalidateLayout {
collectionViewLayout.invalidateLayout()
shouldInvalidateLayout = false
}
}
override func reloadData() {
shouldInvalidateLayout = true
super.reloadData()
}
}
Cette solution est élégante car elle ne nécessite pas de changer votre code ViewController. Je l'ai implémenté sur la branche AutoLayoutCollectionView de cet exemple de projet https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView .
Réécrivez les contraintes par défaut UICollectionViewCell. Voir Larry répondre .
Implémentez collectionView(_:layout:sizeForItemAt:)
et retournez cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
. Voir réponse mate .
Voici une autre solution qui fonctionne sur l'échantillon de code de Cœur et qui a également fonctionné pour mon cas particulier, où les autres réponses ne fonctionnaient pas. Le code ci-dessous remplace la précédente implémentation de la sous-classe CollectionViewCell
dans ViewController.Swift
:
class CollectionViewCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor)
let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor)
let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor)
let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}
}
Ceci est inspiré par la réponse de ale84 de UICollectionViewFlowLayout EstiméItemSize ne fonctionne pas correctement avec iOS12 bien que cela fonctionne correctement avec iOS 11. *
Le problème est que la caractéristique proposée ici - les cellules de vue de collection qui se dimensionnent en fonction de leurs contraintes internes - n'existe pas. Il a (jamais} _ existé. Apple prétend que c'est le cas, mais ce n'est pas le cas. J'ai déposé un bogue à ce sujet chaque année depuis que les vues de collection ont été introduites et que cette réclamation a été faite pour la première fois; et mes rapports de bogues n'ont jamais été fermés, car le bogue est réel. Il n’existe pas de cellules à affichage de collection auto-dimensionnées.
Voir aussi ma réponse ici: https://stackoverflow.com/a/51585910/341994 \
Certaines années, l’essai d’utiliser des cellules à auto-dimensionnement s’est écrasé. Les autres années, cela ne plante pas, mais la mise en page est incorrecte. Mais ça ne fonctionne pas.
La seule manière de faire ce genre de chose consiste à implémenter la méthode de délégué sizeForItemAt
et à fournir la taille vous-même. Vous pouvez facilement le faire en appelant
cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
sur une cellule modèle que vous avez configurée à l'avance. C'est ce que le moteur d'exécution {devrait} _ fait pour vous - mais ce n'est pas le cas.
Alors voici ma solution à la question initiale. Au lieu d'un simple tableau de chaînes, nous avons généré un tableau de paires taille-chaîne (sous forme de tuple). Ensuite:
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MyCell
cell.label.text = self.array[indexPath.row].0
return cell
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return self.array[indexPath.row].1
}
J'ai le même problème, les cellules utilisent la taille estimée (au lieu de la taille automatique) jusqu'au défilement. Le même code construit avec Xcode 9.x fonctionne parfaitement sur iOS 11 et 12, et construit dans Xcode 10, il fonctionne correctement sur iOS 11 mais pas sur iOS 12.
La seule solution que j’ai trouvée jusqu’à présent pour résoudre ce problème consiste à invalider la présentation de la vue de collection, soit dans viewDidAppear
, ce qui peut causer des sauts, ou dans un bloc asynchrone dans viewWillAppear
(je ne sais pas dans quelle mesure cette solution est fiable).
override func viewDidLoad() {
super.viewDidLoad()
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.estimatedItemSize = CGSize(width: 50, height: 50)
layout?.itemSize = UICollectionViewFlowLayout.automaticSize
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// The following block also "fixes" the problem without jumpiness after the view has already appeared on screen.
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// The following line makes cells size properly in iOS 12.
collectionView.collectionViewLayout.invalidateLayout()
}
La solution 2 de Cœur 'empêche la mise en page de clignoter ou de se mettre à jour devant l'utilisateur. Mais cela peut créer des problèmes lorsque vous faites pivoter l'appareil. J'utilise une variable "shouldInvalidateLayout" dans viewWillLayoutSubviews et la définit sur false dans viewDidAppear.
private var shouldInvalidateLayout = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
shouldInvalidateLayout = false
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if shouldInvalidateLayout {
collectionView.collectionViewLayout.invalidateLayout()
}
}
Nous avons eu le même problème sur notre projet. Nous avons également remarqué des différences entre les multiples périphériques dans iOS 12, nécessitant un appel à layoutIfNeeded
& invalidateLayout
. La solution est basée sur l'approche de @ DHennessy13 mais ne nécessite pas de booléen pour gérer des états qui semblaient légèrement hacky.
Ici, il est basé sur un code Rx, la première ligne concerne essentiellement le moment où les données changent. Dans la variable subscribe
, il convient de corriger le problème lié au problème méchant de l'interface utilisateur iOS 12:
viewModel.cellModels.asObservable()
.subscribe(onNext: { [weak self] _ in
// iOS 12 bug in UICollectionView for cell size
self?.collectionView.layoutIfNeeded()
// required for iPhone 8 iOS 12 bug cell size
self?.collectionView.collectionViewLayout.invalidateLayout()
})
.disposed(by: rx.disposeBag)
À propos, cela semble être un problème connu sous iOS 12: https://developer.Apple.com/documentation/ios_release_notes/ios_12_release_notes (dans la section UIKit).
Essaye ça
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
Ajouter à viewDidAppear
et viewWillAppear
fonctionnera bien sûr. Mais viewDidAppear
causera un problème technique à l'utilisateur.