Skip to content

Commit d87ed0b

Browse files
committed
fix spacing
1 parent 9d63f27 commit d87ed0b

1 file changed

Lines changed: 168 additions & 171 deletions

File tree

CenteredCollectionView/Classes/CenteredCollectionViewFlowLayout.swift

Lines changed: 168 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -9,199 +9,196 @@
99
import UIKit
1010

1111
public extension UICollectionView {
12-
/// A convenient way to create a UICollectionView and configue it with a CenteredCollectionViewFlowLayout.
13-
///
14-
/// - Parameters:
15-
/// - frame: The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
16-
/// - centeredCollectionViewFlowLayout: The `CenteredCollectionViewFlowLayout` for the `UICollectionView` to be configured with.
12+
/// A convenient way to create a UICollectionView and configue it with a CenteredCollectionViewFlowLayout.
13+
///
14+
/// - Parameters:
15+
/// - frame: The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
16+
/// - centeredCollectionViewFlowLayout: The `CenteredCollectionViewFlowLayout` for the `UICollectionView` to be configured with.
1717
convenience init(frame: CGRect = .zero, centeredCollectionViewFlowLayout: CenteredCollectionViewFlowLayout) {
18-
self.init(frame: frame, collectionViewLayout: centeredCollectionViewFlowLayout)
19-
decelerationRate = UIScrollView.DecelerationRate.fast
20-
}
18+
self.init(frame: frame, collectionViewLayout: centeredCollectionViewFlowLayout)
19+
decelerationRate = UIScrollView.DecelerationRate.fast
20+
}
2121
}
2222

2323
/// A `UICollectionViewFlowLayout` that _pages_ and keeps its cells centered, resulting in the _"carousel effect"_ 🎡
2424
open class CenteredCollectionViewFlowLayout: UICollectionViewFlowLayout {
25-
private var lastCollectionViewSize: CGSize = CGSize.zero
26-
private var lastScrollDirection: UICollectionView.ScrollDirection!
27-
private var lastItemSize: CGSize = CGSize.zero
28-
var pageWidth: CGFloat {
29-
switch scrollDirection {
30-
case .horizontal:
31-
return itemSize.width + minimumLineSpacing
32-
case .vertical:
33-
return itemSize.height + minimumLineSpacing
34-
25+
private var lastCollectionViewSize: CGSize = CGSize.zero
26+
private var lastScrollDirection: UICollectionView.ScrollDirection!
27+
private var lastItemSize: CGSize = CGSize.zero
28+
var pageWidth: CGFloat {
29+
switch scrollDirection {
30+
case .horizontal:
31+
return itemSize.width + minimumLineSpacing
32+
case .vertical:
33+
return itemSize.height + minimumLineSpacing
3534
default:
3635
return 0
3736
}
38-
}
39-
40-
/// Calculates the current centered page.
41-
public var currentCenteredPage: Int? {
42-
guard let collectionView = collectionView else { return nil }
43-
let currentCenteredPoint = CGPoint(x: collectionView.contentOffset.x + collectionView.bounds.width/2, y: collectionView.contentOffset.y + collectionView.bounds.height/2)
44-
45-
return collectionView.indexPathForItem(at: currentCenteredPoint)?.row
46-
}
47-
48-
public override init() {
49-
super.init()
50-
scrollDirection = .horizontal
51-
lastScrollDirection = scrollDirection
52-
}
53-
54-
required public init?(coder aDecoder: NSCoder) {
55-
super.init(coder: aDecoder)
56-
scrollDirection = .horizontal
57-
lastScrollDirection = scrollDirection
58-
}
59-
60-
override open func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
61-
super.invalidateLayout(with: context)
62-
guard let collectionView = collectionView else { return }
63-
64-
// invalidate layout to center first and last
65-
let currentCollectionViewSize = collectionView.bounds.size
66-
if !currentCollectionViewSize.equalTo(lastCollectionViewSize) || lastScrollDirection != scrollDirection || lastItemSize != itemSize {
67-
switch scrollDirection {
68-
case .horizontal:
69-
let inset = (currentCollectionViewSize.width - itemSize.width) / 2
70-
collectionView.contentInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
71-
collectionView.contentOffset = CGPoint(x: -inset, y: 0)
72-
case .vertical:
73-
let inset = (currentCollectionViewSize.height - itemSize.height) / 2
74-
collectionView.contentInset = UIEdgeInsets(top: inset, left: 0, bottom: inset, right: 0)
75-
collectionView.contentOffset = CGPoint(x: 0, y: -inset)
76-
37+
}
38+
39+
/// Calculates the current centered page.
40+
public var currentCenteredPage: Int? {
41+
guard let collectionView = collectionView else { return nil }
42+
let currentCenteredPoint = CGPoint(x: collectionView.contentOffset.x + collectionView.bounds.width/2, y: collectionView.contentOffset.y + collectionView.bounds.height/2)
43+
44+
return collectionView.indexPathForItem(at: currentCenteredPoint)?.row
45+
}
46+
47+
public override init() {
48+
super.init()
49+
scrollDirection = .horizontal
50+
lastScrollDirection = scrollDirection
51+
}
52+
53+
required public init?(coder aDecoder: NSCoder) {
54+
super.init(coder: aDecoder)
55+
scrollDirection = .horizontal
56+
lastScrollDirection = scrollDirection
57+
}
58+
59+
override open func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
60+
super.invalidateLayout(with: context)
61+
guard let collectionView = collectionView else { return }
62+
63+
// invalidate layout to center first and last
64+
let currentCollectionViewSize = collectionView.bounds.size
65+
if !currentCollectionViewSize.equalTo(lastCollectionViewSize) || lastScrollDirection != scrollDirection || lastItemSize != itemSize {
66+
switch scrollDirection {
67+
case .horizontal:
68+
let inset = (currentCollectionViewSize.width - itemSize.width) / 2
69+
collectionView.contentInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
70+
collectionView.contentOffset = CGPoint(x: -inset, y: 0)
71+
case .vertical:
72+
let inset = (currentCollectionViewSize.height - itemSize.height) / 2
73+
collectionView.contentInset = UIEdgeInsets(top: inset, left: 0, bottom: inset, right: 0)
74+
collectionView.contentOffset = CGPoint(x: 0, y: -inset)
7775
default:
7876
collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
7977
collectionView.contentOffset = .zero
80-
}
81-
lastCollectionViewSize = currentCollectionViewSize
82-
lastScrollDirection = scrollDirection
83-
lastItemSize = itemSize
84-
}
85-
}
86-
87-
override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
88-
guard let collectionView = collectionView else { return proposedContentOffset }
89-
90-
let proposedRect: CGRect = determineProposedRect(collectionView: collectionView, proposedContentOffset: proposedContentOffset)
91-
92-
guard let layoutAttributes = layoutAttributesForElements(in: proposedRect),
93-
let candidateAttributesForRect = attributesForRect(
94-
collectionView: collectionView,
95-
layoutAttributes: layoutAttributes,
96-
proposedContentOffset: proposedContentOffset
97-
) else { return proposedContentOffset }
98-
99-
var newOffset: CGFloat
100-
let offset: CGFloat
101-
switch scrollDirection {
102-
case .horizontal:
103-
newOffset = candidateAttributesForRect.center.x - collectionView.bounds.size.width / 2
104-
offset = newOffset - collectionView.contentOffset.x
105-
106-
if (velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0) {
107-
let pageWidth = itemSize.width + minimumLineSpacing
108-
newOffset += velocity.x > 0 ? pageWidth : -pageWidth
109-
}
110-
return CGPoint(x: newOffset, y: proposedContentOffset.y)
111-
112-
case .vertical:
113-
newOffset = candidateAttributesForRect.center.y - collectionView.bounds.size.height / 2
114-
offset = newOffset - collectionView.contentOffset.y
115-
116-
if (velocity.y < 0 && offset > 0) || (velocity.y > 0 && offset < 0) {
117-
let pageHeight = itemSize.height + minimumLineSpacing
118-
newOffset += velocity.y > 0 ? pageHeight : -pageHeight
119-
}
120-
return CGPoint(x: proposedContentOffset.x, y: newOffset)
78+
}
79+
lastCollectionViewSize = currentCollectionViewSize
80+
lastScrollDirection = scrollDirection
81+
lastItemSize = itemSize
82+
}
83+
}
84+
85+
override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
86+
guard let collectionView = collectionView else { return proposedContentOffset }
87+
88+
let proposedRect: CGRect = determineProposedRect(collectionView: collectionView, proposedContentOffset: proposedContentOffset)
89+
90+
guard let layoutAttributes = layoutAttributesForElements(in: proposedRect),
91+
let candidateAttributesForRect = attributesForRect(
92+
collectionView: collectionView,
93+
layoutAttributes: layoutAttributes,
94+
proposedContentOffset: proposedContentOffset
95+
) else { return proposedContentOffset }
96+
97+
var newOffset: CGFloat
98+
let offset: CGFloat
99+
switch scrollDirection {
100+
case .horizontal:
101+
newOffset = candidateAttributesForRect.center.x - collectionView.bounds.size.width / 2
102+
offset = newOffset - collectionView.contentOffset.x
103+
104+
if (velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0) {
105+
let pageWidth = itemSize.width + minimumLineSpacing
106+
newOffset += velocity.x > 0 ? pageWidth : -pageWidth
107+
}
108+
return CGPoint(x: newOffset, y: proposedContentOffset.y)
109+
110+
case .vertical:
111+
newOffset = candidateAttributesForRect.center.y - collectionView.bounds.size.height / 2
112+
offset = newOffset - collectionView.contentOffset.y
113+
114+
if (velocity.y < 0 && offset > 0) || (velocity.y > 0 && offset < 0) {
115+
let pageHeight = itemSize.height + minimumLineSpacing
116+
newOffset += velocity.y > 0 ? pageHeight : -pageHeight
117+
}
118+
return CGPoint(x: proposedContentOffset.x, y: newOffset)
121119

122120
default:
123121
return .zero
124-
}
125-
}
126-
127-
/// Programmatically scrolls to a page at a specified index.
128-
///
129-
/// - Parameters:
130-
/// - index: The index of the page to scroll to.
131-
/// - animated: Whether the scroll should be performed animated.
132-
public func scrollToPage(index: Int, animated: Bool) {
133-
guard let collectionView = collectionView else { return }
134-
135-
let proposedContentOffset: CGPoint
136-
let shouldAnimate: Bool
137-
switch scrollDirection {
138-
case .horizontal:
139-
let pageOffset = CGFloat(index) * pageWidth - collectionView.contentInset.left
140-
proposedContentOffset = CGPoint(x: pageOffset, y: collectionView.contentOffset.y)
141-
shouldAnimate = abs(collectionView.contentOffset.x - pageOffset) > 1 ? animated : false
142-
case .vertical:
143-
let pageOffset = CGFloat(index) * pageWidth - collectionView.contentInset.top
144-
proposedContentOffset = CGPoint(x: collectionView.contentOffset.x, y: pageOffset)
145-
shouldAnimate = abs(collectionView.contentOffset.y - pageOffset) > 1 ? animated : false
146-
122+
}
123+
}
124+
125+
/// Programmatically scrolls to a page at a specified index.
126+
///
127+
/// - Parameters:
128+
/// - index: The index of the page to scroll to.
129+
/// - animated: Whether the scroll should be performed animated.
130+
public func scrollToPage(index: Int, animated: Bool) {
131+
guard let collectionView = collectionView else { return }
132+
133+
let proposedContentOffset: CGPoint
134+
let shouldAnimate: Bool
135+
switch scrollDirection {
136+
case .horizontal:
137+
let pageOffset = CGFloat(index) * pageWidth - collectionView.contentInset.left
138+
proposedContentOffset = CGPoint(x: pageOffset, y: collectionView.contentOffset.y)
139+
shouldAnimate = abs(collectionView.contentOffset.x - pageOffset) > 1 ? animated : false
140+
case .vertical:
141+
let pageOffset = CGFloat(index) * pageWidth - collectionView.contentInset.top
142+
proposedContentOffset = CGPoint(x: collectionView.contentOffset.x, y: pageOffset)
143+
shouldAnimate = abs(collectionView.contentOffset.y - pageOffset) > 1 ? animated : false
147144
default:
148145
proposedContentOffset = .zero
149146
shouldAnimate = false
150-
}
151-
collectionView.setContentOffset(proposedContentOffset, animated: shouldAnimate)
152-
}
147+
}
148+
collectionView.setContentOffset(proposedContentOffset, animated: shouldAnimate)
149+
}
153150
}
154151

155152
private extension CenteredCollectionViewFlowLayout {
156-
157-
func determineProposedRect(collectionView: UICollectionView, proposedContentOffset: CGPoint) -> CGRect {
158-
let size = collectionView.bounds.size
159-
let origin: CGPoint
160-
switch scrollDirection {
161-
case .horizontal:
162-
origin = CGPoint(x: proposedContentOffset.x, y: collectionView.contentOffset.y)
163-
case .vertical:
164-
origin = CGPoint(x: collectionView.contentOffset.x, y: proposedContentOffset.y)
153+
154+
func determineProposedRect(collectionView: UICollectionView, proposedContentOffset: CGPoint) -> CGRect {
155+
let size = collectionView.bounds.size
156+
let origin: CGPoint
157+
switch scrollDirection {
158+
case .horizontal:
159+
origin = CGPoint(x: proposedContentOffset.x, y: collectionView.contentOffset.y)
160+
case .vertical:
161+
origin = CGPoint(x: collectionView.contentOffset.x, y: proposedContentOffset.y)
165162
default:
166163
origin = .zero
167-
}
168-
return CGRect(origin: origin, size: size)
169-
}
170-
171-
func attributesForRect(
172-
collectionView: UICollectionView,
173-
layoutAttributes: [UICollectionViewLayoutAttributes],
174-
proposedContentOffset: CGPoint
175-
) -> UICollectionViewLayoutAttributes? {
176-
177-
var candidateAttributes: UICollectionViewLayoutAttributes?
178-
let proposedCenterOffset: CGFloat
179-
180-
switch scrollDirection {
181-
case .horizontal:
182-
proposedCenterOffset = proposedContentOffset.x + collectionView.bounds.size.width / 2
183-
case .vertical:
184-
proposedCenterOffset = proposedContentOffset.y + collectionView.bounds.size.height / 2
164+
}
165+
return CGRect(origin: origin, size: size)
166+
}
167+
168+
func attributesForRect(
169+
collectionView: UICollectionView,
170+
layoutAttributes: [UICollectionViewLayoutAttributes],
171+
proposedContentOffset: CGPoint
172+
) -> UICollectionViewLayoutAttributes? {
173+
174+
var candidateAttributes: UICollectionViewLayoutAttributes?
175+
let proposedCenterOffset: CGFloat
176+
177+
switch scrollDirection {
178+
case .horizontal:
179+
proposedCenterOffset = proposedContentOffset.x + collectionView.bounds.size.width / 2
180+
case .vertical:
181+
proposedCenterOffset = proposedContentOffset.y + collectionView.bounds.size.height / 2
185182
default:
186183
proposedCenterOffset = .zero
187-
}
188-
189-
for attributes in layoutAttributes {
190-
guard attributes.representedElementCategory == .cell else { continue }
191-
guard candidateAttributes != nil else {
192-
candidateAttributes = attributes
193-
continue
194-
}
195-
196-
switch scrollDirection {
197-
case .horizontal where abs(attributes.center.x - proposedCenterOffset) < abs(candidateAttributes!.center.x - proposedCenterOffset):
198-
candidateAttributes = attributes
199-
case .vertical where abs(attributes.center.y - proposedCenterOffset) < abs(candidateAttributes!.center.y - proposedCenterOffset):
200-
candidateAttributes = attributes
201-
default:
202-
continue
203-
}
204-
}
205-
return candidateAttributes
206-
}
184+
}
185+
186+
for attributes in layoutAttributes {
187+
guard attributes.representedElementCategory == .cell else { continue }
188+
guard candidateAttributes != nil else {
189+
candidateAttributes = attributes
190+
continue
191+
}
192+
193+
switch scrollDirection {
194+
case .horizontal where abs(attributes.center.x - proposedCenterOffset) < abs(candidateAttributes!.center.x - proposedCenterOffset):
195+
candidateAttributes = attributes
196+
case .vertical where abs(attributes.center.y - proposedCenterOffset) < abs(candidateAttributes!.center.y - proposedCenterOffset):
197+
candidateAttributes = attributes
198+
default:
199+
continue
200+
}
201+
}
202+
return candidateAttributes
203+
}
207204
}

0 commit comments

Comments
 (0)