Swift: So aktualisieren Sie das UICollectionView-Layout nach dem Drehen des Geräts

72

Ich habe UICollectionView (Flowlayout) verwendet, um ein einfaches Layout zu erstellen. Die Breite für jede Zelle wird mit auf die Bildschirmbreite eingestelltself.view.frame.width

Aber wenn ich das Gerät drehe, werden die Zellen nicht aktualisiert.

Geben Sie hier die Bildbeschreibung ein

Ich habe eine Funktion gefunden, die bei Orientierungsänderung aufgerufen wird:

override func willRotateToInterfaceOrientation(toInterfaceOrientation: 
  UIInterfaceOrientation, duration: NSTimeInterval) {
    //code
}

Ich kann jedoch keine Möglichkeit finden, das UICollectionView-Layout zu aktualisieren

Der Hauptcode ist hier:

class ViewController: UIViewController , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{

    @IBOutlet weak var myCollection: UICollectionView!

    var numOfItemsInSecOne: Int!
    override func viewDidLoad() {
        super.viewDidLoad()

        numOfItemsInSecOne = 8
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {

        //print("orientation Changed")
    }

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numOfItemsInSecOne
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellO", forIndexPath: indexPath)

        return cell
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
    let itemSize = CGSize(width: self.view.frame.width, height: 100)
    return itemSize
    }}
Talha Ahmad Khan
quelle

Antworten:

77

Fügen Sie diese Funktion hinzu:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}

Wenn Sie die Ausrichtung ändern, wird diese Funktion aufgerufen.

appiconhero.co
quelle
2
UICollectionViewController hat bereits eine collectionViewEigenschaft, also rufen Sie sie einfach wie gesagt auf. Bearbeiten Sie sie einfach in collectionView.reloadData(). Es wird klappen.
appiconhero.co
2
Sie müssen also die Ansicht neu laden. Sie können nicht einfach eine Layoutaktualisierung auslösen.
Rivera
2
Die Antwort von check @ kelin .invalidateLayout()macht sehr viel Sinn.
Thesummersign
1
viewDidLayoutSubviewsWenn meine App zum Porträt zurückkehrt, stürzt meine App ab, weil alte Layoutzellen zu groß sind. viewWillLayoutSubviews funktioniert perfekt
jfgrang
2
Die App bleibt beim Starten der App hängen, nachdem Sie diese verwendet haben.
rv7284
59

Die bessere Option ist ein Anruf invalidateLayout()anstelle von, reloadData()da dadurch die Wiederherstellung der Zellen nicht erzwungen wird, sodass die Leistung etwas besser ist:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}
kelin
quelle
Dies ist der richtige Weg. Dies sollte die akzeptierte Antwort sein.
Thesummersign
Dies ist der richtige Weg, um es zu tun .. nicht die Daten neu laden
Abdul Waheed
8
das hat mich verknallt. und eine Endlosschleife, die erkannt wird, wenn ich eine print-Anweisung in die viewDidLayoutSubviews-Funktion einfüge.
Nyxee
2
@nyxee, viewWillLayoutSubviewsdann versuche es . Ich wette, Ihre Sammlungsansicht ist die Ansicht des View Controllers? Wenn ja, empfehle ich, es in eine andere Ansicht zu wickeln.
Kelin
6
Die Endlosschleife tritt bei Verwendung auf UICollectionViewController, da in diesem Fall collectionViewauch die Ansicht des Controllers angezeigt wird. Wenn Sie also das collectionViewLayout ändern, werden viewWillLayoutSubviewsverwandte Methoden aufgerufen.
Kelin
16

Sie können es auch auf diese Weise ungültig machen.

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [self.collectionView.collectionViewLayout invalidateLayout]; 
}
awuu
quelle
13

Das viewWillLayoutSubviews () hat bei mir nicht funktioniert. ViewDidLayoutSubviews () auch nicht. Beide ließen die App in eine Endlosschleife gehen, die ich mit einem Druckbefehl überprüfte.

Eine der Möglichkeiten, die funktionieren, ist

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}
Der Doktor
quelle
14
Vergessen Sie nicht anzurufensuper.viewWillTransition...
d4Rk
13

Zum Aktualisieren UICollectionViewLayoutkann auch die traitCollectionDidChangeMethode verwendet werden:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

    guard previousTraitCollection != nil else { return }
    collectionView?.collectionViewLayout.invalidateLayout()
}
vogelig
quelle
2
Dies hat für mich am besten funktioniert. Der erforderliche Ansatz hängt wirklich von Ihrer Anwendung von CollectionView ab. In meinem Fall war dies besser mit den anderen Verhaltensweisen meines benutzerdefinierten FlowLayout kompatibel als mit den anderen Lösungen, da meine Elemente alle dieselbe Größe haben, die von der abhängt traitCollection
Rocket Garden
2
Genial - funktioniert auch beim Instanziieren der UICollectionView in einer benutzerdefinierten UITableViewCell.
DrWhat
7

Wenn UICollectionLayouteine Änderung der Grenzen festgestellt wird, werden Sie gefragt, ob das ungültige Layout umgeleitet werden muss. Sie können die Methode direkt umschreiben. UICollectionLayoutkann die invalidateLayoutMethode zum richtigen Zeitpunkt aufrufen

class CollectionViewFlowLayout: UICollectionViewFlowLayout{

    /// The default implementation of this method returns false.
    /// Subclasses can override it and return an appropriate value
    /// based on whether changes in the bounds of the collection
    /// view require changes to the layout of cells and supplementary views.
    /// If the bounds of the collection view change and this method returns true,
    /// the collection view invalidates the layout by calling the invalidateLayout(with:) method.
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {

        return (self.collectionView?.bounds ?? newBounds) == newBounds
    }
}
Zheng
quelle
3

Sie können Ihr UICollectionView-Layout mithilfe von aktualisieren

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    if isLandscape {
        return CGSizeMake(yourLandscapeWidth, yourLandscapeHeight)
    }
    else {
        return CGSizeMake(yourNonLandscapeWidth, yourNonLandscapeHeight)
    }
}
Abhinandan Pratap
quelle
3

Anrufen viewWillLayoutSubviewsist nicht optimal. Versuchen Sie zuerst, die invalidateLayout()Methode aufzurufen .

Wenn der The behaviour of the UICollectionViewFlowLayout is not definedFehler auftritt, müssen Sie überprüfen, ob alle Elemente in Ihrer Ansicht ihre Größe gemäß einem neuen Layout geändert haben. (Siehe die optionalen Schritte im Beispielcode.)

Hier ist der Code, um Ihnen den Einstieg zu erleichtern. Abhängig von der Art und Weise, wie Ihre Benutzeroberfläche erstellt wird, müssen Sie möglicherweise experimentieren, um die richtige Ansicht zum Aufrufen der recalculateMethode zu finden. Dies sollte Sie jedoch zu Ihren ersten Schritten führen.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

    super.viewWillTransition(to: size, with: coordinator)

    /// (Optional) Additional step 1. Depending on your layout, you may have to manually indicate that the content size of a visible cells has changed
    /// Use that step if you experience the `the behavior of the UICollectionViewFlowLayout is not defined` errors.

    collectionView.visibleCells.forEach { cell in
        guard let cell = cell as? CustomCell else {
            print("`viewWillTransition` failed. Wrong cell type")
            return
        }

        cell.recalculateFrame(newSize: size)

    }

    /// (Optional) Additional step 2. Recalculate layout if you've explicitly set the estimatedCellSize and you'll notice that layout changes aren't automatically visible after the #3

    (collectionView.collectionViewLayout as? CustomLayout)?.recalculateLayout(size: size)


    /// Step 3 (or 1 if none of the above is applicable)

    coordinator.animate(alongsideTransition: { context in
        self.collectionView.collectionViewLayout.invalidateLayout()
    }) { _ in
        // code to execute when the transition's finished.
    }

}

/// Example implementations of the `recalculateFrame` and `recalculateLayout` methods:

    /// Within the `CustomCell` class:
    func recalculateFrame(newSize: CGSize) {
        self.frame = CGRect(x: self.bounds.origin.x,
                            y: self.bounds.origin.y,
                            width: newSize.width - 14.0,
                            height: self.frame.size.height)
    }

    /// Within the `CustomLayout` class:
    func recalculateLayout(size: CGSize? = nil) {
        estimatedItemSize = CGSize(width: size.width - 14.0, height: 100)
    }

    /// IMPORTANT: Within the `CustomLayout` class.
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {

        guard let collectionView = collectionView else {
            return super.shouldInvalidateLayout(forBoundsChange: newBounds)
        }

        if collectionView.bounds.width != newBounds.width || collectionView.bounds.height != newBounds.height {
            return true
        } else {
            return false
        }
    }
Michael
quelle
3

Für mich geht das. Und das ist Code in Objective-C:

- (void)viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];
  [collectionView.collectionViewLayout invalidateLayout];
}
congpc87
quelle
3

Bitte verstehe das

traitCollectionDidChange (previousTraitCollection :) wird beim Drehen nicht auf dem iPad aufgerufen, da die Größenklasse sowohl im Hoch- als auch im Querformat regelmäßig ist. Es gibt viewWillTransition (to: with :), die aufgerufen wird, wenn sich die Größe der Sammlungsansicht ändert.

Außerdem sollten Sie UIScreen.mainScreen (). Bounds nicht verwenden, wenn Ihre App Multitasking unterstützt, da sie möglicherweise nicht den gesamten Bildschirm einnimmt. Verwenden Sie dazu besser collectionView.frame.width.

Mehul
quelle
3

Mein Code:

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

Wird richtig funktionieren !!!

Кирилл Старосельский
quelle
2

Ich hatte auch ein Problem, aber dann wurde es gelöst mit:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionViewFlowLayoutSetup(with: view.bounds.size.width)
        collectionView?.collectionViewLayout.invalidateLayout()
        collectionViewFlowLayoutSetup(with: size.width)
    }

    fileprivate func collectionViewFlowLayoutSetup(with Width: CGFloat){

        if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: Width, height: 300)
        }

    }
user2698544
quelle
2

Ich löse dieses Problem, indem ich eine Benachrichtigung einstelle, wenn sich die Bildschirmausrichtung ändert, und eine Zelle neu lade, die die Elementgröße entsprechend der Bildschirmausrichtung festlegt und den Indexpfad auf die vorherige Zelle einstellt. Dies funktioniert auch mit Flowlayout. Hier ist der Code, den ich geschrieben habe:

var cellWidthInLandscape: CGFloat = 0 {
    didSet {
        self.collectionView.reloadData()
    }
}

var lastIndex: Int = 0

override func viewDidLoad() {
    super.viewDidLoad()

    collectionView.dataSource = self
    collectionView.delegate = self
    NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    cellWidthInLandscape = UIScreen.main.bounds.size.width

}
deinit {
    NotificationCenter.default.removeObserver(self)
}
@objc func rotated() {

        // Setting new width on screen orientation change
        cellWidthInLandscape = UIScreen.main.bounds.size.width

       // Setting collectionView to previous indexpath
        collectionView.scrollToItem(at: IndexPath(item: lastIndex, section: 0), at: .right, animated: false)
}
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

   // Getting last contentOffset to calculate last index of collectionViewCell
    lastIndex = Int(scrollView.contentOffset.x / collectionView.bounds.width)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // Setting new width of collectionView Cell
        return CGSize(width: cellWidthInLandscape, height: collectionView.bounds.size.height)

}
Yubraj Basyal
quelle
0

Ich habe das Problem mit der folgenden Methode gelöst

override func viewDidLayoutSubviews() {
        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            collectionView.collectionViewLayout.invalidateLayout()
            collectionView.collectionViewLayout = flowLayout
        }
    }
CrazyPro007
quelle