title: Why does my Layout constraint on a cells contentView not animate?
body: <p>I try to change the size of a UICollectionViewCells contentView with Auto Layout constraints and it works but it can't get it to animate. It just jumps form one constraint to another.</p>
  <p><a href="https://i.stack.imgur.com/NsjPe.gif" rel="nofollow noreferrer">gif - jumps but does not animate</a></p>
  <p>I learned that to animate constraint changes you usually call the following but when I apply that to the contentView it has no effect.</p>
  <pre class="lang-swift prettyprint-override"><code>// This is how you usually animate constraint changes
  myFunctionThatChangesMyConstraints()
  
  UIView.animate(withDuration: 2) {
      layoutIfNeeded()
  }
  </code></pre>
  <p>I am able to animate the cell height by changing the cell constraints and reapplying a snapshot. I also tried that for the contentView but it has no effect.</p>
  <pre class="lang-swift prettyprint-override"><code>// This works well for cell height but has no effect for contentView height
  myFunctionThatChangesMyConstraints()
  
  UIView.animate(withDuration: 2) {
      self.dataSource.apply(self.dataSource.snapshot(), animatingDifferences: true)
  }
  </code></pre>
  <p><strong>Can someone explain to me what I'm missing or why it does not work?</strong></p>
  <h1></h1>
  <p>Here is a minimal working example of the code I use:</p>
  <pre class="lang-swift prettyprint-override"><code>
  class CollectionViewController: UIViewController {
      
      enum Section {
          case main
      }
      
      private var layout: UICollectionViewCompositionalLayout!
      private var collectionView: UICollectionView!
      private var dataSource: UICollectionViewDiffableDataSource&lt;Section, Int&gt;!
      
      override func loadView() {
          createLayout()
          createCollectionView()
          createDataSource()
          view = collectionView
      }
      
  }
  
  
  // MARK: - Layout
  extension CollectionViewController {
      func createLayout() {
          let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(69))
          let Item = NSCollectionLayoutItem(layoutSize: itemSize)
          
          let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(300))
          let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [Item])
          
          let section = NSCollectionLayoutSection(group: group)
          
          layout = .init(section: section)
      }
      
  }
  
  // MARK: - CollectionView
  extension CollectionViewController {
      func createCollectionView() {
          collectionView = .init(frame: .zero, collectionViewLayout: layout)
          
          let doubleTapGestureRecognizer = DoubleTapGestureRecognizer()
          
          doubleTapGestureRecognizer.doubleTapAction = { [unowned self] touch, _ in
              let touchLocation = touch.location(in: collectionView)
              guard let touchedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return }
              let cell = collectionView.cellForItem(at: touchedIndexPath) as! MyCell
              
              cell.toggleContentHeightConstraint() {
                  UIView.animate(withDuration: 2) {
  //                    cell.layoutIfNeeded() // &lt;- no effect
  //                    self.dataSource.apply(self.dataSource.snapshot(), animatingDifferences: true) // &lt;- also no effect
                  }
              }
              
  //            cell.toggleCellHeightConstraint { // &lt;- works well
  //                UIView.animate(withDuration: 2) {
  //                    self.dataSource.apply(self.dataSource.snapshot(), animatingDifferences: true)
  //                }
  //            }
  
          }
          
          collectionView.addGestureRecognizer(doubleTapGestureRecognizer)
          
      }
      
  }
  
  // MARK: - DataSource
  extension CollectionViewController {
      func createDataSource() {
          let cellRegistration = UICollectionView.CellRegistration&lt;MyCell, Int&gt;() { cell, _, _ in
  
              cell.contentConfiguration = UIHostingConfiguration {
                  MyTextView()
              }
              .background(.orange)
              
              cell.applyConstraints()
              
          }
              
          dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
              return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
          }
  
          var initialSnapshot = NSDiffableDataSourceSnapshot&lt;Section, Int&gt;()
          initialSnapshot.appendSections([Section.main])
          initialSnapshot.appendItems([0, 1, 2])
          dataSource.applySnapshotUsingReloadData(initialSnapshot)
  
      }
      
  }
  
  class MyCell: UICollectionViewCell {
  
      var cellHeightConstraint: NSLayoutConstraint!
      var contentHeightConstraint: NSLayoutConstraint!
      
      var selfSizingConstraint: NSLayoutConstraint!
      
      func applyConstraints() {
          contentView.translatesAutoresizingMaskIntoConstraints = false
          
          cellHeightConstraint = heightAnchor.constraint(equalToConstant: 300)
          cellHeightConstraint.priority = .defaultHigh
          contentHeightConstraint = contentView.heightAnchor.constraint(equalToConstant: 150)
          contentHeightConstraint.priority = .required
          
          selfSizingConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
          selfSizingConstraint.priority = .defaultLow
          
          NSLayoutConstraint.activate([
              contentView.topAnchor.constraint(equalTo: topAnchor),
              contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
              contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
              selfSizingConstraint,
              contentHeightConstraint,
              cellHeightConstraint
          ])
      }
      
      func toggleContentHeightConstraint(animation: @escaping () -&gt; Void) {
          print(&quot;toggle contentHeightConstraint&quot;)
          self.contentHeightConstraint.isActive = !self.contentHeightConstraint.isActive // &lt;- the change that does not animate
          print(&quot;now it's \(contentHeightConstraint.isActive)&quot;)
          
          animation()
      }
      
      func toggleCellHeightConstraint(animation: @escaping () -&gt; Void) {
          print(&quot;toggle cellHeightConstraint&quot;)
          cellHeightConstraint.isActive = !cellHeightConstraint.isActive
          print(&quot;now it's \(cellHeightConstraint.isActive)&quot;)
          
          animation()
      }
      
  }
  
  class DoubleTapGestureRecognizer: UITapGestureRecognizer {
      var doubleTapAction: ((UITouch, UIEvent) -&gt; Void)?
  
      override func touchesBegan(_ touches: Set&lt;UITouch&gt;, with event: UIEvent) {
          if touches.first!.tapCount == 2 {
              self.doubleTapAction?(touches.first!, event)
          }
      }
      
  }
  
  struct MyTextView: View {
      
      var body: some View {
          Text(&quot;Hello World! Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum&quot;)
              .background(.green)
      }
      
  }
  
  </code></pre>