0

I am using a UICollectionViewController to display a collection of UICollectionViewCell that a user can swipe between, on ios 13, swift 5, and UIKit. I ultimatelly want to re-draw the cell if the user has changed the orientation. In my controller, I have noticed that this piece of code gets executed on orientation change:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MyCell
    return cell
}

In MyCell, I am using this code to monitor then a layout is re-initialized:

override init(frame: CGRect) {
    super.init(frame: frame)
    setLayout()
    print("### orientation:: \(UIDevice.current.orientation.isLandscape) ")
}

Result: This method is called 2 times. When the app starts, and when I change from portrait to landscape. Any subsequent changes, do not call it. I suspect that this is fine, as maybe both versions are cached ( a landscape and a portrait ). If I add code to the setLayout() method based on the UIDevice.current.orientation.isLandscape flag, nothing changes while it executes. Note: The actual result of UIDevice.current.orientation.isLandscape is correct.

Here is an example of what is in my setLayout() method:

let topImageViewContainer = UIView();

topImageViewContainer.translatesAutoresizingMaskIntoConstraints = false
topImageViewContainer.backgroundColor = .yellow
addSubview(topImageViewContainer)
    
NSLayoutConstraint.activate([
    topImageViewContainer.topAnchor.constraint(equalTo: topAnchor),
    topImageViewContainer.leftAnchor.constraint(equalTo: leftAnchor),
    topImageViewContainer.rightAnchor.constraint(equalTo: rightAnchor),
    topImageViewContainer.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5)
])
if (UIDevice.current.orientation.isLandscape) {
    let imageView = UIImageView(image: UIImage(named: "photo_a"))
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    topImageViewContainer.addSubview(imageView)
} else {
    let imageView = UIImageView(image: UIImage(named: "photo_b"))
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    topImageViewContainer.addSubview(imageView)
}

In the example above, the code with "photo_a" is executing, but the ui is still showing photo_b. I suspect I am doing something wrong, and I need to somehow migrate towards an "Adaptive layout" as mentioned here: Different layouts in portrait and landscape mode . However, I am not sure how I could hook in some "viewWillTransition*" functions into a UICollectionViewCell that has a UIView.

How can I adjust my MyCell class, that implements UICollectionViewCell, to allow the cell to be updated when the user changes from landscape to protrait in a Swift intended way?

angryip
  • 2,140
  • 5
  • 33
  • 67
  • Implement `layoutSubviews` and look to see whether we are being called due to rotation since last time. “in a Swift intended way?” Irrelevant. Cocoa is Cocoa no matter what language you use. – matt Oct 19 '20 at 02:43
  • @matt I meant "Swift intended way" in more of "I am not hacking code to make it work, rather following best practice. && layoutSubviews executes multiple times on rotation change. – angryip Oct 19 '20 at 02:54

1 Answers1

0

Hi try to check if is landscape or portrait in cellForItem, my example: set colloectionView, flowLayaout and cell id:

class ViewController: UIViewController {

private var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
let cellId = "cellId"

now in viewDidLoad set collection view and layout parameters and the collection constraints:

layout.scrollDirection = .horizontal
    
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .clear
collectionView?.register(BottomCellTableViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.translatesAutoresizingMaskIntoConstraints = false
    
guard let collection = collectionView else { return }
    
view.addSubview(collection)
layout.itemSize = CGSize(width: view.bounds.size.width / 2, height: 200)
collection.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
collection.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
collection.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
collection.heightAnchor.constraint(equalToConstant: 200).isActive = true

after that conform to delegate and datasource in a separate extension (more clean code) and set in it numberOfItems and cellForRow:

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

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

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BottomCellTableViewCell
// check here device orientation
    if UIDevice.current.orientation.isLandscape {
        let image = UIImage(named: "meB")
        cell.imageV.image = image
        
    } else {
        let image = UIImage(named: "me")
        cell.imageV.image = image
    }
// reload the view and the collection
    DispatchQueue.main.async {
        self.collectionView?.reloadData()
    }
    return cell
 }
}

In collectionViewCell adding a subview directly to the cell, and pinning the image view to the cell, is wrong. You add it to a contentView and pin to it relative constraints:

class BottomCellTableViewCell: UICollectionViewCell {

let imageV: UIImageView = {
    let iv = UIImageView()
    iv.image = UIImage(named: "me")
    iv.contentMode = .scaleAspectFill
    iv.clipsToBounds = true
    iv.translatesAutoresizingMaskIntoConstraints = false
    
    return iv
}()

override init(frame: CGRect) {
    super.init(frame: frame)
    
    contentView.backgroundColor = .red
    
    contentView.addSubview(imageV)
    imageV.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    imageV.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
    imageV.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
    imageV.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
 }
}

This is the result:

enter image description here

Fabio
  • 5,432
  • 4
  • 22
  • 24
  • Can you comment on "In collectionViewCell adding a subview directly to the cell, and pinning the image view to the cell, is wrong" ? I am a bit confused why the view logic is meant to go into the controller, and not in the viewcell. – angryip Oct 19 '20 at 13:51
  • @angryip for the contentView you can read this https://developer.apple.com/documentation/uikit/uitableviewcell/1623229-contentview ... Add pin directly to cell is illegal. The logic go in the view because when change orientation you must reload collection view data to change the image – Fabio Oct 19 '20 at 13:58
  • ah, makes sense. the name of the object is just a bit misleading is all. This documentation also proves what you are mentioning. https://developer.apple.com/documentation/uikit/uicollectionviewcell . I'll try it out shortly and see how it behaves. thanks – angryip Oct 19 '20 at 14:13