Thanks for the answer.
I implemented the changes but still the same issue is happening, specifically for the 3rd cell (index 2).
This is the layout used:
class PageFlowLayout: UICollectionViewFlowLayout {
override class var layoutAttributesClass: AnyClass {
UICollectionViewLayoutAttributes.self
}
private var calculatedAttributes: [UICollectionViewLayoutAttributes] = []
private var calculatedContentWidth: CGFloat = 0
private var calculatedContentHeight: CGFloat = 0
public weak var delegate: PageFlowLayoutDelegate?
override var collectionViewContentSize: CGSize {
return CGSize(width: self.calculatedContentWidth, height: self.calculatedContentHeight)
}
override init() {
super.init()
self.estimatedItemSize = .zero
self.scrollDirection = .horizontal
self.minimumLineSpacing = 0
self.minimumInteritemSpacing = 0
self.sectionInset = .zero
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
guard
let collectionView = collectionView,
collectionView.numberOfSections > 0,
calculatedAttributes.isEmpty
else { return }
estimatedItemSize = collectionView.bounds.size
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let itemOrigin = CGPoint(x: CGFloat(item) * collectionView.bounds.width, y: 0)
attributes.frame = .init(origin: itemOrigin, size: collectionView.bounds.size)
calculatedAttributes.append(attributes)
}
calculatedContentWidth = collectionView.bounds.width * CGFloat(calculatedAttributes.count)
calculatedContentHeight = collectionView.bounds.size.height
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return calculatedAttributes.compactMap { $0.frame.intersects(rect) ? $0 : nil }
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return calculatedAttributes[indexPath.item]
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView else { return false }
if newBounds.size != collectionView.bounds.size {
return true
}
if newBounds.size.width > 0 {
let pages = calculatedContentWidth / newBounds.size.width
let arePagesExact = pages.truncatingRemainder(dividingBy: 1) == 0
return !arePagesExact
}
return false
}
override func invalidateLayout() {
calculatedAttributes = []
super.invalidateLayout()
}
override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
return false
}
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds)
if let collectionView, let currentPage = delegate?.currentPage(), newBounds.width > 0 {
let targetX = CGFloat(currentPage) * newBounds.width
let adjustment = targetX - collectionView.contentOffset.x
context.contentOffsetAdjustment.x = adjustment
}
return context
}
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
calculatedAttributes = []
super.invalidateLayout(with: context)
}
override func targetContentOffset(
forProposedContentOffset proposedContentOffset: CGPoint,
withScrollingVelocity velocity: CGPoint
) -> CGPoint {
guard let collectionView, collectionView.bounds.width > 0 else {
return proposedContentOffset
}
let pageWidth = collectionView.bounds.width
let currentOffset = collectionView.contentOffset.x
let currentPage = round(currentOffset / pageWidth)
var targetPage: CGFloat
if abs(velocity.x) > 0.2 {
targetPage = velocity.x > 0 ? currentPage + 1 : currentPage - 1
} else {
targetPage = round(proposedContentOffset.x / pageWidth)
}
let pageCount = CGFloat(collectionView.numberOfItems(inSection: 0))
targetPage = max(0, min(targetPage, pageCount - 1))
return CGPoint(x: targetPage * pageWidth, y: 0)
}
// This function updates the contentOffset in case is wrong
override func finalizeCollectionViewUpdates() {
guard let collectionView, let currentPage = delegate?.currentPage() else { return }
let xPosition = CGFloat(currentPage) * collectionView.bounds.width
if xPosition != collectionView.contentOffset.x {
let offset = CGPoint(x: xPosition, y: 0)
collectionView.setContentOffset(offset, animated: false)
}
}
}
I also removed the changes on collection view layoutSubviews()
Full implementation is updated here: https://github.com/mrciezas/PagedCollectionViewIssue#
Topic:
UI Frameworks
SubTopic:
General
Tags: