webentwicklung-frage-antwort-db.com.de

Wenn die UICollectionView-Layoutzellen in iOS 12 verwendet werden, verwenden Sie das Autolayout in der Spitze

Gleicher Code wie dieser

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

zellbeschränkungen:

 enter image description here

wenn ich es auf iOS 12 starte, ist es anders Linker Simulator ist iOS 11 und rechts ist iOS 12:

 enter image description here

Beim Scrollen werden die Rahmen der Zellen jedoch normal sein.


Beispielprojekt zum Reproduzieren des Problems: https://github.com/Coeur/StackOverflow51375566

27
EvilHydra

Beachten Sie bei allen Lösungen, dass es nicht erforderlich ist, reloadData in viewDidLoad explizit aufzurufen. Dies geschieht automatisch.

Lösung 1

Inspiriert von Samantha Idee : invalidateLayout asynchron in viewDidLoad.

override func viewDidLoad() {
    super.viewDidLoad()

    //[...]

    for _ in 0 ..< 1000 {
        array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
    }

    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

Lösung 2

(unvollkommen, siehe DHennessy13-Verbesserung)

Basierend auf Peter Lapisus Antwort . invalidateLayout in viewWillLayoutSubviews.

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

Wie von DHennessy13 bemerkt, ist diese aktuelle Lösung mit viewWillLayoutSubviews unvollständig, da sie beim Drehen des Bildschirms ungültig wird.

Sie können DHennessy13-Verbesserung in Bezug auf diese Lösung verfolgen.

Lösung 3

Basierend auf einer Kombination aus Tyler Sheaffer Antwort , Shawn Aukstak und Swift und Samantha-Idee. Subclass your CollectionView, um invalidateLayout für layoutSubviews auszuführen.

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

Diese Lösung ist elegant, da der ViewController-Code nicht geändert werden muss. Ich habe es auf dem Zweig AutoLayoutCollectionView dieses Beispielprojekts https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView implementiert.

Lösung 4

Umschreiben Sie die UICollectionViewCell-Standardeinschränkungen. Siehe Larry antwort .

Lösung 5

Implementieren Sie collectionView(_:layout:sizeForItemAt:) und geben Sie cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) zurück. Siehe matt antwort .

20
Cœur

Hier ist eine weitere Lösung, die mit dem Codebeispiel von Cœur funktioniert, und auch für meinen speziellen Fall, bei dem die anderen Antworten nicht stimmten. Der folgende Code ersetzt die vorherige Implementierung der CollectionViewCell-Unterklasse in 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])
    }
}

Dies ist inspiriert durch die Antwort von ale84 aus UICollectionViewFlowLayout EstimatedItemSize funktioniert nicht ordnungsgemäß mit iOS12, obwohl es mit iOS 11 problemlos funktioniert. *

19
Larry

Das Problem ist, dass das Feature hier positioniert ist - Collection-Ansicht Zellen, deren Größe sich aufgrund ihrer internen Einschränkungen ergibt - existiert nicht Es hat niemals existiert. Apple behauptet, dass dies der Fall ist, aber nicht. Ich habe diesbezüglich jedes Jahr einen Fehler eingereicht, seit die Ansichten der Sammlung eingeführt wurden und diese Behauptung zuerst gemacht wurde. und meine Fehlerberichte wurden nie geschlossen, weil der Fehler echt ist. Es gibt keine Ansichtszellen für die Größenänderung in der Sammlung.

Siehe auch meine Antwort hier: https://stackoverflow.com/a/51585910/341994 \

In einigen Jahren ist der Versuch, Zellen mit automatischer Größenbestimmung zu verwenden, abgestürzt. In anderen Jahren stürzt es nicht ab, aber das Layout wird falsch angezeigt. Aber es funktioniert nicht.

Die Methode {nur, um diese Art von Vorgang auszuführen, besteht darin, die Delegatmethode sizeForItemAt zu implementieren und die Größe selbst anzugeben. Sie können das leicht tun, indem Sie anrufen 

cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

in einer Modellzelle, die Sie zuvor konfiguriert haben. Das ist, was die Laufzeit sollte für Sie tun - aber das tut es nicht.

Hier ist meine Lösung für die ursprüngliche Frage. Anstelle eines einfachen Arrays von Strings haben wir ein Array von Stringgrößenpaaren (als Tuple) generiert. Dann:

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
}
9
matt

Ich habe das gleiche Problem, Zellen verwenden die geschätzte Größe (anstelle der automatischen Größe), bis gescrollt wird. Derselbe Code, der mit Xcode 9.x erstellt wurde, läuft unter iOS 11 und 12 einwandfrei, und in Xcode 10 ist er korrekt unter iOS 11, aber nicht unter iOS 12 ausgeführt.

Der einzige Weg, den ich bisher gefunden habe, um das Problem zu beheben, besteht darin, das Layout der Sammlungsansicht entweder in viewDidAppear für ungültig zu erklären, was einen gewissen Sprung verursachen kann, oder in einem asynchronen Block in viewWillAppear (nicht sicher, wie zuverlässig diese Lösung ist).

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

Lösung 2 von Cœur s verhindert, dass das Layout vor dem Benutzer blinkt oder aktualisiert wird. Es kann jedoch zu Problemen beim Drehen des Geräts kommen. Ich verwende eine Variable "shouldInvalidateLayout" in viewWillLayoutSubviews und setze sie in viewDidAppear auf false.

private var shouldInvalidateLayout = true

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    shouldInvalidateLayout = false
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    if shouldInvalidateLayout {
        collectionView.collectionViewLayout.invalidateLayout()
    }
}
6
DHennessy13

Wir hatten das gleiche Problem bei unserem Projekt. Wir haben auch Unterschiede zwischen den verschiedenen Geräten in iOS 12 festgestellt, die einen Aufruf von layoutIfNeeded & invalidateLayout erfordern. Die Lösung basiert auf dem Ansatz von @Dhennessy13, erfordert jedoch kein Boolean, um Zustände zu verwalten, die etwas hackig zu sein schienen.

Hier basiert er auf einem Rx-Code. Im Grunde ist die erste Zeile, wenn sich die Daten ändern. Innerhalb von subscribe müssen die unangenehmen iOS 12-Benutzeroberflächenfehler behoben werden:

        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)

Bearbeiten:

Es scheint übrigens ein bekanntes Problem in iOS 12 zu sein: https://developer.Apple.com/documentation/ios_release_notes/ios_12_release_notes (in UIKit-Abschnitt).

0
Toka

Versuche dies

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

Das Hinzufügen von viewDidAppear und viewWillAppear wird natürlich funktionieren. viewDidAppear verursacht jedoch eine Störung beim Benutzer.

0