title: Autosizing Cells in UICollectionView (all in code)
body: <p>Using Swift-5.0, Xcode-10.2, iOS-12.2,</p>
  
  <p>There is an issue in my code while trying to achieve "autosizing" of cells in a UICollectionView (using UICollectionViewFlowLayout as its layout).</p>
  
  <p>The height of the cell is content-based (and unknown upfront) - therefore I try to get the <strong>cell's height to autosize</strong> (the "width" I don't care for now and can, for example, be set to <code>frame.width</code>).</p>
  
  <p>Even tough, I only use one large UICollectionView-Cell for the example below, I would still like to keep "dequeueing" of the cells alive since later on, there will be many more cells that need to be filled with large content. Therefore, to make this example more simple, I keept the <code>numberOfItemsInSection</code> at 1.</p>
  
  <p>Moreover, for the below example, each custom CollectionViewCell is filled with a vertical StackView (vertically adding-up a couple of Labels and ImageViews). The StackView is not important here, but I made it as a quick example. Again, the content of the custom CollectionViewCell will change later on (especially it will change to a content-dependent height). The fixed-height cell-content in the code below is just for example reasons... </p>
  
  <p>But what I would still like to get out of this, is the question on how to make the CollectionViewCell autosize its height according to the content (whether fixed-height like in the below example or dynamic content-based-height like in the future implementation) ???</p>
  
  <p>I keep getting the following Constraint-error:</p>
  
  <pre class="lang-swift prettyprint-override"><code>Unable to simultaneously satisfy constraints.
  Probably at least one of the constraints in the following list is one
  you don't want. 
  Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
  </code></pre>
  
  <p>Everyting works, if I give the Cell a very large fixed height using the method <code>sizeForItemAt</code> inside my <code>UICollectionViewController</code>. (i.e. with "very large" I mean much bigger than the cell's height turns out when dequeueing of the cell takes place).</p>
  
  <p>But as explained above, I do not want to set the cell's height to a fixed value (using the UICollectionViewController's method <code>sizeForItemAt</code> - but rather achieve the desired autosizing.</p>
  
  <p>To autosize the cell, I tried the following:</p>
  
  <p><strong>Approach A)</strong> </p>
  
  <p>Use <code>collectionViewFlowLayout.estimatedItemSize = CGSize(...)</code></p>
  
  <p>(and don't use <code>sizeForItemAt</code> method)</p>
  
  <p>--> Again same thing: If the estimated-size is set large enough, no error occurs. If I set it too small, then I get the same Constraint-error...</p>
  
  <p><strong>Approach B)</strong></p>
  
  <p>Use <code>collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize</code></p>
  
  <p>(and use or don't use <code>sizeForItemAt</code> method - does not make a difference)</p>
  
  <p>--> Again same error: As soon as I start scrolling on the UICollectionView-cell, it throws the same Constraint-error...</p>
  
  <p>Somewhat promising is the observation that using <code>UICollectionViewFlowLayout.automaticSize</code> makes the App work as desired (except that the above error is still thrown - but strange-enough, the App continues somehow to run anyway). For me it is not acceptable that the App works and an error is thrown. The question is, how to get rid of the Constraint-error ??</p>
  
  <p>Here is all the code that I use:</p>
  
  <pre class="lang-swift prettyprint-override"><code>class TestViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
  
      let cellId = "cellID"
  
      override func viewDidLoad() {
          super.viewDidLoad()
          collectionView.backgroundColor = .yellow
          collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
      }
  
      override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {
          return 1
      }
  
      override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&gt; UICollectionViewCell {
          let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
          return cell
      }
  
  //    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&gt; CGSize {
  //        return .init(width: view.frame.width, height: 4000)
  //    }
  
      init() {
          let collectionViewFlowLayout = UICollectionViewFlowLayout()
  //        collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 4000)
          collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
          super.init(collectionViewLayout: collectionViewFlowLayout)
      }
  
      required init?(coder aDecoder: NSCoder) {
          fatalError("init(coder:) has not been implemented")
      }   
  }
  </code></pre>
  
  <p>Here is the CustomCollectionViewCell:</p>
  
  <pre class="lang-swift prettyprint-override"><code>import UIKit
  
  class MyCollectionViewCell: UICollectionViewCell {
  
      let titleLabel1 = UILabel(text: "Title 123", font: .boldSystemFont(ofSize: 30))
      let titleLabel2 = UILabel(text: "Testing...", font: .boldSystemFont(ofSize: 15))
      let imgView1 = UIImageView(image: nil)
      let imgView2 = UIImageView(image: nil)
      let imgView3 = UIImageView(image: nil)
      let imgView4 = UIImageView(image: nil)
      let imgView5 = UIImageView(image: nil)
  
  
      let imageView: UIImageView = {
          let imgView = UIImageView()
  
          imgView.backgroundColor = .green
          return imgView
      }()
  
      override init(frame: CGRect) {
          super.init(frame: frame)
  
          backgroundColor = .yellow
          titleLabel1.constrainHeight(constant: 50)
          titleLabel1.constrainWidth(constant: 250)
          titleLabel2.constrainHeight(constant: 50)
          titleLabel2.constrainWidth(constant: 250)
  
          imgView1.constrainHeight(constant: 100)
          imgView1.constrainWidth(constant: 200)
          imgView1.backgroundColor = .green
          imgView2.constrainHeight(constant: 100)
          imgView2.constrainWidth(constant: 200)
          imgView2.backgroundColor = .green
          imgView3.constrainHeight(constant: 100)
          imgView3.constrainWidth(constant: 200)
          imgView3.backgroundColor = .green
          imgView4.constrainHeight(constant: 100)
          imgView4.constrainWidth(constant: 200)
          imgView4.backgroundColor = .green
          imgView5.constrainHeight(constant: 100)
          imgView5.constrainWidth(constant: 200)
          imgView5.backgroundColor = .green
  
          let stackView = UIStackView(arrangedSubviews: [titleLabel1, imgView1, titleLabel2, imgView2, imgView3, imgView4, imgView5])
  
          addSubview(stackView)
          stackView.anchor(top: safeAreaLayoutGuide.topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)
          stackView.spacing = 20
          stackView.axis = .vertical
          stackView.alignment = .center
      }
  
      required init?(coder aDecoder: NSCoder) {
          fatalError("init(coder:) has not been implemented")
      }
  }
  </code></pre>
  
  <p>Needed extensions are defined as such:</p>
  
  <pre class="lang-swift prettyprint-override"><code>extension UILabel {
      convenience init(text: String, font: UIFont) {
          self.init(frame: .zero)
          self.text = text
          self.font = font
          self.backgroundColor = .red
      }
  }
  
  extension UIImageView {
      convenience init(cornerRadius: CGFloat) {
          self.init(image: nil)
          self.layer.cornerRadius = cornerRadius
          self.clipsToBounds = true
          self.contentMode = .scaleAspectFill
      }
  }
  </code></pre>
  
  <p>To abstract away the anchoring and height-definitions of the autolayout-constraints of the cell's components (i.e. a bunch of labels and imageViews that fill the cell), I used the following UIView extension...:</p>
  
  <p>(The extension has been published by <a href="https://twitter.com/buildthatapp" rel="nofollow noreferrer">Brian Voong</a> - see <a href="https://youtu.be/iqpAP7s3b-8" rel="nofollow noreferrer">video link</a>)</p>
  
  <pre class="lang-swift prettyprint-override"><code>// Reference Video: https://youtu.be/iqpAP7s3b-8
  extension UIView {
  
      @discardableResult
      func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -&gt; AnchoredConstraints {
  
          translatesAutoresizingMaskIntoConstraints = false
          var anchoredConstraints = AnchoredConstraints()
  
          if let top = top {
              anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
          }
  
          if let leading = leading {
              anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
          }
  
          if let bottom = bottom {
              anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
          }
  
          if let trailing = trailing {
              anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
          }
  
          if size.width != 0 {
              anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
          }
  
          if size.height != 0 {
              anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
          }
  
          [anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach{ $0?.isActive = true }
  
          return anchoredConstraints
      }
  
      func fillSuperview(padding: UIEdgeInsets = .zero) {
          translatesAutoresizingMaskIntoConstraints = false
          if let superviewTopAnchor = superview?.topAnchor {
              topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true
          }
  
          if let superviewBottomAnchor = superview?.bottomAnchor {
              bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true
          }
  
          if let superviewLeadingAnchor = superview?.leadingAnchor {
              leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true
          }
  
          if let superviewTrailingAnchor = superview?.trailingAnchor {
              trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true
          }
      }
  
      func centerInSuperview(size: CGSize = .zero) {
          translatesAutoresizingMaskIntoConstraints = false
          if let superviewCenterXAnchor = superview?.centerXAnchor {
              centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true
          }
  
          if let superviewCenterYAnchor = superview?.centerYAnchor {
              centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true
          }
  
          if size.width != 0 {
              widthAnchor.constraint(equalToConstant: size.width).isActive = true
          }
  
          if size.height != 0 {
              heightAnchor.constraint(equalToConstant: size.height).isActive = true
          }
      }
  
      func centerXInSuperview() {
          translatesAutoresizingMaskIntoConstraints = false
          if let superViewCenterXAnchor = superview?.centerXAnchor {
              centerXAnchor.constraint(equalTo: superViewCenterXAnchor).isActive = true
          }
      }
  
      func centerYInSuperview() {
          translatesAutoresizingMaskIntoConstraints = false
          if let centerY = superview?.centerYAnchor {
              centerYAnchor.constraint(equalTo: centerY).isActive = true
          }
      }
  
      func constrainWidth(constant: CGFloat) {
          translatesAutoresizingMaskIntoConstraints = false
          widthAnchor.constraint(equalToConstant: constant).isActive = true
      }
  
      func constrainHeight(constant: CGFloat) {
          translatesAutoresizingMaskIntoConstraints = false
          heightAnchor.constraint(equalToConstant: constant).isActive = true
      }
  }
  </code></pre>