UICollectionView:使用动画和自定义布局调整单元格大小
UICollectionView: Resizing cells with animations and custom layouts
我有一个带有自定义布局和可区分数据源的集合视图。我想用动画调整单元格的大小。
使用自定义布局: 看起来单元格的内容在动画期间缩放到新的大小。只有当单元格达到最终大小时,才最终重绘(见下面的动画)
使用股票流布局 子视图在动画期间正确布局:标签保留在顶部,没有调整大小,开关也一样。
所以我假设问题出在布局实现中,可以将什么添加到布局中以允许在动画期间进行正确的布局?
描述
这个例子中使用的自定义布局是FSQCollectionViewAlignedLayout,我也是用我在代码中使用的自定义得到的。我注意到在某些情况下,始终 return 相同的布局属性对象很重要,而 FSQCollectionViewAlignedLayout
则不然。我修改为不return份,结果还是一样
对于单元格(在 xib 中定义),单元格的 contentMode
及其 contentView
设置为 .top
。该单元格的 contentView
包含 2 个子视图:一个标签(布局在 layoutSubviews
中)和一个开关(布局有约束,但它的存在对动画没有影响)。
控制器代码为:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
enum Section {
case main
}
@IBOutlet weak var collectionView : UICollectionView!
var layout = FSQCollectionViewAlignedLayout()
var dataSource : UICollectionViewDiffableDataSource<Section, String>!
var cellHeight : CGFloat = 100
override func loadView() {
super.loadView()
layout.defaultCellSize = CGSize(width: 150, height: 100)
collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { cv, indexPath, item in
guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else { return nil }
cell.label.text = item
return cell
})
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(["1", "2"], toSection: .main)
dataSource.apply(snapshot)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: cellHeight)
}
@IBAction func resizeAction(_ sender: Any) {
if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout {
UIView.animate(withDuration: 0.25) {
layout.defaultCellSize.height += 50
let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
UIView.animate(withDuration: 0.25) {
self.cellHeight += 50
let invalidation = UICollectionViewFlowLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
}
}
class Cell : UICollectionViewCell {
@IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
}
}
我在这个问题上花了开发者支持票。答案是不支持重新加载单元格时的自定义动画。
因此,作为一种解决方法,我最终创建了新的单元格实例,并将其添加到视图层次结构中,然后添加自动布局约束,以便它们与正在调整大小的单元格完全重叠。然后动画在那些新添加的单元格上 运行。在动画结束时,这些单元格被删除。
我有一个带有自定义布局和可区分数据源的集合视图。我想用动画调整单元格的大小。
使用自定义布局: 看起来单元格的内容在动画期间缩放到新的大小。只有当单元格达到最终大小时,才最终重绘(见下面的动画)
使用股票流布局 子视图在动画期间正确布局:标签保留在顶部,没有调整大小,开关也一样。
所以我假设问题出在布局实现中,可以将什么添加到布局中以允许在动画期间进行正确的布局?
描述
这个例子中使用的自定义布局是FSQCollectionViewAlignedLayout,我也是用我在代码中使用的自定义得到的。我注意到在某些情况下,始终 return 相同的布局属性对象很重要,而 FSQCollectionViewAlignedLayout
则不然。我修改为不return份,结果还是一样
对于单元格(在 xib 中定义),单元格的 contentMode
及其 contentView
设置为 .top
。该单元格的 contentView
包含 2 个子视图:一个标签(布局在 layoutSubviews
中)和一个开关(布局有约束,但它的存在对动画没有影响)。
控制器代码为:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
enum Section {
case main
}
@IBOutlet weak var collectionView : UICollectionView!
var layout = FSQCollectionViewAlignedLayout()
var dataSource : UICollectionViewDiffableDataSource<Section, String>!
var cellHeight : CGFloat = 100
override func loadView() {
super.loadView()
layout.defaultCellSize = CGSize(width: 150, height: 100)
collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { cv, indexPath, item in
guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else { return nil }
cell.label.text = item
return cell
})
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(["1", "2"], toSection: .main)
dataSource.apply(snapshot)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: cellHeight)
}
@IBAction func resizeAction(_ sender: Any) {
if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout {
UIView.animate(withDuration: 0.25) {
layout.defaultCellSize.height += 50
let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
UIView.animate(withDuration: 0.25) {
self.cellHeight += 50
let invalidation = UICollectionViewFlowLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
}
}
class Cell : UICollectionViewCell {
@IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
}
}
我在这个问题上花了开发者支持票。答案是不支持重新加载单元格时的自定义动画。
因此,作为一种解决方法,我最终创建了新的单元格实例,并将其添加到视图层次结构中,然后添加自动布局约束,以便它们与正在调整大小的单元格完全重叠。然后动画在那些新添加的单元格上 运行。在动画结束时,这些单元格被删除。