>我正在使用以下方法将多个水平集合视图滚动到最后一个(最右边)项目:
timelineCollectionView.scrollToMaxContentOffset(animated: false)
postsCollectionView.scrollToMaxContentOffset(animated: false)
这很好用,除了我不知道把它们放在哪里。
我尝试过的各个地方:
viewWillAppear - 它不会滚动,就好像单元格尚未完全加载一样。
viewDidAppear - 它确实完美地滚动。棒!除了现在,即使你输入动画:false,你也可以看到它滚动。集合视图在最左侧加载一秒钟,然后更新到最右侧
viewDidLayoutSubviews - 这非常有效 - 时机和一切!但是,它被调用了很多次。如果我能确定刚刚布置了哪个子视图,也许我可以在那里滚动它,但我不确定如何。
有没有更好的选择?我该怎么做?
这些也是我用来设置内容偏移量的函数:
extension UIScrollView {
var minContentOffset: CGPoint {
return CGPoint(
x: -contentInset.left,
y: -contentInset.top)
}
var maxContentOffset: CGPoint {
return CGPoint(
x: contentSize.width - bounds.width + contentInset.right,
y: contentSize.height - bounds.height + contentInset.bottom)
}
func scrollToMinContentOffset(animated: Bool) {
setContentOffset(minContentOffset, animated: animated)
}
func scrollToMaxContentOffset(animated: Bool) {
setContentOffset(maxContentOffset, animated: animated)
}
}
有更好的选择吗?
是的,有,那将是UICollectionView.scrollToItem(at:at:animated:)
接下来是等到单元格取消排队,以便我们可以调用UICollectionView.scrollToItem(at:at:animated:)
,有多种方法可以做到这一点。
您可以在animateWithDuration:animations:completion
中调用UICollectionView.reloadData
它既适用于viewDidLoad
,也可以viewWillAppear
,滚动也是无缝的,如下所示:
func scrollToItem(at index: Int) {
UIView.animate(withDuration: 0.0, animations: { [weak self] in
self?.collectionView.reloadData()
}, completion: { [weak self] (finished) in
if let count = self?.dataSource?.count, index < count {
let indexPath = IndexPath(item: index, section: 0)
self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
}
})
}
另一种方法是在UICollectionView.reloadData
后将代码块调度到主队列,如下所示:
func scrollToItem(at index: Int) {
self.collectionView.reloadData()
DispatchQueue.main.async { [weak self] in
// this will be executed after cells dequeuing
if let count = self?.dataSource?.count, index < count {
let indexPath = IndexPath(item: index, section: 0)
self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
}
}
}
我还创建了一个使用水平UICollectionView
的要点,只需创建一个新的 xcode 项目并将 ViewController.swift替换为要点源代码。
似乎最好的地方是在viewWillAppear中,它现在正在工作。请注意,我必须添加一些调用:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let postCount = posts.fetchedObjects?.count
let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
postsCollectionView.setNeedsLayout()
postsCollectionView.layoutIfNeeded()
postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
}
我的 viewWillAppear 之前不起作用的原因是它不知道我的收藏视图的内容大小,并且它认为 contentSize 为 0,所以 scrollToItem 和 setContentOffset 没有将其移动到任何地方。 layoutIfNeed() 检查 contentOffset 现在它不为零,它会移动适当的量。
编辑:事实上,如果你愿意,你甚至可以把它放在ViewDidLoad中,这样当你添加和关闭补充视图时它就不会重新触发。关键是访问内容大小的layoutIfNeed命令。
您可以在viewdidLoad中使用performBatchUpdate。
timelineCollectionView.reloadData()
timelineCollectionView.performBatchUpdates(nil, completion: {
(result) in
timelineCollectionView.scrollToMaxContentOffset(animated: false)
})
第二个选项是您可以将观察者放在集合视图 ContentSize 中。
override func viewDidLoad() {
super.viewDidLoad()
timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
print("set ContentOffset here")
timelineCollectionView.scrollToMaxContentOffset(animated: false)
}
}
由于您需要 scrollView 的内容大小和边界来计算目标内容偏移量,因此您需要在设置之前对 scrollView 进行布局。
viewDidLayoutSubviews
对我来说也很好看。每次视图控制器的视图刚刚布置其子视图(仅其子视图,而不是其子视图的子视图)时,都会调用它。只需添加一个标志即可确保更改仅执行一次。当您处理旋转或特征集合更改时,黑客非常常见。
var firstTime = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard firstTime else { return }
firstTime = false
collectionView.scrollToMaxContentOffset(animated: false)
}
有没有更好的选择?
viewWillAppear
被召唤得太早了。此时边界和内容大小是错误的。viewDidAppear
被称为太晚了。太糟糕了,布局已经完成了。视图控制器演示文稿的动画完成后将调用它,因此滚动视图的内容偏移量将在整个动画持续时间内为零。破解UIView.animation或performBatchUpdates的完成块? 嗯,它有效,但这完全违反直觉。我认为这不是一个正确的解决方案。
此外,如文档中所述:
完成:[...]如果动画的持续时间为 0,则在下一个运行循环周期开始时执行此块。
你只是在添加一个额外的无用循环。viewDidLayoutSubviews
在当前结束之前调用。