Open onmyway133 opened 5 years ago
final class CarouselLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil } guard let collectionView = collectionView else { return nil } return attributes.map({ transform(collectionView: collectionView, attribute: $0) }) } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } private func transform(collectionView: UICollectionView, attribute: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let a = attribute let width = collectionView.frame.size.width let itemOffset = a.center.x - collectionView.contentOffset.x let middleOffset = (itemOffset / width) - 0.5 change( width: collectionView.frame.size.width, attribute: attribute, middleOffset: middleOffset ) return attribute } private func change(width: CGFloat, attribute: UICollectionViewLayoutAttributes, middleOffset: CGFloat) { let alpha: CGFloat = 0.8 let itemSpacing: CGFloat = 0.21 let scale: CGFloat = 1.0 let scaleFactor = scale - 0.1 * abs(middleOffset) let scaleTransform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor) let translationX = -(width * itemSpacing * middleOffset) let translationTransform = CGAffineTransform(translationX: translationX, y: 0) attribute.alpha = 1.0 - abs(middleOffset) + alpha attribute.transform = translationTransform.concatenating(scaleTransform) } }
How to use
let layout = CarouselLayout() layout.scrollDirection = .horizontal layout.sectionInset = .zero layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0
We can inset cell content and use let scale: CGFloat = 1.0 to avoid scaling down center cell
let scale: CGFloat = 1.0
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) } }
When I tap on cell, how to zoom it fullscreen? Either single cell or all cells... I need to zoom fullscreen and then back to carousel.
Based on AnimatedCollectionViewLayout
How to use
We can inset cell content and use
let scale: CGFloat = 1.0
to avoid scaling down center cellBased on CityCollectionViewFlowLayout