Skip to content

Instantly share code, notes, and snippets.

@Coder-ACJHP
Last active October 23, 2024 20:20
Show Gist options
  • Save Coder-ACJHP/a1eb1ac44b7f69b2131f9fd6e81ce0b8 to your computer and use it in GitHub Desktop.
Save Coder-ACJHP/a1eb1ac44b7f69b2131f9fd6e81ce0b8 to your computer and use it in GitHub Desktop.
Android 14 Samsung calendar like carousel collectionView (iOS 12, UIKit) video clip in first comment
class CarouselFlowLayout: UICollectionViewFlowLayout {
let scaleFactor: CGFloat = 0.7 // Minimum scale for side cells
override func prepare() {
super.prepare()
guard let collectionView else { return }
scrollDirection = .horizontal
// Define cell size
itemSize = collectionView.bounds.inset(by: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)).size
// Calculate left right spaces
let leftRightInset = (collectionView.bounds.width - itemSize.width) / 2
// Adjust the minimum line spacing dynamically based on the scale factor
minimumLineSpacing = calculateMinimumLineSpacing(forScale: scaleFactor)
// Center cells in the screen
sectionInset = .init(top: .zero, left: leftRightInset, bottom: .zero, right: leftRightInset)
}
private func calculateMinimumLineSpacing(forScale scale: CGFloat) -> CGFloat {
// Calculate the width reduction due to scaling
let scaledWidth = itemSize.width * scale
// Calculate the spacing so that the smaller cells appear snug next to each other
let spacing = (itemSize.width - scaledWidth) / 2.2
return -spacing // Negative spacing ensures tight alignment
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributesArray = super.layoutAttributesForElements(in: rect) else { return nil }
let centerX = collectionView!.contentOffset.x + collectionView!.frame.size.width / 2
// Iterate over the copied attributes, not the originals
let copiedAttributesArray = attributesArray.map { $0.copy() as! UICollectionViewLayoutAttributes }
for attributes in copiedAttributesArray {
let distance = abs(attributes.center.x - centerX)
let normalizedDistance = distance / (collectionView!.frame.width / 2)
let scale = 1 - (1 - scaleFactor) * min(1, normalizedDistance)
attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
attributes.alpha = scale // Adjust opacity based on scale
}
return copiedAttributesArray
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true // Ensure the layout updates during scrolling
}
}
class CarouselViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: CarouselFlowLayout())
collectionView.backgroundColor = .clear
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.decelerationRate = .fast // Smooth scrolling effect
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: String(describing: UICollectionViewCell.self))
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
configureCollectionView()
}
private func configureCollectionView() {
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension CarouselViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 10 }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellId = String(describing: UICollectionViewCell.self)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.contentView.layer.cornerRadius = 30
cell.contentView.backgroundColor = .white
return cell
}
}
extension CarouselViewController: UIScrollViewDelegate {
// Snap item to center
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
let cellWidthIncludingSpacing = layout.itemSize.width + layout.minimumLineSpacing
let estimatedIndex = targetContentOffset.pointee.x / cellWidthIncludingSpacing
let index: CGFloat
if velocity.x > 0 { index = ceil(estimatedIndex)
} else if velocity.x < 0 { index = floor(estimatedIndex)
} else { index = round(estimatedIndex)
}
targetContentOffset.pointee = CGPoint(x: index * cellWidthIncludingSpacing, y: 0)
}
}
@Coder-ACJHP
Copy link
Author

Simulator.Screen.Recording.-.iPhone.12.Pro.-.2024-10-23.at.23.18.29.mp4

Video clip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment