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

时间:2018-12-28 01:49:42

标签: ios swift uicollectionview

我正在使用以下命令将多个水平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)
    }
}

4 个答案:

答案 0 :(得分:1)

  

还有更好的选择吗?

是的,那就是UICollectionView.scrollToItem(at:at:animated:)

下一步是等待单元格出队,以便我们调用UICollectionView.scrollToItem(at:at:animated:),并且有多种方法可以执行此操作。

您可以在UICollectionView.reloadData内调用animateWithDuration:animations:completion,它在viewDidLoadviewWillAppear中都可以使用,滚动也很流畅,就像这样:

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命令。