不使用reload方法调整UICollectionView单元格大小

13

如何在不使用reload方法的情况下调整UICollectionView的单元格大小?我只想在某些事件上调用以下方法。我不想重新加载UICollectionView或其任何部分或行。

   func collectionView(collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                           sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize 
3个回答

55

我用下面这行代码找到了解决我的问题的方法。

collectionView?.collectionViewLayout.invalidateLayout()

请注意:若要对此进行动画调整大小,请使用 UIView.animate

let duration: TimeInterval = 0.3
UIView.animate(withDuration: duration, animations: {
    collectionView.collectionViewLayout.invalidateLayout() 
})

1
我该如何使用动画来实现它? - אורי orihpt
1
那似乎也会导致内容偏移量被设置为零?有什么办法可以防止这种情况发生吗? - Georg
太棒了!谢谢。 - Vassily

1
我可以确认Payal Maniyar的解决方案同样适用于UICollectionCompositional布局。 在我的情况下,我有一个CollectionViewCell,它只嵌入了一个UITextView。组成布局正确地确定了单元格中文本视图的初始大小。(附注:这是通过在UITextView中禁用滚动来实现的,因为它已经在另一个可滚动的视图中)。问题是,当用户直接在文本视图中键入时,我想要更改单元格的高度而不重新加载它。我希望避免重新加载单元格,原因有两个。a)重新加载的单元格在用户键入时会闪烁,b)文本视图失去焦点;从而迫使用户再次点击文本视图以继续输入。所以我的解决方案如下:
  1. I have a cell with only UITextView inside of it where the cell is also the UITextView's delegate as such:

    class MyCollectionCell: UICollectionViewCell {
      @IBOutlet private weak var myTextView: UITextView!
      //this local variable used to determine if the intrinsic content size has changed or not
      private var textViewHeight: CGFloat = .zero
      ...
      ...
      override func awakeFromNib() {
       super.awakeFromNib()
       myTextView.delegate = self
      }
      ...
      ...
    }
    
  2. Conform this cell to UITextViewDelegate

    extension MyCollectionCell: UITextViewDelegate { 
    
    func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
      //this assumes that collection view already correctly laid out the cell
      //to the correct height for the contents of the UITextView
      //textViewHeight simply needs to catch up to it before user starts typing
      let fittingSize = textView.sizeThatFits(CGSize(width: myTextView.frame.width, height: CGFloat.greatestFiniteMagnitude))
      textViewHeight = fittingSize.height
    
      return true
    }
    
    func textViewDidChange(_ textView: UITextView) {
      //flag to determine whether textview's size is changing
      var shouldResize = false
      //calculate fitting size after the content has changed
      let fittingSize = textView.sizeThatFits(CGSize(width: myTextView.frame.width, height: CGFloat.greatestFiniteMagnitude))
    
      //if the current height is not equal to 
      if textViewHeight != fittingSize.height {
        shouldResize = true
        //save the new height 
        textViewHeight = fittingSize.height
      }
    
      //notify the cell's delegate (most likely a UIViewController) 
      //that UITextView's intrinsic content size has changed
      //perhaps with a protocol such as this:
      delegate?.textViewDidChange(newText: textView.text, alsoRequiresResize: shouldResize)
    }
    
  3. Then when the delegate receives the notification, perhaps saves the updated text and updates the layout such as this:

    myCollectionView.collectionViewLayout.invalidateLayout()
    
这个解决方案最好的部分是您无需调用collectionView(_:layout:sizeForItemAt:)来调整单元格大小,因为您一开始就没有使用笨重的FlowLayout。
干杯!

1
我也在使用组合布局,但不幸的是这对我来说并不完美。当我使布局无效时,滚动位置会发生变化。我甚至尝试了使特定部分无效,但这对我也没有起作用 :( - Nikhil Muskur
关于组合布局,看起来 dataSource.apply(snapshot, animated: animated) 应该足以调用而不是 invalidateLayout() - goodliving

0
我正在UICollectionViewCell内显示视频。我发现使用UIView.animate更新高度并添加淡入淡出动画时无法正常工作。下面的代码完美运行。
collectionView.performBatchUpdates({
    collectionView.collectionViewLayout.invalidateLayout()
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接