Last active
July 25, 2020 12:58
-
-
Save hsleedevelop/c1676c8ea727a9fe97e3d2ab85f9227b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UPCarouselFlowLayout.swift | |
// UPCarouselFlowLayoutDemo | |
// | |
// Created by Paul Ulric on 23/06/2016. | |
// Copyright © 2016 Paul Ulric. All rights reserved. | |
// | |
// https://github.com/ink-spot/UPCarouselFlowLayout/blob/master/UPCarouselFlowLayout/UPCarouselFlowLayout.swift | |
import UIKit | |
public enum UPCarouselFlowLayoutSpacingMode { | |
case fixed(spacing: CGFloat) | |
case overlap(visibleOffset: CGFloat) | |
} | |
open class UPCarouselFlowLayout: UICollectionViewFlowLayout { | |
fileprivate struct LayoutState { | |
var size: CGSize | |
var direction: UICollectionView.ScrollDirection | |
func isEqual(_ otherState: LayoutState) -> Bool { | |
return self.size.equalTo(otherState.size) && self.direction == otherState.direction | |
} | |
} | |
@IBInspectable open var sideItemScale: CGFloat = 0.6 | |
@IBInspectable open var sideItemAlpha: CGFloat = 0.6 | |
@IBInspectable open var sideItemShift: CGFloat = 0.0 | |
open var spacingMode = UPCarouselFlowLayoutSpacingMode.fixed(spacing: 40) | |
fileprivate var state = LayoutState(size: CGSize.zero, direction: .horizontal) | |
override open func prepare() { | |
super.prepare() | |
let currentState = LayoutState(size: self.collectionView!.bounds.size, direction: self.scrollDirection) | |
if !self.state.isEqual(currentState) { | |
self.setupCollectionView() | |
self.updateLayout() | |
self.state = currentState | |
} | |
} | |
fileprivate func setupCollectionView() { | |
guard let collectionView = self.collectionView else { return } | |
if collectionView.decelerationRate != UIScrollView.DecelerationRate.fast { | |
collectionView.decelerationRate = UIScrollView.DecelerationRate.fast | |
} | |
} | |
fileprivate func updateLayout() { | |
guard let collectionView = self.collectionView else { return } | |
let collectionSize = collectionView.bounds.size | |
let isHorizontal = (self.scrollDirection == .horizontal) | |
let yInset = (collectionSize.height - self.itemSize.height) / 2 | |
let xInset = (collectionSize.width - self.itemSize.width) / 2 | |
self.sectionInset = UIEdgeInsets.init(top: yInset, left: xInset, bottom: yInset, right: xInset) | |
let side = isHorizontal ? self.itemSize.width : self.itemSize.height | |
let scaledItemOffset = (side - side*self.sideItemScale) / 2 | |
switch self.spacingMode { | |
case .fixed(let spacing): | |
self.minimumLineSpacing = spacing - scaledItemOffset | |
case .overlap(let visibleOffset): | |
let fullSizeSideItemOverlap = visibleOffset + scaledItemOffset | |
let inset = isHorizontal ? xInset : yInset | |
self.minimumLineSpacing = inset - fullSizeSideItemOverlap | |
} | |
} | |
override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { | |
return true | |
} | |
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { | |
guard let superAttributes = super.layoutAttributesForElements(in: rect), | |
let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes] | |
else { return nil } | |
return attributes.map({ self.transformLayoutAttributes($0) }) | |
} | |
fileprivate func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { | |
guard let collectionView = self.collectionView else { return attributes } | |
let isHorizontal = (self.scrollDirection == .horizontal) | |
let collectionCenter = isHorizontal ? collectionView.frame.size.width/2 : collectionView.frame.size.height/2 | |
let offset = isHorizontal ? collectionView.contentOffset.x : collectionView.contentOffset.y | |
let normalizedCenter = (isHorizontal ? attributes.center.x : attributes.center.y) - offset | |
let maxDistance = (isHorizontal ? self.itemSize.width : self.itemSize.height) + self.minimumLineSpacing | |
let distance = min(abs(collectionCenter - normalizedCenter), maxDistance) | |
let ratio = (maxDistance - distance)/maxDistance | |
let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha | |
let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale | |
let shift = (1 - ratio) * self.sideItemShift | |
attributes.alpha = alpha | |
attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) | |
attributes.zIndex = Int(alpha * 10) | |
if isHorizontal { | |
attributes.center.y = attributes.center.y + shift | |
} else { | |
attributes.center.x = attributes.center.x + shift | |
} | |
return attributes | |
} | |
override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { | |
guard let collectionView = collectionView , !collectionView.isPagingEnabled, | |
let layoutAttributes = self.layoutAttributesForElements(in: collectionView.bounds) | |
else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset) } | |
let isHorizontal = (self.scrollDirection == .horizontal) | |
let midSide = (isHorizontal ? collectionView.bounds.size.width : collectionView.bounds.size.height) / 2 | |
let proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x : proposedContentOffset.y) + midSide | |
var targetContentOffset: CGPoint | |
if isHorizontal { | |
let closest = layoutAttributes.sorted { abs($0.center.x - proposedContentOffsetCenterOrigin) < abs($1.center.x - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes() | |
targetContentOffset = CGPoint(x: floor(closest.center.x - midSide), y: proposedContentOffset.y) | |
} | |
else { | |
let closest = layoutAttributes.sorted { abs($0.center.y - proposedContentOffsetCenterOrigin) < abs($1.center.y - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes() | |
targetContentOffset = CGPoint(x: proposedContentOffset.x, y: floor(closest.center.y - midSide)) | |
} | |
return targetContentOffset | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment