在我当前的项目中,我有一个包含 2 个单元格的 collectionView。第一个单元格的宽度和高度等于 iPhone 屏幕的高度和宽度。在底部,我有一个带有 2 个图标的自定义导航栏。当我使用:
let point = CGPoint(x: screenWidth, y: 0)
collectionView.setContentOffset(point, animated: true)
动画效果很好,但对我来说有点太慢了。但是当我使用时:
UIView.animate(withDuration: 0.2, animations: {
self.collectionView.setContentOffset(point, animated: false)
})
然后第一个单元格不会像第一个动画那样移出屏幕(向左),而是立即消失,然后第二个单元格从右侧移入屏幕。
有人知道为什么第一个单元格消失了吗?
如果你只有 两个单元格,你可能会更好(出于几个原因)让它们在滚动视图中查看。
但是,如果您想坚持使用双单元集合视图...
当您告诉 UIKit 为
.contentOffset
变化设置动画时,UIKit 会找出在动画结束时需要显示什么。
因为你正在滚动一个单元格完全超出视野,UIKit 决定它不需要在动画期间管理那个单元格。
一种解决方案是将
.contentOffset.x
设置为一点比需要的少,然后在动画的完成块中“完成”定位。
所以...
如果我们要从单元格
0
到单元格1
(从右向左滑动):
x: screenWidth - 1.0
.contentOffset.x = screenWidth
完成如果我们要从单元格
1
到单元格0
(从左向右滑动):
x: 1.0
.contentOffset.x = 0.0
完成示例代码:
func animateToFirstCell() {
var point: CGPoint = .init(x: 1.0, y: 0.0)
UIView.animate(withDuration: 0.2, animations: {
self.collectionView.setContentOffset(point, animated: false)
}, completion: { _ in
point.x = 0.0
self.collectionView.setContentOffset(point, animated: false)
})
}
func animateToSecondCell() {
var point: CGPoint = .init(x: screenWidth - 1.0, y: 0.0)
UIView.animate(withDuration: 0.2, animations: {
self.collectionView.setContentOffset(point, animated: false)
}, completion: { _ in
point.x = self.screenWidth
self.collectionView.setContentOffset(point, animated: false)
})
}
编辑
UICollectionView
的一些好处:
如果你只有两个“全视图单元格”,你可以在它们之间切换,所以你没有使用集合视图的任何其他好处,你可能会发现将两个视图放在滚动视图中要容易得多。
这种方法的优点之一是您的视图(“单元格”)不会在您每次来回滚动时被删除/添加。
这是一个简单的例子......
我们将使用
UICollectionViewCell
子类代替 UIView
:
class ViewInsteadOfCell: UIView {
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.textAlignment = .center
v.textColor = .black
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
addSubview(theLabel)
NSLayoutConstraint.activate([
theLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
theLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
theLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75),
theLabel.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
])
}
}
以及一个示例视图控制器,展示了如何实现它:
class ScrollViewExampleVC: UIViewController {
var scrollView: UIScrollView = UIScrollView()
// let's define this here, so we can "play with the speed"
// to make it easier to see the difference (e.g. the "cells" don't disappear)
let animDuration: Double = 0.2
override func viewDidLoad() {
super.viewDidLoad()
// instead of a collection view with cells,
// let's use a stack view with two "cell" views
let stackView = UIStackView()
stackView.distribution = .fillEqually
// these are defaults, but just for clarity
stackView.axis = .horizontal
stackView.spacing = 0
let cellA = ViewInsteadOfCell()
cellA.backgroundColor = .systemRed
cellA.theLabel.text = "First"
stackView.addArrangedSubview(cellA)
let cellB = ViewInsteadOfCell()
cellB.backgroundColor = .systemGreen
cellB.theLabel.text = "Second"
stackView.addArrangedSubview(cellB)
// two buttons for the bottom to switch
// between "Left" and "Right"
var cfg = UIButton.Configuration.filled()
cfg.title = "First"
let btnA = UIButton(configuration: cfg)
btnA.addAction (
UIAction { _ in
self.animateToFirstCell()
}, for: .touchUpInside
)
cfg.title = "Second"
let btnB = UIButton(configuration: cfg)
btnB.addAction (
UIAction { _ in
self.animateToSecondCell()
}, for: .touchUpInside
)
// don't let buttons expand vertically
btnA.setContentHuggingPriority(.required, for: .vertical)
btnB.setContentHuggingPriority(.required, for: .vertical)
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
btnA.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnA)
btnB.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnB)
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
let fg = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// buttons at the bottom
btnA.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
btnA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
btnB.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
btnB.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
btnB.leadingAnchor.constraint(equalTo: btnA.trailingAnchor, constant: 8.0),
btnB.widthAnchor.constraint(equalTo: btnA.widthAnchor),
// scroll view top/leading/trailing to view safe area
// bottom to top of buttons
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: btnA.topAnchor, constant: -8.0),
// stack view constrained all four sides to scroll view's Content Layout Guide
stackView.topAnchor.constraint(equalTo: cg.topAnchor),
stackView.leadingAnchor.constraint(equalTo: cg.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: cg.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: cg.bottomAnchor),
// stack view height equal to scroll view Frame Layout Guide height
stackView.heightAnchor.constraint(equalTo: fg.heightAnchor),
// stack view width equal to scroll view Frame Layout Guide width * 2
stackView.widthAnchor.constraint(equalTo: fg.widthAnchor, multiplier: 2.0),
])
}
func animateToFirstCell() {
UIView.animate(withDuration: animDuration, animations: {
self.scrollView.setContentOffset(.init(x: 0.0, y: 0.0), animated: false)
})
}
func animateToSecondCell() {
UIView.animate(withDuration: animDuration, animations: {
self.scrollView.setContentOffset(.init(x: self.scrollView.frame.width, y: 0.0), animated: false)
})
}
}