# SnapKit 12: UICollectionView + SnapKit — App Store Uslubidagi Vertikal + Gorizontal Layout

> **Holat:** 2026-yil oktabr. Ilovalar dizaynida eng ko'p so'raladigan va ko'p qo'llaniladigan tuzilma — bu App Store uslubidagi aralash scroll qilinadigan (vertikal va gorizontal) layoutlardir. Ushbu darsda biz SnapKit va `UICollectionViewCompositionalLayout` yordamida aynan shunday zamonaviy, murakkab va moslashuvchan dizaynlarni qanday yaratishni o'rganamiz.

## Mundarija
1. [UICollectionView + SnapKit asoslari](#basics)
2. [UICollectionViewFlowLayout + SnapKit Cell](#flow-layout)
3. [UICollectionViewCompositionalLayout (Zamonaviy usul)](#compositional)
4. [🔥 APP STORE LAYOUT (Asosiy qism)](#app-store)
5. [Nested Horizontal ScrollView yondashuvi (Eski usul)](#nested)
6. [Section Background Decoration](#decoration)
7. [Performance va Best Practices](#performance)
8. [Xulosa](#xulosa)
9. [Manbalar](#manbalar)

---

## 1. UICollectionView + SnapKit asoslari {#basics}

`UICollectionView` — bu iOS'da ma'lumotlarni turli xil grid (to'r) va murakkab ko'rinishlarda taqdim etish uchun eng kuchli vositadir. SnapKit yordamida CollectionView'ni ekranga joylashtirish juda oson, biroq asosiy ish `UICollectionViewCell` ichidagi elementlarni to'g'ri joylashtirishda.

### CollectionView'ni superview'ga yopishtirish

Odatda CollectionView butun ekranni egallaydi.

```swift
import UIKit
import SnapKit

class BasicCollectionViewController: UIViewController {
    
    private lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .systemBackground
        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        return cv
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        view.addSubview(collectionView)
    }
    
    private func setupConstraints() {
        collectionView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
}
```

### Cell ichida SnapKit bilan ishlash

Cell'ni loyihalashda **eng muhim qoida**: barcha subview'larni to'g'ridan-to'g'ri `cell` ga emas, balki `contentView` ga qo'shishingiz kerak!

```swift
import UIKit
import SnapKit

class BasicCell: UICollectionViewCell {
    
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupSubviews() {
        // MUHIM: contentView'ga qo'shiladi!
        contentView.addSubview(titleLabel)
        contentView.backgroundColor = .systemBlue
        contentView.layer.cornerRadius = 8
    }
    
    private func setupConstraints() {
        titleLabel.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(8)
        }
    }
}
```

---

## 2. UICollectionViewFlowLayout + SnapKit Cell {#flow-layout}

`UICollectionViewFlowLayout` oddiy grid va ro'yxatlar yasash uchun ishlatiladi. SnapKit cell ichidagi elementlarni joylashtirishga javobgar bo'lsa, FlowLayout cell'ning o'lchamini belgilaydi.

### Self-sizing (O'z-o'zini o'lchaydigan) Cell'lar

Ba'zida cell'ning o'lchami uning ichidagi content'ga bog'liq bo'lishi kerak. Buni amalga oshirish uchun FlowLayout'ga avtomatik o'lchamni beramiz.

```swift
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
// SnapKit yordamida cell ichida to'g'ri (tepadan pastga uzluksiz) constraint'lar yozilgan bo'lsa,
// cell o'zini o'zi to'g'ri o'lchaydi.
```

Grid yasashda ko'pincha bizga aniq o'lchamlar kerak bo'ladi (masalan, 2 ustunli dizayn). Bunda `sizeForItemAt` ishlatamiz.

```swift
extension BasicCollectionViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // Ekranni 2 ga bo'lamiz, o'rtadagi bo'shliqni ayirib tashlaymiz
        let padding: CGFloat = 16
        let width = (collectionView.bounds.width - padding * 3) / 2
        return CGSize(width: width, height: width * 1.2) // Proportsional balandlik
    }
}
```

---

## 3. UICollectionViewCompositionalLayout (Zamonaviy usul) {#compositional}

iOS 13+ da Apple murakkab layoutlarni yasash uchun `UICollectionViewCompositionalLayout` ni taqdim etdi. Bu yerda SnapKit va Layout'ning vazifalari aniq ajratilgan:

1. **CompositionalLayout**: Cell'larning guruhlanishi, section'lar va scroll yo'nalishini (vertikal/gorizontal) boshqaradi.
2. **SnapKit**: Faqatgina bir dona cell ichidagi UI elementlarning joylashuvini (`contentView` ga nisbatan) belgilaydi.

Compositional Layout ierarxiyasi quyidagicha:
```text
Layout
└── Section
    └── Group (gorizontal yoki vertikal)
        └── Item (sizning Cell'ingiz)
```

---

## 4. 🔥 APP STORE LAYOUT (Asosiy qism) {#app-store}

Endi eng qiziq qismiga keldik. Biz haqiqiy App Store "Today" yoki "Apps" sahifasiga o'xshash layout yaratamiz. 
Xususiyatlari:
- Asosiy scroll vertikal.
- Ichidagi ba'zi qismlar (section'lar) gorizontal scroll bo'ladi.
- DiffableDataSource ishlatamiz (xavfsiz data binding).

### A. Modellar (Item va Section)

```swift
import UIKit
import SnapKit

enum AppStoreSection: Int, CaseIterable {
    case banners = 0
    case featuredApps
    case categories
    case topCharts
}

enum AppStoreItem: Hashable {
    case banner(id: Int, imageUrl: String)
    case featuredApp(id: Int, name: String, category: String)
    case category(id: Int, name: String)
    case topChartApp(id: Int, rank: Int, name: String)
}
```

### B. View Controller va Compositional Layout

Bu yerda biz turli xil section'lar uchun turlicha layout'lar yozamiz.

```swift
class AppStoreViewController: UIViewController {
    
    private lazy var collectionView: UICollectionView = {
        let layout = createLayout()
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .systemGroupedBackground
        cv.register(BannerCell.self, forCellWithReuseIdentifier: BannerCell.identifier)
        cv.register(FeaturedAppCell.self, forCellWithReuseIdentifier: FeaturedAppCell.identifier)
        cv.register(CategoryCell.self, forCellWithReuseIdentifier: CategoryCell.identifier)
        cv.register(TopChartCell.self, forCellWithReuseIdentifier: TopChartCell.identifier)
        cv.register(SectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SectionHeaderView.identifier)
        return cv
    }()
    
    private var dataSource: UICollectionViewDiffableDataSource<AppStoreSection, AppStoreItem>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Apps"
        navigationController?.navigationBar.prefersLargeTitles = true
        setupSubviews()
        setupConstraints()
        configureDataSource()
        applyMockData()
    }
    
    private func setupSubviews() {
        view.addSubview(collectionView)
    }
    
    private func setupConstraints() {
        collectionView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
    
    // MARK: - Compositional Layout
    
    private func createLayout() -> UICollectionViewCompositionalLayout {
        return UICollectionViewCompositionalLayout { [weak self] (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
            guard let sectionType = AppStoreSection(rawValue: sectionIndex) else { return nil }
            
            switch sectionType {
            case .banners:
                return self?.createBannerSection()
            case .featuredApps:
                return self?.createFeaturedAppsSection()
            case .categories:
                return self?.createCategoriesSection()
            case .topCharts:
                return self?.createTopChartsSection()
            }
        }
    }
    
    // 1. Banners (Tepada katta rasm, paging)
    private func createBannerSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
        
        // Group height qat'iy 250 point
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(250))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        // Markazlashgan paging gorizontal scroll
        section.orthogonalScrollingBehavior = .groupPagingCentered
        section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 0, bottom: 24, trailing: 0)
        
        return section
    }
    
    // 2. Featured Apps (Gorizontal uzluksiz scroll)
    private func createFeaturedAppsSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        // Kengligi ekranning 90%i
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(80))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .continuous
        section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 24, trailing: 16)
        section.interGroupSpacing = 12
        
        // Header qo'shish
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        section.boundarySupplementaryItems = [header]
        
        return section
    }
    
    // 3. Categories (Vertikal grid - 2 ta ustun)
    private func createCategoriesSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(60))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 12, bottom: 24, trailing: 12)
        
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        section.boundarySupplementaryItems = [header]
        
        return section
    }
    
    // 4. Top Charts (Gorizontal paging, har guruhda 3 tadan)
    private func createTopChartsSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.33))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0)
        
        // Balandligi kattaroq, ichida 3 ta item vertikal joylashadi
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.92), heightDimension: .absolute(240))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 3)
        
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging
        section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 24, trailing: 16)
        section.interGroupSpacing = 16
        
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        section.boundarySupplementaryItems = [header]
        
        return section
    }
}
```

### C. SnapKit bilan yozilgan Cell'lar

Haqiqiy SnapKit sehri shu yerda ro'y beradi!

#### 1. Banner Cell

```swift
class BannerCell: UICollectionViewCell {
    static let identifier = "BannerCell"
    
    private let imageView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFill
        iv.backgroundColor = .systemTeal
        iv.layer.cornerRadius = 12
        iv.clipsToBounds = true
        return iv
    }()
    
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textColor = .white
        label.text = "Featured Title"
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupSubviews() {
        contentView.addSubview(imageView)
        imageView.addSubview(titleLabel) // Rasm ustiga matn
    }
    
    private func setupConstraints() {
        imageView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        titleLabel.snp.makeConstraints { make in
            make.leading.bottom.equalToSuperview().inset(16)
            make.trailing.equalToSuperview().inset(16)
        }
    }
}
```

#### 2. Featured App Cell (Icon + Name + Category + Get Button)

```swift
class FeaturedAppCell: UICollectionViewCell {
    static let identifier = "FeaturedAppCell"
    
    private let iconImageView: UIImageView = {
        let iv = UIImageView()
        iv.backgroundColor = .systemOrange
        iv.layer.cornerRadius = 16
        iv.layer.cornerCurve = .continuous
        iv.clipsToBounds = true
        return iv
    }()
    
    private let textStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 4
        stack.alignment = .leading
        return stack
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .semibold)
        return label
    }()
    
    private let categoryLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 14, weight: .regular)
        label.textColor = .secondaryLabel
        return label
    }()
    
    private let getButton: UIButton = {
        let btn = UIButton(type: .system)
        btn.setTitle("GET", for: .normal)
        btn.titleLabel?.font = .systemFont(ofSize: 15, weight: .bold)
        btn.backgroundColor = .systemGray5
        btn.layer.cornerRadius = 15
        return btn
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupSubviews() {
        contentView.addSubview(iconImageView)
        contentView.addSubview(textStackView)
        textStackView.addArrangedSubview(nameLabel)
        textStackView.addArrangedSubview(categoryLabel)
        contentView.addSubview(getButton)
    }
    
    private func setupConstraints() {
        iconImageView.snp.makeConstraints { make in
            make.leading.equalToSuperview()
            make.centerY.equalToSuperview()
            make.size.equalTo(64)
        }
        
        getButton.snp.makeConstraints { make in
            make.trailing.equalToSuperview()
            make.centerY.equalToSuperview()
            make.width.equalTo(72)
            make.height.equalTo(30)
        }
        
        textStackView.snp.makeConstraints { make in
            make.leading.equalTo(iconImageView.snp.trailing).offset(12)
            make.trailing.equalTo(getButton.snp.leading).offset(-12)
            make.centerY.equalToSuperview()
        }
    }
    
    func configure(name: String, category: String) {
        nameLabel.text = name
        categoryLabel.text = category
    }
}
```

> **Senior eslatmasi:** E'tibor bering, Layout (Compositional Layout) cell'ning balandligini o'rnatadi. SnapKit orqali esa biz ichki elementlarni markazlashtiramiz (`centerY`).

#### 3. Section Header (Supplementary View)

```swift
class SectionHeaderView: UICollectionReusableView {
    static let identifier = "SectionHeaderView"
    
    let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 22, weight: .bold)
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(titleLabel)
        titleLabel.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.bottom.equalToSuperview().inset(8)
        }
    }
    
    required init?(coder: NSCoder) { fatalError() }
}
```

### D. Data Source ulanishi

`AppStoreViewController` ichiga qaytib, Data Source qismini to'ldiramiz:

```swift
extension AppStoreViewController {
    private func configureDataSource() {
        dataSource = UICollectionViewDiffableDataSource<AppStoreSection, AppStoreItem>(collectionView: collectionView) { (cv, indexPath, item) -> UICollectionViewCell? in
            
            switch item {
            case .banner:
                let cell = cv.dequeueReusableCell(withReuseIdentifier: BannerCell.identifier, for: indexPath) as! BannerCell
                return cell
                
            case .featuredApp(_, let name, let cat):
                let cell = cv.dequeueReusableCell(withReuseIdentifier: FeaturedAppCell.identifier, for: indexPath) as! FeaturedAppCell
                cell.configure(name: name, category: cat)
                return cell
                
            case .category:
                let cell = cv.dequeueReusableCell(withReuseIdentifier: CategoryCell.identifier, for: indexPath) as! CategoryCell
                return cell
                
            case .topChartApp:
                let cell = cv.dequeueReusableCell(withReuseIdentifier: TopChartCell.identifier, for: indexPath) as! TopChartCell
                return cell
            }
        }
        
        dataSource.supplementaryViewProvider = { (cv, kind, indexPath) in
            let header = cv.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeaderView.identifier, for: indexPath) as! SectionHeaderView
            
            let section = AppStoreSection(rawValue: indexPath.section)
            switch section {
            case .featuredApps: header.titleLabel.text = "Tavsiya etiladi"
            case .categories: header.titleLabel.text = "Kategoriyalar"
            case .topCharts: header.titleLabel.text = "Top Chartlar"
            default: header.titleLabel.text = ""
            }
            
            return header
        }
    }
    
    private func applyMockData() {
        var snapshot = NSDiffableDataSourceSnapshot<AppStoreSection, AppStoreItem>()
        snapshot.appendSections(AppStoreSection.allCases)
        
        snapshot.appendItems([
            .banner(id: 1, imageUrl: ""),
            .banner(id: 2, imageUrl: ""),
            .banner(id: 3, imageUrl: "")
        ], toSection: .banners)
        
        snapshot.appendItems([
            .featuredApp(id: 1, name: "SnapKit Pro", category: "Developer Tools"),
            .featuredApp(id: 2, name: "Uzbek Dictionary", category: "Reference"),
            .featuredApp(id: 3, name: "SwiftUI Master", category: "Education")
        ], toSection: .featuredApps)
        
        // ... boshqa sectionlar uchun itemlar
        
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}
```

---

## 5. Nested Horizontal ScrollView yondashuvi (Eski usul) {#nested}

Compositional Layout paydo bo'lishidan oldin App Store dizayni juda murakkab usulda qilingan:
- Asosiy ekran `UITableView` dan iborat edi.
- Gorizontal scroll bo'ladigan har bir qator uchun maxsus `UITableViewCell` yasalib, uning ichiga `UICollectionView` joylashtirilgan.

```swift
class NestedTableViewCell: UITableViewCell {
    
    // Cell ichidagi Collection View
    private lazy var horizontalCollectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        return cv
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        contentView.addSubview(horizontalCollectionView)
        horizontalCollectionView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
            make.height.equalTo(200) // Qat'iy balandlik berish shart edi!
        }
    }
    // ...
}
```
> **Muhim:** Bu usulni bilish foydali, chunki eski (legacy) proyektlarda bunga ko'p duch kelasiz. Ammo yangi ilovalarda har doim **Compositional Layout** ishlating!

---

## 6. Section Background Decoration {#decoration}

Compositional Layout'ning yana bir zo'r xususiyati — ma'lum bir section'ning orqasiga fon (background view) qo'yish. SnapKit bu background view ichidagi dizaynni qilishda ishlatiladi.

```swift
// Layout ichida:
let section = NSCollectionLayoutSection(group: group)
let backgroundItem = NSCollectionLayoutDecorationItem.background(elementKind: "sectionBackground")
section.decorationItems = [backgroundItem]

// View Controller da ro'yxatdan o'tkazish:
layout.register(MyCustomBackgroundView.self, forDecorationViewOfKind: "sectionBackground")
```

---

## 7. Performance va Best Practices {#performance}

1. **Cell reuse va SnapKit:** 
   Agar siz cell ichidagi view'larni holatiga qarab butunlay boshqa joyga ko'chirishingiz kerak bo'lsa (masalan `prepareForReuse` da), doim ham yangidan constraint qo'shmang. `snp.remakeConstraints` o'rniga yashirin (hidden) qilib qo'yish yoki bitta reference'ni yangilash (`updateConstraints`) performanceni oshiradi.
   
2. **Prefetching:**
   Rasmlarni yuklash og'ir jarayon bo'lgani uchun, `collectionView.isPrefetchingEnabled = true` (default holati shunday) dan foydalanib rasmlarni barvaqt yuklash (`UICollectionViewDataSourcePrefetching` orqali) tavsiya etiladi.

---

## Xulosa {#xulosa}

UICollectionView va SnapKit birgalikda iOS'dagi eng kuchli layout duetlaridan birini hosil qiladi. **Compositional Layout** yordamida "Nima (qanday sectionlar) qayerda turishi kerakligini" belgilasak, **SnapKit** yordamida "O'sha narsaning ichki ko'rinishi qanday bo'lishi kerakligini" eng sodda tarzda yozamiz. 

App Store'ning bugungi sahifasi aynan shu 2 ta texnologiyaning uyg'unlashuvidan kelib chiqqan va bu bilimlar sizni haqiqiy Senior darajasidagi interfeyslar yozishga tayyorlaydi.

## Manbalar {#manbalar}

1. [Apple Documentation: UICollectionViewCompositionalLayout](https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout)
2. [SnapKit Documentation](https://snapkit.github.io/SnapKit/)
3. [Modern Collection Views in iOS (WWDC)](https://developer.apple.com/videos/play/wwdc2019/215/)
4. [Diffable Data Sources](https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource)
