Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix target content offset edge case #115

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions MagazineLayout/Public/MagazineLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,26 +265,32 @@ public final class MagazineLayout: UICollectionViewLayout {
supplementaryViewLayoutAttributesForPendingAnimations.removeAll()

targetContentOffsetAnchor = nil
preInvalidationContentSize = nil
preInvalidationContentInset = nil

super.finalizeCollectionViewUpdates()
}

override public func prepare(forAnimatedBoundsChange oldBounds: CGRect) {
super.prepare(forAnimatedBoundsChange: oldBounds)

let contentSize: CGSize
let contentInset: UIEdgeInsets
if let metricsSnapshot = metricsSnapshots.first(where: { $0.bounds == oldBounds }) {
contentSize = metricsSnapshot.contentSize
contentInset = metricsSnapshot.contentInset
} else {
contentSize = collectionViewContentSize
contentInset = self.contentInset
}

targetContentOffsetAnchor = targetContentOffsetAnchor(
bounds: oldBounds,
contentHeight: preInvalidationContentSize?.height ?? collectionViewContentSize.height,
topInset: preInvalidationContentInset?.top ?? contentInset.top,
bottomInset: preInvalidationContentInset?.bottom ?? contentInset.bottom)
contentHeight: contentSize.height,
topInset: contentInset.top,
bottomInset: contentInset.bottom)
}

override public func finalizeAnimatedBoundsChange() {
targetContentOffsetAnchor = nil
preInvalidationContentSize = nil
preInvalidationContentInset = nil

super.finalizeAnimatedBoundsChange()
}
Expand Down Expand Up @@ -754,10 +760,29 @@ public final class MagazineLayout: UICollectionViewLayout {
return
}

if collectionViewContentSize.width > 0, collectionViewContentSize.height > 0 {
preInvalidationContentSize = preInvalidationContentSize ?? collectionViewContentSize
// We need to save a few content size and content inset values for different bounds. This
// allows us to compute the correct target content offset in `prepareForAnimatedBoundsChange`.
// The root issue is that in the aforementioned function, we need a way to know what the content
// size and content inset _were_ for a given bounds value.
let metricsSnapshot = MetricsSnapshot(
bounds: currentCollectionView.bounds,
contentSize: collectionViewContentSize,
contentInset: lastContentInset ?? contentInset)

// Replace existing snapshot if the bounds is the same
let indexOfExistingMetricsSnapshot = metricsSnapshots.firstIndex {
$0.bounds == metricsSnapshot.bounds
}
if let indexOfExistingMetricsSnapshot {
metricsSnapshots[indexOfExistingMetricsSnapshot] = metricsSnapshot
} else {
metricsSnapshots.append(metricsSnapshot)
}

// Limit to 3 snapshots
if metricsSnapshots.count > 3 {
metricsSnapshots.removeFirst()
}
preInvalidationContentInset = preInvalidationContentInset ?? lastContentInset ?? contentInset

let shouldInvalidateLayoutMetrics = !context.invalidateEverything &&
!context.invalidateDataSourceCounts
Expand Down Expand Up @@ -831,8 +856,13 @@ public final class MagazineLayout: UICollectionViewLayout {

private var lastContentInset: UIEdgeInsets?
private var cachedCollectionViewWidth: CGFloat?
private var preInvalidationContentSize: CGSize?
private var preInvalidationContentInset: UIEdgeInsets?

private struct MetricsSnapshot {
let bounds: CGRect
let contentSize: CGSize
let contentInset: UIEdgeInsets
}
private var metricsSnapshots = [MetricsSnapshot]()

// These properties are used to prevent scroll jumpiness due to self-sizing after rotation; see
// comment in `invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)` for more
Expand Down
Loading