import UIKit import PlaygroundSupport // --- Model --- // A simple data model that is hashable to work with diffable data sources. struct Item: Hashable { let id = UUID() let color: UIColor } // --- View Controller --- class CarouselViewController: UIViewController { // --- Properties --- private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! private var items: [Item] = [ Item(color: .systemRed), Item(color: .systemBlue), Item(color: .systemGreen), Item(color: .systemOrange), Item(color: .systemPurple), Item(color: .systemTeal) ] // --- Lifecycle Methods --- override func viewDidLoad() { super.viewDidLoad() setupCollectionView() configureDataSource() applySnapshot() } // --- Setup Methods --- private func setupCollectionView() { // Create a compositional layout for the collection view. let layout = createLayout(maximumCellWidth: view.bounds.width) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.backgroundColor = .systemBackground view.addSubview(collectionView) // Register the cell class. collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "itemCell") } // --- Layout Creation --- private func createLayout(maximumCellWidth: CGFloat) -> UICollectionViewLayout { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1), heightDimension: .estimated(200)// .fractionalHeight(1) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(200)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary section.visibleItemsInvalidationHandler = { items, offset, env in let visibleRect = CGRect(origin: CGPoint(x: offset.x, y: offset.y), size: env.container.contentSize) items.forEach { item in let itemFrame = item.frame let intersection = visibleRect.intersection(itemFrame) let visibleRatio = intersection.width / itemFrame.width // Clamp between 0.8 (partially visible) and 1.0 (fully visible) let minScale: CGFloat = 0.4 let scale = minScale + (1.0 - minScale) * max(0, min(1, visibleRatio)) item.alpha = scale } } return UICollectionViewCompositionalLayout(section: section) } // --- Data Source Configuration --- private func configureDataSource() { // Create a diffable data source to manage the collection view's data. dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in // Configure and return the cell for each item. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "itemCell", for: indexPath) cell.backgroundColor = item.color cell.layer.cornerRadius = 8 return cell } } private func applySnapshot() { // Create a snapshot of the current data and apply it to the data source. var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections(["0"]) snapshot.appendItems(items) dataSource.apply(snapshot, animatingDifferences: true) } } // --- Playground Execution --- // Set up the view controller to be displayed in the playground's live view. let viewController = CarouselViewController() PlaygroundPage.current.liveView = viewController