AppleのPhotoのアプリでは、任意のオフセットまでスクロールしながらデバイスを回転させると、以前は中央にあった同じセルが、回転後に中央に配置されます。
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が、異なるセルサイズ、セル間隔などに対して予想されるものと異なる可能性がある場合、これはどのように可能でしょうか?
これを行うには、 ICollectionViewDelegate メソッド collectionView(_:targetContentOffsetForProposedContentOffset:)
を実装します。
レイアウトの更新中、またはレイアウト間を移行するときに、コレクションビューはこのメソッドを呼び出して、アニメーションの最後に使用する提案されたコンテンツオフセットを変更する機会を提供します。レイアウトまたはアニメーションによってアイテムがデザインに最適でない方法で配置される可能性がある場合は、新しい値を返す可能性があります。
または、UICollectionViewLayoutを既にサブクラス化している場合は、レイアウトサブクラスに targetContentOffset(forProposedContentOffset:)
を実装できます。これはより便利な場合があります。
そのメソッドの実装では、中央のセルがコレクションビューの中央に配置されるようにするコンテンツオフセットを計算して返します。セルサイズが固定されている場合は、他のUI要素(ステータスバーやナビゲーションバーのフレームが消えるなど)によって引き起こされたコンテンツオフセットへの変更を元に戻すだけです。
セルサイズがデバイスの向きによって異なる場合:
indexPathForItem(at:)
を呼び出し、コンテンツオフセット(およびコレクションビューの高さの半分を加えたもの)を渡して、中央のセルのインデックスパスをフェッチして保存します。目に見える境界)をポイントとして。targetContentOffset(forProposedContentOffset:)
メソッドの1つを実装します。保存したインデックスパスを使用してlayoutAttributesForItem(at:)
を呼び出し、中央のセルのレイアウト属性を取得します。ターゲットコンテンツオフセットは、そのアイテムのフレームの中央です(コレクションビューの表示境界の高さの半分未満)。最初のステップは、ViewControllerのviewWillTransition(to:with:)
またはscrollViewDidScroll()
に実装できます。また、prepareForAnimatedBoundsChange()
のレイアウトオブジェクトに実装することも、デバイスの向きの変更によって引き起こされる境界の変更を確認した後、invalidateLayout(with:)
に実装することもできます。 (このような状況では、shouldInvalidateLayout(forBoundsChange:)
がtrue
を返すようにする必要もあります。)
コンテンツのはめ込み、セルの間隔、またはアプリに固有のその他の事項を調整する必要がある場合があります。
これは、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
}