我正在使用以下命令将多个水平collectionView滚动到最后一个(最右边)项目:
timelineCollectionView.scrollToMaxContentOffset(animated: false)
postsCollectionView.scrollToMaxContentOffset(animated: false)
这很好用,除了我不知道将它们放在哪里。
我尝试过的各个地方:
viewWillAppear-它不会像单元格尚未完全加载一样滚动。
viewDidAppear-它确实滚动完美。太棒了!除了现在,即使将动画设置为false,您也可以看到它滚动。在更新到最右边之前,collectionView会在最左边加载一秒钟,然后进行更新
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)
}
}
答案 0 :(得分:1)
还有更好的选择吗?
是的,那就是UICollectionView.scrollToItem(at:at:animated:)
下一步是等待单元格出队,以便我们调用UICollectionView.scrollToItem(at:at:animated:)
,并且有多种方法可以执行此操作。
您可以在UICollectionView.reloadData
内调用animateWithDuration:animations:completion
,它在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)
}
}
}
我还创建了gist,其中我使用水平UICollectionView
,只需创建一个新的xcode项目,然后将要使用的源代码替换ViewController.swift。
答案 1 :(得分:0)
您可以在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)
}
}
答案 2 :(得分:0)
由于需要内容大小和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的完成块? 好吧,它起作用了,但这完全违反直觉。我认为这不是一个适当的解决方案。
此外,如documentation中所指定:
完成:[...]如果动画的持续时间为0,则在下一个运行循环周期的开始执行此块。
您只是添加了一个额外的无用循环。 viewDidLayoutSubviews
会在当前值结束之前被调用。
答案 3 :(得分:0)
似乎最好的方法是在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之前不起作用的原因是,它不知道我的collectionView的contentSize,并且认为contentSize为0,所以scrollToItem和setContentOffset不会将其移动到任何地方。 layoutIfNeeded()检查contentOffset,现在检查其非零值,然后将其移动适当的量。
编辑:实际上,您甚至可以根据需要将其放在ViewDidLoad中,这样当您添加和关闭辅助视图时,它就不会重新触发。关键确实是用于访问contentSize的layoutIfNeeded命令。