在Apple的Photo 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
})
}
不要使用' indexPathsForVisibleItems '而只使用' contentOffset '?计算偏移后的偏移?但是,当旋转后的contentSize与不同的单元尺寸,单元间距等的预期不同时,这怎么可能呢?
答案 0 :(得分:26)
您可以通过实施UICollectionViewDelegate方法collectionView(_:targetContentOffsetForProposedContentOffset:)
:
在布局更新期间,或在布局之间转换时,集合视图会调用此方法,以便您有机会更改要在动画结束时使用的建议内容偏移。如果布局或动画可能导致项目以不适合您设计的方式定位,则可能会返回新值。
或者,如果您已经将UICollectionViewLayout子类化,则可以在布局子类中实现targetContentOffset(forProposedContentOffset:)
,这可能更方便。
在您的方法实现中,计算并返回会导致中心单元格位于集合视图中心的内容偏移量。如果您的单元格大小是固定的,则应该简单地撤消由其他UI元素(例如状态栏和/或导航栏的消失帧)导致的内容偏移的更改。
如果您的手机尺寸因设备方向而异:
indexPathForItem(at:)
来获取并保存中心单元的索引路径,将内容偏移量(加上集合视图的可见边界的高度的一半)作为点传递targetContentOffset(forProposedContentOffset:)
方法之一。通过使用保存的索引路径调用layoutAttributesForItem(at:)
来检索中心单元的布局属性。目标内容偏移量是该项目框架的中间位置(减去集合视图可见边界高度的一半)。第一步可以在viewWillTransition(to:with:)
或scrollViewDidScroll()
中的视图控制器中实施。在检查由设备方向更改引起的边界更改后,它也可以在prepareForAnimatedBoundsChange()
中的布局对象中实现,也可以在invalidateLayout(with:)
中实现。 (在这种情况下,您还需要确保shouldInvalidateLayout(forBoundsChange:)
返回true
。)
您可能需要针对内容插入,单元格间距或应用特定的其他事项进行调整。
答案 1 :(得分:3)
这是swift
中 jamesk 解决方案的实施:
在ViewController中:
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()
}
在UICollectionViewDelegateFlowLayout中:
func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
guard let oldCenter = prevIndexPathAtCenter else {
return proposedContentOffset
}
let attrs = collectionView.layoutAttributesForItem(at: oldCenter)
let newOriginForOldIndex = attrs?.frame.origin
return newOriginForOldIndex ?? proposedContentOffset
}
答案 2 :(得分:0)
正如@jamesk所说,我们有不同的处理方式,其中之一是:
我在collectionView
中有一个ViewController
,其默认值为UICollectionViewDelegateFlowLayout
。 (我不会覆盖它)。
我只有一个部分。
我想先在 @jamesk 和 @ 0rt 的坦克中寻求答案。
class ViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var currentVisibleIndexPath: IndexPath = IndexPath(row: 0, section: 0)
// 1. get the current Index of item
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let center = CGPoint(x: scrollView.contentOffset.x + (scrollView.frame.width / 2), y: (scrollView.frame.height / 2))
if let ip = self.screenView.indexPathForItem(at: center) {
currentVisibleIndexPath = ip
}
}
// 2. if transition happen, invalidateLayout of collectionView, to recalculate layout positions, but this does not change its offset
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
collectionView.collectionViewLayout.invalidateLayout()
}
// 3. change offset to make current index to center of collection view
// copy of @0rt answer
func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
let attrs = collectionView.layoutAttributesForItem(at: currentVisibleIndexPath)
let newOriginForOldIndex = attrs?.frame.origin
return newOriginForOldIndex ?? proposedContentOffset
}
}
答案 3 :(得分:0)
接受的答案对我不起作用。
就我而言,{
"data": {
"repository": {
"issue": {
"title": "fix dead path in startover",
"projectCards": {
"nodes": [
{
"createdAt": "2017-08-28T05:47:49Z",
"column": {
"name": "Done"
},
"project": {
"name": "Startover",
"body": "Show epg / caipy events + startover modes"
}
}
]
}
}
}
}
}
UICollectionViewDelegate
也没有调用collectionView(_:targetContentOffsetForProposedContentOffset:)
的方法
UICollectionViewLayout
相反,我继承了targetContentOffset(forProposedContentOffset:)
并取代了UICollectionView
方法:
layoutSubviews
这可能会导致一些副作用,因此在生产中使用之前请仔细检查