# UIKit 13: Zamonaviy UICollectionView — Compositional Layout, Diffable Data Source va Cell Registration

> **Holat:** 2026-yil sentabr. Eski `UICollectionViewFlowLayout`, `UICollectionViewDataSource` va `reloadData()` davri butunlay o'tmishda qoldi. Ilgari har bir dasturchi `numberOfItemsInSection` va `cellForItemAt` metodlarida index path'larni noto'g'ri hisoblab, mashhur `NSInternalInconsistencyException` (Invalid update: invalid number of items) crash'iga duch kelardi. Zamonaviy iOS dasturlashda 3 ta ustun yaratilgan: **Compositional Layout**, **Diffable Data Source** va **Cell Registration**. Ushbu darsda App Store darajasidagi murakkab, ko'p qatlamli va 120 FPS'da silliq ishlovchi interfeyslarni noldan qurish mexanikasi tahlil qilinadi.

## Mundarija

1. [Eski vs Zamonaviy Collection View: Nega inqilob yuz berdi?](#evolution)
2. [UICollectionViewCompositionalLayout: 4 pog'onali mental model](#compositional-layout)
3. [Orthogonal Scrolling: App Store uslubidagi gorizontal karusellar](#orthogonal-scrolling)
4. [Diffable Data Source & Snapshots: Myers diffing algoritmi](#diffable-data-source)
5. [Zamonaviy Cell Registration: String identifikatorlarsiz xavfsiz kod](#cell-registration)
6. [To'liq Amaliy Loyiha: App Store Feed ekrani (Banner + Grid + List)](#example)
7. [Senior Interview Savollari](#interview)
8. [Xulosa](#xulosa)
9. [Manbalar](#manbalar)

---

## 1. Eski vs Zamonaviy Collection View: Nega inqilob yuz berdi? {#evolution}

Ilgari bir ekranda ham gorizontal karusel, ham vertikal ro'yxat, ham 2 ustunli setka (grid) qilish uchun dasturchilar `UITableViewCell` ichiga alohida `UICollectionView` tiqishga (nested collection views) majbur bo'lishardi. Bu esa xotirani yeb, scroll paytida kuchli qotishlarni (scroll stutter) keltirib chiqarardi.

| Xususiyat | Eski usul (Legacy UIKit) | Zamonaviy usul (Modern UIKit) |
| :--- | :--- | :--- |
| **Layout** | `UICollectionViewFlowLayout` (Cheklangan, faqat bitta yo'nalish) | `UICollectionViewCompositionalLayout` (Item -> Group -> Section) |
| **Gorizontal scroll** | Subview ichiga yangi CollectionView qo'yish | `orthogonalScrollingBehavior` (Bitta satr kod!) |
| **Data Source** | `UICollectionViewDataSource` (Index path asosida, xavfli) | `UICollectionViewDiffableDataSource<Section, Item>` (Xavfsiz) |
| **Ma'lumot yangilash**| `reloadData()` (O'chib-yonish) yoki `performBatchUpdates` (Crash xavfi) | `apply(snapshot, animatingDifferences: true)` (Myers diffing O(N)) |
| **Cell ro'yxatdan o'tkazish** | `register(MyCell.self, forCellWithReuseIdentifier: "MyCell")` | `UICollectionView.CellRegistration<Cell, Item>` (Generik & Type-Safe) |

---

## 2. UICollectionViewCompositionalLayout: 4 pog'onali mental model {#compositional-layout}

Compositional Layout o'lchamlarni hisoblashda legoga o'xshaydi: kichik g'ishtchalardan yirik inshoot yasaladi.

```text
+───────────────────────────────────────────────────────────────+
| Layout (UICollectionViewCompositionalLayout)                  |
|  +─────────────────────────────────────────────────────────+  |
|  | Section 1 (NSCollectionLayoutSection)                   |  |
|  |   +───────────────────────────────────────────────────+ |  |
|  |   | Group (NSCollectionLayoutGroup)                   | |  |
|  |   |   +──────────────+  +──────────────+              | |  |
|  |   |   | Item 1       |  | Item 2       |              | |  |
|  |   |   | (Item)       |  | (Item)       |              | |  |
|  |   |   +──────────────+  +──────────────+              | |  |
|  |   +───────────────────────────────────────────────────+ |  |
|  +─────────────────────────────────────────────────────────+  |
+───────────────────────────────────────────────────────────────+
```

### O'lcham turlari (`NSCollectionLayoutDimension`):
1. **`.fractionalWidth(0.5)` / `.fractionalHeight(1.0)`:** Ota element o'lchamining foizi (masalan, 0.5 = 50%).
2. **`.absolute(100)`:** Aniq nuqtalarda berilgan qat'iy o'lcham (masalan, aynan 100 pt).
3. **`.estimated(60)`:** O'zgaruvchan (dinamik) o'lcham. Ichidagi kontentga (masalan, ko'p qatorli matnga) qarab o'zi kengayadi.

```swift
import UIKit

@MainActor
func createTwoColumnGridSection() -> NSCollectionLayoutSection {
    // 1. Item: Ota guruh enining 50%i, balandligi esa guruh balandligi bilan teng:
    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)

    // 2. Group: Ekran enining 100%i, balandligi qat'iy 120 pt:
    let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .absolute(120)
    )
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 2)

    // 3. Section: Guruhlar ketma-ketligi:
    let section = NSCollectionLayoutSection(group: group)
    section.contentInsets = NSDirectionalEdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
    return section
}
```

---

## 3. Orthogonal Scrolling: App Store uslubidagi gorizontal karusellar {#orthogonal-scrolling}

Vertikal aylanuvchi ro'yxat ichida alohida bir bo'limni gorizontal aylanuvchi karuselga aylantirish uchun endi nested collection view kerak emas! Bor-yo'g'i bitta xususiyat o'rnatiladi:

```swift
import UIKit

@MainActor
func createBannerCarouselSection() -> NSCollectionLayoutSection {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .fractionalHeight(1.0)
    )
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(0.85), // Kartaning cheti ko'rinib tursin
        heightDimension: .absolute(180)
    )
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)
    section.interGroupSpacing = 16
    section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 16, bottom: 20, trailing: 16)

    // SEHR: Bir satr kod bilan gorizontal silliq karusel:
    section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary

    return section
}
```

### `orthogonalScrollingBehavior` variantlari:
* **`.none`:** Standart vertikal harakat.
* **`.continuous`:** Oddiy erkin gorizontal scroll.
* **`.continuousGroupLeadingBoundary`:** (App Store standarti) Scroll to'xtaganda eng yaqin guruhning chap chegarasiga avtomatik qadalanadi (magnetic snap).
* **`.paging`:** Butun sahifa bo'lib o'tadi.

---

## 4. Diffable Data Source & Snapshots: Myers diffing algoritmi {#diffable-data-source}

Eski `UICollectionViewDataSource`da ma'lumotlar massivini o'zgartirib `reloadData()` chaqirilsa, barcha ko'rinib turgan kataklar ekrandan o'chib, qaytadan chizilardi — natijada ekranda o'chib-yonish (flickering) bo'lardi. `performBatchUpdates` esa elementlar indeksini `1` taga adashtirsangiz ham butun ilovani crash qilardi.

**`UICollectionViewDiffableDataSource`** bu muammoni butunlay hal qildi:
1. Siz ma'lumotlar massiviga emas, **`NSDiffableDataSourceSnapshot`** obyektiga tayanasiz.
2. Snapshot ichiga bo'limlar va ularning elementlarini solasiz.
3. `apply(snapshot, animatingDifferences: true)` chaqirilganda, Apple'ning ichki **Myers Diffing algoritmi (O(N))** eski snapshot bilan yangi snapshot o'rtasidagi farqni topadi.
4. Faqat qo'shilgan, o'chirilgan yoki surilgan kataklar xirurgik aniqlikda, mukammal animatsiya bilan o'zgaradi!

```text
[ Eski Snapshot ]  ──────── Myers Diffing (O(N)) ────────► [ Yangi Snapshot ]
  Item A                     Faqat o'zgargan Item C           Item A
  Item B ── (O'chirildi)      uchirilib, Item D qo'shiladi    Item C
  Item C                                                      Item D (Yangi!)
```

> **Qoida:** Snapshot'dagi `SectionIdentifierType` va `ItemIdentifierType` albatta **`Hashable`** va **`Sendable`** bo'lishi shart!

---

## 5. Zamonaviy Cell Registration: String identifikatorlarsiz xavfsiz kod {#cell-registration}

Ilgari kataklarni string identifikatorlar bilan ro'yxatdan o'tkazib, keyin majburiy kasting (`as! MyCell`) qilardik:
`collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! MyCell`
Bitta harf xato ketsa, dastur darhol crash bo'lardi.

iOS 14+ da paydo bo'lgan **`UICollectionView.CellRegistration`** generik bo'lib, xavfsizlikni 100% kafolatlaydi:

```swift
import UIKit

struct ProductItem: Hashable, Sendable {
    let id: UUID
    let title: String
    let price: String
}

@MainActor
func makeProductCellRegistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, ProductItem> {
    UICollectionView.CellRegistration<UICollectionViewListCell, ProductItem> { cell, indexPath, item in
        var content = cell.defaultContentConfiguration()
        content.text = item.title
        content.secondaryText = item.price
        content.image = UIImage(systemName: "cart.fill")
        cell.contentConfiguration = content
    }
}
```

---

## 6. To'liq Amaliy Loyiha: App Store Feed ekrani (Banner + Grid + List) {#example}

Quyida barcha 3 ta zamonaviy texnologiyani birlashtirgan, to'liq ishlovchi feed ekrani:

```swift
import UIKit

// 1. Modellar (Hashable va Sendable bo'lishi shart)
public enum FeedSection: Int, CaseIterable, Hashable, Sendable {
    case banners
    case categories
    case topApps
}

public struct FeedItem: Hashable, Sendable {
    public let id: String
    public let title: String
    public let subtitle: String
    public let iconName: String

    public init(id: String, title: String, subtitle: String, iconName: String) {
        self.id = id
        self.title = title
        self.subtitle = subtitle
        self.iconName = iconName
    }
}

// 2. Asosiy ViewController
public final class AppStoreFeedViewController: UIViewController {

    private var collectionView: UICollectionView!
    private var dataSource: UICollectionViewDiffableDataSource<FeedSection, FeedItem>!

    public override func viewDidLoad() {
        super.viewDidLoad()
        title = "Discover"
        view.backgroundColor = .systemBackground

        configureCollectionView()
        configureDataSource()
        loadInitialData()
    }

    private func configureCollectionView() {
        collectionView = UICollectionView(
            frame: view.bounds,
            collectionViewLayout: createLayout()
        )
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.backgroundColor = .systemBackground
        view.addSubview(collectionView)
    }

    // 3. Murakkab 3 xil bo'limli Compositional Layout
    private func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { [weak self] sectionIndex, layoutEnvironment in
            guard let self, let section = FeedSection(rawValue: sectionIndex) else { return nil }

            switch section {
            case .banners:
                return self.createBannerSection()
            case .categories:
                return self.createCategoryGridSection()
            case .topApps:
                return self.createTopAppsListSection(layoutEnvironment: layoutEnvironment)
            }
        }
    }

    private func createBannerSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalHeight(1.0)
        )
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        let groupSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(0.88),
            heightDimension: .absolute(190)
        )
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 14
        section.contentInsets = NSDirectionalEdgeInsets(top: 12, leading: 16, bottom: 20, trailing: 16)
        // Karusel effekti
        section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
        return section
    }

    private func createCategoryGridSection() -> 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, repeatingSubitem: item, count: 2)

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 12, bottom: 20, trailing: 12)
        return section
    }

    private func createTopAppsListSection(
        layoutEnvironment: NSCollectionLayoutEnvironment
    ) -> NSCollectionLayoutSection {
        var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        config.headerMode = .none
        return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
    }

    // 4. Type-safe Cell Registration va Diffable Data Source
    private func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, FeedItem> {
            cell, indexPath, item in
            var content = cell.defaultContentConfiguration()
            content.text = item.title
            content.secondaryText = item.subtitle
            content.image = UIImage(systemName: item.iconName)
            cell.contentConfiguration = content
        }

        dataSource = UICollectionViewDiffableDataSource<FeedSection, FeedItem>(
            collectionView: collectionView
        ) { collectionView, indexPath, item in
            collectionView.dequeueConfiguredReusableCell(
                using: cellRegistration,
                for: indexPath,
                item: item
            )
        }
    }

    // 5. Snapshot yuklash
    private func loadInitialData() {
        var snapshot = NSDiffableDataSourceSnapshot<FeedSection, FeedItem>()
        snapshot.appendSections([.banners, .categories, .topApps])

        snapshot.appendItems([
            FeedItem(id: "b1", title: "Yilning eng yaxshi ilovalari", subtitle: "2026 tanlovi", iconName: "star.fill"),
            FeedItem(id: "b2", title: "Apple Design Awards", subtitle: "G'oliblar ro'yxati", iconName: "trophy.fill")
        ], toSection: .banners)

        snapshot.appendItems([
            FeedItem(id: "c1", title: "Ta'lim", subtitle: "Kitoblar va fan", iconName: "book.fill"),
            FeedItem(id: "c2", title: "Moliya", subtitle: "Bank va investitsiya", iconName: "banknote.fill")
        ], toSection: .categories)

        snapshot.appendItems([
            FeedItem(id: "a1", title: "SwiftKey AI", subtitle: "Aqlli klaviatura", iconName: "keyboard"),
            FeedItem(id: "a2", title: "Focus Master", subtitle: "Vaqtni rejalashtirish", iconName: "clock.badge.checkmark")
        ], toSection: .topApps)

        dataSource.apply(snapshot, animatingDifferences: false)
    }
}
```

---

## 7. Senior Interview Savollari {#interview}

### 1. Myers Diffing algoritmi qanday ishlaydi va uning murakkabligi qanday?
**Javob:** Myers Diffing algoritmi ikkita ketma-ketlik (eski va yangi snapshot) o'rtasidagi eng qisqa tahrir masofasini (Shortest Edit Distance) topadi. Uning o'rtacha vaqt murakkabligi $O(N + D^2)$ bo'lib (bu yerda $N$ — elementlar soni, $D$ — farqlar soni), juda tez hisoblanadi va foydalanuvchiga freym yo'qotishlarsiz (hitch-free) silliq animatsiyani taqdim etadi.

### 2. Nega `DiffableDataSource` bilan `reloadData()` ishlatilmaydi?
**Javob:** `reloadData()` barcha ko'rinib turgan kataklarni darhol yo'q qilib, ularni noldan yaratadi. Bu esa xotirani ortiqcha band qiladi, layout o'lchamlarini qayta hisoblashga majbur qiladi va ekranning miltillashiga (flickering) sabab bo'ladi. Diffable DataSource esa `apply()` orqali faqat o'zgargan kataklargina yangilanishini ta'minlaydi.

### 3. `CellRegistration` ning eski identifikatorli ro'yxatdan o'tkazishdan asosiy ustunligi nimada?
**Javob:** 
1. **Type-Safety:** Majburiy string identifikatorlar va `as! MyCell` kastingiga chek qo'yildi.
2. **Kapsulatsiya:** Katakni sozlash (configuration logic) bitta blok ichida saqlanadi, bu kodni modulli va oson testlanadigan qiladi.
3. **Xotira tejash:** Keraksiz nib yuklash yoki xato identifikator bilan crash bo'lish xavfi nolga tushirilgan.

---

## 8. Xulosa {#xulosa}

* `UICollectionViewCompositionalLayout` — o'lchamlarni deklarativ ravishda **Item -> Group -> Section -> Layout** pog'onalari bo'yicha belgilash imkonini beradi.
* Bitta bo'limni gorizontal aylanuvchi qilish uchun **`orthogonalScrollingBehavior`** ning o'zi yetarli.
* **`DiffableDataSource`** va **`Snapshots`** orqali `NSInternalInconsistencyException` crash'lari butunlay unutiladi.
* **`CellRegistration`** orqali qat'iy tiplangan va xavfsiz kataklar yaratiladi.

---

## 9. Manbalar {#manbalar}

* [Apple Documentation: UICollectionViewCompositionalLayout](https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout)
* [Apple Documentation: UICollectionViewDiffableDataSource](https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource)
* [WWDC: Advances in UICollectionView](https://developer.apple.com/videos/play/wwdc2020/10026/)
* [WWDC: Modern cell configuration](https://developer.apple.com/videos/play/wwdc2020/10027/)
