3
votes

I have a simple UICollectionViewCell with one image and a label. In regular-regular size class I want the image to be on top, and the label below it. This is what it looks like in the Xcode's preview tab:

enter image description here

In any other size class I want the image to be on the left, and the label on the right:

enter image description here

I setup the constraints this way:

ImageView has following constraints:

  • leading and top constraints for Any-Any
  • fixed width and height for Any-Any

The label has following constraints:

  • trailing constraint to Superview - for Any-Any
  • top constraint to the ImageView - only for Regular-Regular
  • leading constraint to Superview - only for Regular-Regular
  • top constraint to Superview - for Any-Any but not for Regular-Regular
  • leading constraint to ImageView - for Any-Any but not for Regular-Regular

I also implemented the method to return the different cell size based on the current trait collection:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    if (traitCollection.horizontalSizeClass == .Regular) {
      return CGSizeMake(240, 194)
    }
    else {
      return CGSizeMake(340, 128)
    }
  }

The cell looks fine in the preview, and everything works fine when I run it on iPhone (e.g Compact-Regular). However, auto layout breaks when I run it on iPad:

enter image description here

And of course I get a bunch of warnings in the debug console: Unable to simultaneously satisfy constraints.

So, I guess the question is - what is the proper way of setting up the cell for different size classes?

I created a github repo with a demo project

Thanks!

2
if you support only iOS 9 and later, you can do that by using stackViewHamzaGhazouani

2 Answers

3
votes

What you want to do is disable those constraints for the Regular-Regular size class like you have, but also change their priority to < 1000 (i.e. not required). That way it will instead use the higher priority constraints for the specific size class.

enter image description here

1
votes

This may not be the most elegant solution but my attempts to do this in the past have led to more pain with Autolayout size classes and I found the best solution was:

  1. Have a separate nib for for each size class and then layout out the views in each as needed.
  2. Have separate CollectionViewFlowLayouts for each size class and then transition between them when the size changes.

Following this pattern the cell size would be determined by the collectionViewFlowlayout rather than sizeForItem... and you will have more control over other parameters such as sectionInset and minimumLineSpacing etc. which often need varying when changing size classes in a collection view.

Here are the code changes that worked for me:

  let cellIdentifierCompact = "CustomCellCompact"
  let cellIdentifierRegular = "CustomCellReg"

  lazy var compactLayout: UICollectionViewFlowLayout = {
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSizeMake(240, 194)
    return layout
  }()

lazy var regLayout: UICollectionViewFlowLayout = {
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSizeMake(320, 128)
    return layout
  }()

override func viewDidLoad() {
    super.viewDidLoad()

    let customCellNib = UINib(nibName: cellIdentifierCompact, bundle: nil)
    self.collectionView.registerNib(customCellNib, forCellWithReuseIdentifier: cellIdentifierCompact)

    let customCellNibReg = UINib(nibName: cellIdentifierRegular, bundle: nil)
    self.collectionView.registerNib(customCellNibReg, forCellWithReuseIdentifier: cellIdentifierRegular)
    self.configureLayoutForSize()
  }


func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cellId = self.collectionView.collectionViewLayout == self.regLayout ? cellIdentifierRegular : cellIdentifierCompact
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath)
    return cell
  }

  func configureLayoutForSize(){
    switch self.traitCollection.horizontalSizeClass{
    case UIUserInterfaceSizeClass.Regular:
      self.collectionView.setCollectionViewLayout(self.regLayout, animated: false)
    case .Compact, .Unspecified:
      self.collectionView.setCollectionViewLayout(self.compactLayout, animated: false)
    }
  }

  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    self.configureLayoutForSize()
    self.collectionView.reloadData()
  }