UICollectionView 单元格展开动画问题
Posted
技术标签:
【中文标题】UICollectionView 单元格展开动画问题【英文标题】:UICollectionView Cell Expansion Animation Problem 【发布时间】:2020-05-19 20:12:49 【问题描述】:我遇到了一个问题,我的 UICollectionView 使用 UICollectionViewCompositionalLayout + Diffable 数据源不允许我平滑地为垂直单元格展开动画。我有一列类似于 UITableView 的全角单元格,我正在尝试扩展单元格以在单元格选择时显示截断的文本。
这在没有动画的情况下可以正常工作,但是当我尝试对其进行动画处理时,如果扩展单元格下方的单元格最终位置超出屏幕边界,我无法让它看起来流畅,因为单元格会立即出现消失而不是被动画向下“推”。这个问题可以在链接的gif中看到https://imgur.com/a/ulj0p6n
如果扩展单元格下方的单元格的最终位置在屏幕边界内,则不会出现此问题,如下所示:https://imgur.com/a/uJ2RRRn。
我发现了一个似乎有类似问题的旧问题:UICollectionView animating cell size change causes undesired behavior
那里的答案很老,对我没有帮助,因为我没有使用 Flow Layout,也没有使用 Obj-C。
我看到许多应用程序(例如 Instagram,当图像标题具有“查看更多”时)使用 UICollectionView 实现了预期的结果而没有任何动画问题,所以我认为这一定是可能的。
【问题讨论】:
【参考方案1】:如果不查看任何代码就很难诊断您的问题,因此这里有一个示例,证明使用UICollectionViewCompositionalLayout
和UICollectionViewDiffableDataSource
可以实现具有平滑动画的垂直扩展单元格。
工作原理
有两个类值得注意:
Cell
:这是垂直扩展的UICollectionViewCell
。
ViewController
:这是配置集合视图、数据源等的UIViewController
。
当通过collectionView(_:didSelectItemAt:)
选择Cell
时,items
数组中对应项的isExpanded
属性将切换,并且通过updateSnapshot()
更新数据源的快照:
代码
使用 Single View App 模板创建一个新的 Xcode 项目,并将此代码放入 ViewController.swift
文件中:
import UIKit
// MARK: - Cell -
final class Cell: UICollectionViewCell
static let reuseIdentifier = "Cell"
var isExpanded = false
didSet label.numberOfLines = numberOfLines
var numberOfLines: Int isExpanded ? 0 : 3
lazy var label: UILabel =
let label = UILabel()
label.numberOfLines = numberOfLines
label.frame.size = contentView.bounds.size
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return label
()
override init(frame: CGRect)
super.init(frame: frame)
contentView.addSubview(label)
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func sizeThatFits(_ size: CGSize) -> CGSize
label.sizeThatFits(size)
// MARK: - UIViewController -
class ViewController: UIViewController
struct Item: Hashable
let text: String
var isExpanded = false
private let uuid = UUID()
var items: [Item] = [
.init(
text: """
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.
"""
),
.init(
text: """
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.
""",
isExpanded: true
)
]
lazy var collectionView: UICollectionView =
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout())
collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.contentInset.top = 44
collectionView.backgroundColor = .white
collectionView.delegate = self
return collectionView
()
lazy var dataSource = UICollectionViewDiffableDataSource<Int, Item>(collectionView: collectionView) collectionView, indexPath, itemIdentifier in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else fatalError()
cell.isExpanded = itemIdentifier.isExpanded
cell.label.text = itemIdentifier.text
return cell
override func viewDidLoad()
super.viewDidLoad()
view.addSubview(collectionView)
updateSnapshot()
private func createCollectionViewLayout() -> UICollectionViewCompositionalLayout
let layoutSize = NSCollectionLayoutSize.init(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(200)
)
let section = NSCollectionLayoutSection(group:
.vertical(
layoutSize: layoutSize,
subitems: [.init(layoutSize: layoutSize)]
)
)
section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
section.interGroupSpacing = 20
return .init(section: section)
private func updateSnapshot()
var snapshot = NSDiffableDataSourceSnapshot<Int, Item>()
snapshot.appendSections([0])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: true)
// MARK: - UICollectionViewDelegate -
extension ViewController: UICollectionViewDelegate
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
guard let itemIdentifier = dataSource.itemIdentifier(for: indexPath) else return
items[indexPath.row] = .init(text: itemIdentifier.text, isExpanded: !itemIdentifier.isExpanded)
updateSnapshot()
【讨论】:
感谢您提供出色的答案和示例,我能够使用它达到我想要的结果。我还有一个问题,是否可以调整 diffable 动画?就我现在的情况而言,扩展单元格下方的内容有点太慢了,无法移开,并且在动画过程中瞬间出现了一些难看的重叠。 不客气!我认为使用apply(_:animatingDifferences:completion:)
时无法提供自定义动画。
@JWK 谢谢你的例子,但你知道为什么boundarySupplementaryItems
在折叠/展开动画中被隐藏了吗?
@Carsten 我在这里回答了你的问题:***.com/questions/64790617/…以上是关于UICollectionView 单元格展开动画问题的主要内容,如果未能解决你的问题,请参考以下文章
iOS:使用约束动画 UICollectionView 垂直扩展?