我在视图控制器中有一个简单的
UICollectionView
。我通过按钮对集合视图的顶部约束进行动画处理。点击第一个按钮后,集合视图单元格的动画效果非常奇怪。后续点击后,动画变得流畅。
制作动画的方法:
@objc func animateAction() {
UIView.animate(withDuration: 1) {
self.animateUp.toggle()
self.topConstraint.constant = self.animateUp ? 100 : self.view.bounds.height - 100
self.view.layoutIfNeeded()
}
}
编辑:实际需要构建什么:
看起来您正在对集合视图的顶部约束进行动画处理,这会更改其高度。
集合视图仅在需要时渲染单元格。
因此,一开始只创建一个(或两个)单元格。然后,当您更改高度时,将创建并添加新单元格。所以,你会看到一个“奇怪的动画”。
您想要做的是NOT为您的集合视图设置底部约束。相反,设置其高度约束,然后更改顶部约束以使其上下“滑动”:
我假设您正在使用
UICollectionViewCompositionalLayout.list
和 appearance: .insetGrouped
...
这是获得该结果的完整示例:
struct MyCVData: Hashable {
var name: String
}
class AnimCVViewController: UIViewController {
var myCollectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<Section, MyCVData>!
var cvDataList: [MyCVData] = []
enum Section {
case main
}
var snapshot: NSDiffableDataSourceSnapshot<Section, MyCVData>!
var topConstraint: NSLayoutConstraint!
// when collection view is "Up" we want its
// Top to be 100-points from the Top of the view (safe area)
var topPosition: CGFloat = 100
// when collection view is "Down" we want its
// Top to be 80-points from the Bottom of the view (safe area)
var bottomPosition: CGFloat = 80
override func viewDidLoad() {
super.viewDidLoad()
// so we have a title if we're in a navigation controller
self.navigationController?.setNavigationBarHidden(true, animated: false)
view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
configureCollectionView()
buildData()
// create an Animate button
let btn = UIButton()
btn.backgroundColor = .yellow
btn.setTitle("Animate", for: [])
btn.setTitleColor(.black, for: .normal)
btn.setTitleColor(.lightGray, for: .highlighted)
btn.translatesAutoresizingMaskIntoConstraints = false
myCollectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btn)
view.addSubview(myCollectionView)
let g = view.safeAreaLayoutGuide
// start with the collection view "Down"
topConstraint = myCollectionView.topAnchor.constraint(equalTo: g.bottomAnchor, constant: -bottomPosition)
NSLayoutConstraint.activate([
// constrain the button at the Top, 200-pts width, centered horizontally
btn.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
btn.widthAnchor.constraint(equalToConstant: 200.0),
btn.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// button Height 10-points less than our collection view's Top Position
btn.heightAnchor.constraint(equalToConstant: topPosition - 10.0),
// activate top constraint
topConstraint,
// collection view Height should be the Height of the view (safe area)
// minus the Top Position
myCollectionView.heightAnchor.constraint(equalTo: g.heightAnchor, constant: -topPosition),
// let's use 40-points leading and trailing
myCollectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
myCollectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
])
// add an action for the button
btn.addTarget(self, action: #selector(animateAction), for: .touchUpInside)
}
@objc func animateAction() {
// if the topConstraint constant is -bottomPosition, that means it is "Down"
// so, if it's "Down"
// animate it so its Top is its own Height from the Bottom
// otherwise
// animate it so its Top is at bottomPosition
topConstraint.constant = topConstraint.constant == -bottomPosition ? -myCollectionView.frame.height : -bottomPosition
UIView.animate(withDuration: 1.0, animations: {
self.view.layoutIfNeeded()
})
}
func configureCollectionView() {
var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
layoutConfig.backgroundColor = .red
let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
myCollectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout)
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, MyCVData> { (cell, indexPath, item) in
var content = UIListContentConfiguration.cell()
content.text = item.name
content.textProperties.font.withSize(8.0)
content.textProperties.font = UIFont.preferredFont(forTextStyle: .body)
content.textProperties.adjustsFontSizeToFitWidth = false
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource<Section, MyCVData>(collectionView: myCollectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: MyCVData) -> UICollectionViewCell? in
// Dequeue reusable cell using cell registration (Reuse identifier no longer needed)
let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
for: indexPath,
item: identifier)
return cell
}
}
func buildData() {
// create 20 data items ("Cell: 1" / "Cell: 2" / "Cell: 3" / etc...)
for i in 0..<20 {
let d = MyCVData(name: "Cell: \(i)")
cvDataList.append(d)
}
// Create a snapshot that define the current state of data source's data
self.snapshot = NSDiffableDataSourceSnapshot<Section, MyCVData>()
self.snapshot.appendSections([.main])
self.snapshot.appendItems(cvDataList, toSection: .main)
// Display data in the collection view by applying the snapshot to data source
self.dataSource.apply(self.snapshot, animatingDifferences: false)
}
}
我遇到了这个问题,这对我有帮助。 需要在 collectionView(_:cellForItemAt:) 的末尾添加这一行
UIView.performWithoutAnimation {
cell.layoutIfNeeded()
}
return cell