almas
almas

Reputation: 7187

UICollectionViewCell - different layout for different size classes

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:

The label has following constraints:

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!

Upvotes: 3

Views: 2396

Answers (2)

Jan Gorman
Jan Gorman

Reputation: 1006

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

Upvotes: 3

Dallas Johnson
Dallas Johnson

Reputation: 1536

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

Upvotes: 1

Related Questions