53 votes

Dans iOS 12, quand la mise en page des cellules de l'UICollectionView doit-elle utiliser la mise en page automatique dans la NIB ?

Le même code comme ceci

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()

les contraintes des cellules :

enter image description here

quand je l'exécute sous iOS 12, c'est différent. Le simulateur de gauche est iOS 11, et celui de droite est iOS 12 :

enter image description here

Mais, quand je le ferai défiler, les cadres des cellules seront normaux.


Exemple de projet pour reproduire le problème : https://github.com/Coeur/StackOverflow51375566

0 votes

Je ne parviens pas à reproduire le problème car les versions ios 11 et ios 12 produisent le même résultat.

1 votes

@PranavKasetti Je l'ai reproduit facilement dans Xcode 10 beta 6 en utilisant le code donné. J'ai mis un lien vers un exemple de projet dans la question pour plus de commodité.

60voto

Cœur Points 1538

Pour toutes les solutions, notez qu'il n'est pas nécessaire d'appeler explicitement reloadData en viewDidLoad : cela se fera automatiquement.

Solution 1

Inspiré par L'idée de 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()
    }
}

Solution 2

(imparfait, voir l'amélioration de DHennessy13 à ce sujet)

Sur la base de Réponse de Peter Lapisu . invalidateLayout en viewWillLayoutSubviews .

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView.collectionViewLayout.invalidateLayout()
}

Comme l'a noté DHennessy13, cette solution actuelle avec viewWillLayoutSubviews est imparfait car il invalideraLayout lors de la rotation de l'écran.

Vous pouvez suivre Amélioration DHennessy13 concernant cette solution.

Solution 3

Sur la base d'une combinaison de Réponse de Tyler Sheaffer , Portage de Shawn Aukstak chez Swift et l'idée de Samantha. Sous-classez votre CollectionView pour effectuer invalidateLayout en 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 modifier le code de votre ViewController. Je l'ai implémentée sur la branche AutoLayoutCollectionView de cet exemple de projet. https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView .

Solution 4

Réécriture des contraintes par défaut de UICollectionViewCell. Voir Réponse de Larry .

Solution 5

Mettre en œuvre collectionView(_:layout:sizeForItemAt:) et retourner cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) . Voir réponse mate .

0 votes

La troisième solution comporte également des avertissements de mise en page sur iOS 12 - Xcode 10

0 votes

Après avoir essayé de modifier le nombre d'éléments de la collectionView en les ajoutant ou en les supprimant.

0 votes

@Stan Vous pouvez fournir une réponse concernant la solution qui est compatible avec l'édition de la vue de la collection, et éventuellement un exemple de mise en œuvre qui illustre le problème avec l'édition (peut-être un fork de mon github ?).

31voto

matt Points 60113

Le problème est que la fonctionnalité proposée ici - des cellules de vue de collection qui se dimensionnent elles-mêmes en fonction de leurs contraintes internes dans un UICollectionViewFlowLayout - ne peut pas être utilisée. n'existe pas . Il a nunca existé. Apple prétend que c'est le cas, mais ce n'est pas le cas. J'ai signalé un bug à ce sujet chaque année depuis que les vues de collection ont été introduites et que cette affirmation a été faite pour la première fois ; et mes rapports de bug n'ont jamais été fermés, parce que le bug est réel. Il n'existe pas de cellules de vue de collection à taille unique.

Voir aussi ma réponse ici : https://stackoverflow.com/a/51585910/341994

Certaines années, la tentative d'utiliser des cellules autodimensionnées s'est soldée par un échec. D'autres années, il n'y a pas eu de plantage, mais la mise en page était erronée. Mais il ne fonctionne pas .

El seulement La façon de faire ce genre de chose est d'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 au préalable. C'est ce que le runtime debe faire pour vous - mais ce n'est pas le cas.

Voici donc ma solution à la question initiale. Au lieu d'un simple tableau de chaînes de caractères, nous avons généré un tableau de paires de chaînes de caractères (comme un 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
}

1 votes

Que voulez-vous dire par "cellule que vous avez configurée à l'avance" ? Où faites-vous réellement l'appel à cell.contentView.systemLayoutSizeFitting(:) ?

0 votes

@d4Rk plus facile à montrer qu'à décrire, voir raw.githubusercontent.com/mattneub/

0 votes

Ok, donc en gros tu instancies une cellule par toi-même, puis dans sizeForItemAt le configurer avec les données de ce même indexPath, puis laisser systemLayoutSizeFitting "faire la mise en page automatique" et renvoyer cette taille. Même si c'est la bonne solution, il y a encore beaucoup à faire, mais il semble qu'il n'y ait pas de solution vraiment satisfaisante, tant qu'Apple ne corrige pas ce problème.

29voto

Larry Points 384

Voici une autre solution qui fonctionne sur Cœur Il a également fonctionné dans mon cas particulier, alors que les autres réponses ne l'ont pas fait. Le code ci-dessous remplace l'implémentation précédente de la fonction CollectionViewCell sous-classe 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é de la réponse de ale84 de UICollectionViewFlowLayout estimatedItemSize ne fonctionne pas correctement avec iOS12 alors qu'il fonctionne bien avec iOS 11.*.

1 votes

Cette approche comporte toujours des avertissements de mise en page sur inserting / deleting articles collectionView événements :(

1 votes

Cette approche résout complètement le problème pour moi. Merci.

0 votes

Excellent. Les autres solutions ne fonctionnaient pas pour moi, mais celle-ci fonctionne.

12voto

Samantha Points 1683

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 bien sur iOS 11 et 12, et construit dans Xcode 10 il fonctionne correctement sur iOS 11 mais pas sur iOS 12.

La seule façon que j'ai trouvée jusqu'à présent pour résoudre ce problème est d'invalider la mise en page de la vue de la collection, soit dans l'onglet viewDidAppear ce qui peut provoquer des sauts, ou dans un bloc asynchrone à l'intérieur de viewWillAppear (je ne suis pas sûr de la fiabilité de cette solution).

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()
}

0 votes

Cela fonctionne, merci. Et écrire un bloc asynchrone à l'intérieur de viewWillAppear semble mieux que viewDidApear.

0 votes

J'ai trouvé d'autres moyens d'appeler invalidateLayout au lieu de viewWillAppear .

0 votes

J'ai dû combiner cela avec un appel manuel à NSString Size, et j'ai simplement renvoyé manuellement la taille. Mais les fonctions n'étaient pas appelées avant de faire ce que vous avez dit. "sizeforitematindexpath" devient 0 dans iOS 12, fondamentalement, même avec cette réponse

6voto

DHennessy13 Points 61

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 je lui attribue la valeur 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()
    }
}

1 votes

C'est le seul qui a fonctionné pour moi sans clignoter. Comment est-ce possible que cela change sans aucune explication de la part d'Apple ? Ils vont juste dire de regarder la documentation et sur la prochaine version un autre bug qui n'a aucun sens.

0 votes

@Andreas777 veuillez partager avec moi un petit exemple de projet où d'autres solutions ne fonctionnent pas, afin que je puisse analyser la cause. Merci.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X