设备轮换后的UICollectionView contentOffset

问题描述 投票:10回答:2

我想做什么:

在Apple的Photo's App上,如果在滚动到任意偏移的同时旋转设备,则预先在中心的相同单元将在旋转后终止于中心。

我试图用UICollectionView实现相同的行为。 'indexPathsForVisibleItems'似乎是这样做的方式....

到目前为止我得到了什么:

以下代码提供了旋转之间的平滑操作,但中心项计算似乎是关闭的:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    self.collectionView?.collectionViewLayout.invalidateLayout()

    let middleItem = (self.collectionView?.indexPathsForVisibleItems.count / 2) - 1
    let indexPath = self.collectionView?.indexPathsForVisibleItems[middleItem]

    coordinator.animate(alongsideTransition: { ctx in

        self.collectionView?.layoutIfNeeded()
        self.collectionView?.scrollToItem(at: indexPath!, at: .centeredVertically, animated: false)

    }, completion: { _ in


    })
}

什么不适用于上述代码:

  1. 肖像 - >风景:从细胞最终应该在中心处稍微偏离。
  2. 肖像 - >风景 - >肖像:完全偏离肖像中的偏移量。
  3. 人像 - >风景 - >人像 - >风景:离开!

笔记:

  • 由于必须重新计算单元格大小和间距,我必须在旋转时使集合视图布局无效。
  • self.collectionView?.layoutIfNeeded()在动画中 阻止,使过渡平稳
  • 可能是在集合视图布局未完成之前调用scrollToItem,导致不正确的滚动偏移?

替代方法?

而不是使用'indexPathsForVisibleItems',只使用'contentOffset'?计算偏移后的偏移?但是,当旋转后的contentSize可能与预期的不同单元尺寸,单元间距等不同时,这怎么可能呢?

ios swift uiscrollview uicollectionview uicollectionviewlayout
2个回答
28
投票

你可以通过实现UICollectionViewDelegate方法collectionView(_:targetContentOffsetForProposedContentOffset:)来做到这一点:

在布局更新期间,或在布局之间转换时,集合视图调用此方法,使您有机会更改要在动画结束时使用的建议内容偏移。如果布局或动画可能导致项目以不适合您的设计的方式定位,则可能会返回新值。

或者,如果您已经将UICollectionViewLayout子类化,则可以在布局子类中实现targetContentOffset(forProposedContentOffset:),这可能更方便。

在您的方法实现中,计算并返回内容偏移量,该偏移量将导致中心单元格位于集合视图的中心。如果您的单元格大小是固定的,则应该简单地撤消由其他UI元素(例如状态栏和/或导航栏的消失帧)导致的内容偏移的更改。

如果您的单元格大小因设备方向而异:

  1. 在设备旋转之前,通过在集合视图上调用indexPathForItem(at:)来获取并保存中心单元的索引路径,将内容偏移量(加上集合视图的可见边界的高度的一半)作为点。
  2. 实现其中一个targetContentOffset(forProposedContentOffset:)方法。通过使用保存的索引路径调用layoutAttributesForItem(at:)来检索中心单元格的布局属性。目标内容偏移量是该项目框架的中间位置(减少了集合视图可见边界高度的一半)。

第一步可以在viewWillTransition(to:with:)scrollViewDidScroll()中的视图控制器中实现。它也可以在prepareForAnimatedBoundsChange()中的布局对象中实现,或者在检查由设备方向更改引起的边界更改后可能在invalidateLayout(with:)中实现。 (在这种情况下,您还需要确保shouldInvalidateLayout(forBoundsChange:)返回true。)

您可能需要针对内容插入,单元格间距或应用程序特定的其他事项进行调整。


5
投票

这是qazxsw poi中jamesk解决方案的实现:

在您的ViewController中:

swift

在UICollectionViewDelegateFlowLayout中:

fileprivate var prevIndexPathAtCenter: IndexPath?

fileprivate var currentIndexPath: IndexPath? {
    let center = view.convert(collectionView.center, to: collectionView)
    return collectionView.indexPathForItem(at: center)
}

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
    super.willTransition(to: newCollection, with: coordinator)

    if let indexAtCenter = currentIndexPath {
        prevIndexPathAtCenter = indexAtCenter
    }
    collectionView.collectionViewLayout.invalidateLayout()
}
© www.soinside.com 2019 - 2024. All rights reserved.