Last active
October 23, 2024 20:20
-
-
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
This file contains 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
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 | |
} | |
} | |
This file contains 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
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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simulator.Screen.Recording.-.iPhone.12.Pro.-.2024-10-23.at.23.18.29.mp4
Video clip