On Apple\'s Photo\'s App, if you rotate device while scroll to an arbitrary offset, same cell that was in the center beforehand would end u
This is the implementation of jamesk solution:
In your 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()
}
In 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
}
You can do this by implementing the UICollectionViewDelegate method collectionView(_:targetContentOffsetForProposedContentOffset:):
During layout updates, or when transitioning between layouts, the collection view calls this method to give you the opportunity to change the proposed content offset to use at the end of the animation. You might return a new value if the layout or animations might cause items to be positioned in a way that is not optimal for your design.
Alternatively, if you've already subclassed UICollectionViewLayout, you can implement targetContentOffset(forProposedContentOffset:) in your layout subclass, which may be more convenient.
In your implementation of that method, compute and return the content offset that would cause the center cell to be positioned in the center of the collection view. If your cell sizes are fixed, it should be a simple matter of undoing the change to the content offset caused by other UI elements (such as the disappearing frames of the status bar and/or navigation bar).
If your cell sizes vary with device orientation:
targetContentOffset(forProposedContentOffset:)
methods. Retrieve the layout attributes for the center cell by calling layoutAttributesForItem(at:)
with the saved index path. The target content offset is the middle of the frame for that item (less half of the height of the collection view's visible bounds).The first step could be implemented in your view controller in viewWillTransition(to:with:)
or in scrollViewDidScroll()
. It could also be implemented in your layout object in prepareForAnimatedBoundsChange()
, or perhaps in invalidateLayout(with:)
after checking for a bounds change caused by a change in device orientation. (You would also need to ensure that shouldInvalidateLayout(forBoundsChange:)
returns true
in those circumstances.)
You may need to make adjustments for content insets, cell spacing, or other matters specific to your app.
As @jamesk said we have different way to do it, one of them is this:
I have a collectionView
in a ViewController
with its default UICollectionViewDelegateFlowLayout
. (I don't override it).
I have only one section.
In advance I want to tanks of @jamesk and @0rt for their answers.
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
}
}
Accepted answer did not work for me.
In my case neither method of UICollectionViewDelegate
collectionView(_:targetContentOffsetForProposedContentOffset:)
nor the method of UICollectionViewLayout
was called
targetContentOffset(forProposedContentOffset:)
Instead, I inherited from UICollectionView
and overrode layoutSubviews
method:
class CollectionView: UICollectionView {
override func layoutSubviews() {
let old = contentOffset
super.layoutSubviews()
contentOffset = old
}
}
This may cause some side effects, so check it carefully before use in production