1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| import UIKit
class CityCollectionViewFlowLayout: UICollectionViewFlowLayout {
fileprivate var lastCollectionViewSize: CGSize = CGSize.zero var scaleOffset: CGFloat = 200 var scaleFactor: CGFloat = 0.9 var alphaFactor: CGFloat = 0.3 var lineSpacing: CGFloat = 25.0 required init?(coder _: NSCoder) { fatalError() } init(itemSize: CGSize) { super.init() self.itemSize = itemSize minimumLineSpacing = lineSpacing scrollDirection = .horizontal } func setItemSize(itemSize: CGSize) { self.itemSize = itemSize } override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) { super.invalidateLayout(with: context) guard let collectionView = self.collectionView else { return } if collectionView.bounds.size != lastCollectionViewSize { configureContentInset() lastCollectionViewSize = collectionView.bounds.size } } override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { guard let collectionView = self.collectionView else { return proposedContentOffset } let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height) guard let layoutAttributes = self.layoutAttributesForElements(in: proposedRect) else { return proposedContentOffset } var candidateAttributes: UICollectionViewLayoutAttributes? let proposedContentOffsetCenterX = proposedContentOffset.x + collectionView.bounds.width / 2 for attributes in layoutAttributes { if attributes.representedElementCategory != .cell { continue } if candidateAttributes == nil { candidateAttributes = attributes continue } if abs(attributes.center.x - proposedContentOffsetCenterX) < abs(candidateAttributes!.center.x - proposedContentOffsetCenterX) { candidateAttributes = attributes } } guard let aCandidateAttributes = candidateAttributes else { return proposedContentOffset } var newOffsetX = aCandidateAttributes.center.x - collectionView.bounds.size.width / 2 let offset = newOffsetX - collectionView.contentOffset.x if (velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0) { let pageWidth = itemSize.width + minimumLineSpacing newOffsetX += velocity.x > 0 ? pageWidth : -pageWidth } return CGPoint(x: newOffsetX, y: proposedContentOffset.y) } override func shouldInvalidateLayout(forBoundsChange _: CGRect) -> Bool { return true } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let collectionView = self.collectionView, let superAttributes = super.layoutAttributesForElements(in: rect) else { return super.layoutAttributesForElements(in: rect) } let contentOffset = collectionView.contentOffset let size = collectionView.bounds.size let visibleRect = CGRect(x: contentOffset.x, y: contentOffset.y, width: size.width, height: size.height) let visibleCenterX = visibleRect.midX guard case let newAttributesArray as [UICollectionViewLayoutAttributes] = NSArray(array: superAttributes, copyItems: true) else { return nil } newAttributesArray.forEach { let distanceFromCenter = visibleCenterX - $0.center.x let absDistanceFromCenter = min(abs(distanceFromCenter), self.scaleOffset) let scale = absDistanceFromCenter * (self.scaleFactor - 1) / self.scaleOffset + 1 $0.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) let alpha = absDistanceFromCenter * (self.alphaFactor - 1) / self.scaleOffset + 1 $0.alpha = alpha } return newAttributesArray } func configureContentInset() { guard let collectionView = self.collectionView else { return } let inset = collectionView.bounds.size.width / 2 - itemSize.width / 2 collectionView.contentInset = UIEdgeInsets.init(top: 0, left: inset, bottom: 0, right: inset) collectionView.contentOffset = CGPoint(x: -inset, y: 0) } func resetContentInset() { guard let collectionView = self.collectionView else { return } collectionView.contentInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) } }
|