uicollectionviewautolayoutuiinterfaceorientationuitraitcollection

Swift 5 UICollectionViewCell Autolayout constraints


I am working on remastering Swift and UIKit after a long time.

WorkFlow:

  1. I have created a ViewController with a UICollectionView aligning its centers in both X and Y axis and Proportional Width and Height to Root View.
  2. Assigned to collectionview.delegate and collectionView.dataSource it appropriate class instance corresponding to UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
  3. I have created a UICollectionViewCell and called dequeuereusablecell... in cellforItemAt...
  4. I have access to sizeForItemAt... to calculate appropriate size
    // item count represents number of cells per row in a section within collectionview
    let refWidth = collectionView.frame.width / CGFloat(itemCount); // sectionInsets are UIEdgeInsets.zero for simplicity in reproducing the issue
    let calculatedSize = CGSize(width: refWidth, height: refWidth/2 )
    
  5. Implemented traitCollectionDidChange in ViewController and forced reloadData on collectionView
  6. I have been testing with many Devices (Simulators to be exact) it works for most cases

Scenario:

  1. If both the horizontal and vertical Size classes are exactly same - Example: iPad Mini
  2. ItemCount is 1
  3. Page is first opened in Landscape
  4. Then the Page is rotated into Portrait

Issue:

  1. Collection Does not Reload as the traitcollection has not changed.
  2. Current width of the Cell is greater than the current width of CollectionView

Few of Approaches I tried:

  1. In cellForItemAt... method, before returning the cell, I have added a constraint to cell with CollectionView as follows - `Applicatin Crashed with a message that such a constraint cannot be applied.
        let itemCount = itemsPerRowInEachSection(collectionView)
        if cell.contentView.constraints.first(where: { cst in cst.identifier == "widthConst" }) != nil {
            let newConst = NSLayoutConstraint(item: cell,
                                              attribute: .width,
                                              relatedBy: .equal,
                                              toItem: collectionView,
                                              attribute: .width,
                                              multiplier: CGFloat(1)/CGFloat(itemCount),
                                              constant: 0)
            newConst.identifier = "widthConst"
            cell.addConstraint(newConst)
        }
    
  2. Similar to first approach but tried applying constraint in interfacebuilder. There was no way to do that as command click and drag between the CollectionView and CollectionViewCell does not prompt any constraints at all

Question:

  1. Should I implement somehthing like a orientation change listener and reload the collectionView there instead of trait collection change listener?
    • If I go for orientation Listener, In future if I work on, multi tasking support, If I change traitCollection withut changing the Orientation, this approachdoes not work.
    • If I implement both traitCollection and Orientation Change Listeners, lot of times they collide and will attempt to reload collectionView twice.
  2. Can someone hep me with finding appropriate solution for this issue?

Solution

  • As per OP comment...

    If you need to modify layout based on the size of the view - such as on device rotation - you can use:

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

    From Apple's docs:

    UIKit calls this method before changing the size of a presented view controller’s view. You can override this method in your own objects and use it to perform additional tasks related to the size change. For example, a container view controller might use this method to override the traits of its embedded child view controllers. Use the provided coordinator object to animate any changes you make.