在视图出现之前,在哪里可以将内容偏移量设置为UICollectionView中的最后一项?



>我正在使用以下方法将多个水平集合视图滚动到最后一个(最右边)项目:

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在当前结束之前调用。

最新更新