# SnapKit 14: Katta Amaliy Loyiha va Debugging — E-Commerce Ekrani, .labeled(), Xotira va Best Practices

> **Holat:** 2026-yil oktabr holatiga ko'ra dolzarb va tekshirilgan. Barcha kodlar Swift 6, iOS 15+ va SnapKit 5.7+ da ishlaydi.

Ushbu so'nggi va eng katta **Capstone** darsimizda SnapKit-dagi **debugging** usullari, **xotira (memory)** xavflari, **best practice'lar** va **katta amaliy loyiha** sifatida haqiqiy *E-Commerce (Elektron tijorat)* ekranini noldan yaratamiz. 

## Mundarija
1. [Debugging SnapKit Constraintlar](#debugging)
2. [Xotira va Retain Cycle xavflari](#xotira)
3. [SnapKit Best Practices (Professional qoidalar)](#best-practices)
4. [KATTA AMALIY LOYIHA: E-Commerce Mahsulot Sahifasi](#loyiha)
5. [Unit Test Yozish (Bonus)](#unit-test)
6. [Kurs Yakuniy Xulosa](#yakun)
7. [Xulosa](#xulosa)
8. [Manbalar](#manbalar)

---

## 1. Debugging SnapKit Constraintlar {#debugging}

Dastur tuzishda constraint'lar doim ham birinchi urinishda to'g'ri bo'lmasligi mumkin. Auto Layout qizil rangli uzun va tushunarsiz xatolarni (console warnings) chiqaradi. Buni SnapKit orqali oson hal qilish yo'llari mavjud.

### `.labeled()` orqali nomlash
Constraint'ga nom berish Auto Layout xatolarida aynan qaysi constraint buzilayotganini topishga yordam beradi:

```swift
import UIKit
import SnapKit

let profileImageView = UIImageView()
view.addSubview(profileImageView)

profileImageView.snp.makeConstraints { make in
    // Nomi Auto Layout loglarida chiqadi
    make.top.equalToSuperview().offset(20).labeled("profileAvatarTop")
    make.width.height.equalTo(100).labeled("profileAvatarSize")
    make.centerX.equalToSuperview().labeled("profileAvatarCenterX")
}
```

Agar ushbu constraint buzilsa (conflict), konsolda `NSLayoutConstraint` xatosi ichida `profileAvatarTop` nomi yaqqol ko'rinadi.

### LLDB da ko'rish
Dastur to'xtatilganda (breakpoint orqali) quyidagi komandani konsolga yozib view'ning constraintlarini ko'rishingiz mumkin:
```text
po profileImageView.snp.layoutConstraints
```

### Ambiguous Layout (Noaniq joylashuv)
Ba'zan view qayerda turishi kerakligini yoki qanday o'lchamda bo'lishini yetarlicha constraint berilmaganligi sababli Auto Layout aniqlay olmaydi.
Buni aniqlash uchun:
```swift
if profileImageView.hasAmbiguousLayout {
    print("profileImageView ning layouti noaniq!")
}

// Yoki vizual tekshirish uchun (debug vaqtida view'ni har xil holatga o'zgartirib ko'radi)
profileImageView.exerciseAmbiguityInLayout()
```

Bundan tashqari Xcode'ning **Debug View Hierarchy** tugmasi orqali (View Debugger) dastur ishlayotganida UI'ni 3D formatda va barcha xatolarni o'ng paneldan (Issue Navigator) ko'rishingiz mumkin.

---

## 2. Xotira va Retain Cycle xavflari {#xotira}

SnapKit ishlatayotganda `makeConstraints`, `updateConstraints`, `remakeConstraints` bloklarida retain cycle bo'ladimi?

> **Senior eslatmasi:** SnapKit closure'larida `[weak self]` **KERAK EMAS!** 

SnapKit bu closure'larni saqlab qolmaydi (capture qilib turmaydi). U darhol bajariladi va tugaydi.
Ammo agar siz closure ichida view'ning property'lariga murojaat qilsangiz, retain cycle xavfi umuman yo'q.

```swift
class MyView: UIView {
    let someView = UIView()
    var offsetValue: CGFloat = 20

    func setup() {
        addSubview(someView)
        someView.snp.makeConstraints { make in
            // [weak self] yozish shart emas
            make.top.equalToSuperview().offset(self.offsetValue)
        }
    }
}
```

Biroq, agar closure ichida nimanidir asinxron ravishda bajarsangiz (bu SnapKit'da bo'lmaydi), shunda weak ishlatiladi. `Constraint` obyekti view'ning o'ziga tegishli bo'lganligi uchun unga ishora (reference) saqlashda ham `weak` ishlatilmaydi:
```swift
// To'g'ri
var topConstraint: Constraint? 
```

Xotira sizib chiqishi (Memory Leak) bor yo'qligini Xcode'dagi **Memory Graph Debugger** orqali tekshirishni unutmang.

---

## 3. SnapKit Best Practices (Professional qoidalar) {#best-practices}

14 dars davomida o'rganganlarimiz asosida professional standartlar ro'yxati:

| Qoida turi | Nima qilish kerak / Nima qilmaslik kerak |
| --- | --- |
| ✅ **DOIM** | `UITableViewCell` yoki `UICollectionViewCell` larda constraintlarni `contentView` ga bering. `self` ga emas. |
| ✅ **DOIM** | `addSubview` ni doim `makeConstraints` dan **OLDIN** chaqiring. Aks holda crash bo'ladi. |
| ✅ **DOIM** | Dinamik (o'zgarib turadigan) constraint'larga `.priority(999)` yoki undan pastroq bering. Agar conflict bo'lsa, `.required` (1000) dasturni ishdan chiqarmaydi (xato yozadi xolos). |
| ✅ **DOIM** | Faqat doimiyni (constant / offset) o'zgartirish uchun `updateConstraints` ishlating. |
| ✅ **DOIM** | Layout butunlay o'zgarganda (masalan, portrait'dan landscape'ga) `remakeConstraints` ishlating. Yoki shunchaki `Constraint` turidagi o'zgaruvchini yangilang. |
| ✅ **DOIM** | Muhim UI elementlarga va murakkab joylarga `.labeled("name")` qo'shing. |
| ❌ **QILMANG** | Bitta view'ga ikki marta alohida `makeConstraints` chaqirmang. Ikkinchisini `update` yoki `remake` qiling. |
| ❌ **QILMANG** | `translatesAutoresizingMaskIntoConstraints` ni qo'lda yozib o'tirmang. SnapKit `make...` chaqirilganda buni o'zi false qiladi. |
| ❌ **QILMANG** | Auto Layout ishlagan view'larga (SnapKit qilingan) qo'lda `frame` bermang. Bu ziddiyat yaratadi. |

---

## 4. KATTA AMALIY LOYIHA: E-Commerce Mahsulot Sahifasi {#loyiha}

Ushbu amaliyotda biz to'liq ishlaydigan **E-Commerce Product Page** yaratamiz. U Sticky bottom bar, scroll view ichida turli xil ma'lumotlar va custom view'lardan iborat.

### A. Asosiy Controller va ScrollView sozlamalari

```swift
import UIKit
import SnapKit

@MainActor
final class ProductViewController: UIViewController {
    
    // UI Komponentlari
    private let scrollView = UIScrollView()
    private let contentView = UIView()
    
    // Custom Viewlar
    private let carouselView = ProductImageCarousel()
    private let infoView = ProductInfoView()
    private let variantView = VariantSelectorView()
    private let descriptionView = DescriptionSectionView()
    private let stickyBottomBar = StickyBottomBar()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        view.backgroundColor = .white
        
        // Navigation bar sozlamalari
        title = "Mahsulot Tafsilotlari"
        let cartButton = UIBarButtonItem(image: UIImage(systemName: "cart"), style: .plain, target: nil, action: nil)
        navigationItem.rightBarButtonItem = cartButton
        
        // Asosiy viewlarni qo'shish
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        view.addSubview(stickyBottomBar)
        
        // Kontentni scrollView ichiga qo'shish
        contentView.addSubview(carouselView)
        contentView.addSubview(infoView)
        contentView.addSubview(variantView)
        contentView.addSubview(descriptionView)
        
        setupConstraints()
    }
    
    private func setupConstraints() {
        // Sticky Bottom Bar - doim pastda
        stickyBottomBar.snp.makeConstraints { make in
            make.leading.trailing.bottom.equalToSuperview().labeled("bottomBarEdges")
            // Balandligi o'z-o'zidan ichidagi elementlardan aniqlanadi
        }
        
        // ScrollView - pastki qismi stickyBar'ning tepasigacha
        scrollView.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview().labeled("scrollViewTopEdges")
            make.bottom.equalTo(stickyBottomBar.snp.top).labeled("scrollViewBottom")
        }
        
        // ContentView
        contentView.snp.makeConstraints { make in
            make.edges.equalToSuperview().labeled("contentEdges")
            make.width.equalTo(scrollView.snp.width).labeled("contentWidth")
        }
        
        // 1. Carousel
        carouselView.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview().labeled("carouselEdges")
            // 3:4 aspect ratio (balandlik = kenglik * 1.33)
            make.height.equalTo(carouselView.snp.width).multipliedBy(1.33).labeled("carouselHeight")
        }
        
        // 2. Product Info (Narx, sarlavha)
        infoView.snp.makeConstraints { make in
            make.top.equalTo(carouselView.snp.bottom).offset(16).labeled("infoTop")
            make.leading.trailing.equalToSuperview().inset(16)
        }
        
        // 3. Variant Selector
        variantView.snp.makeConstraints { make in
            make.top.equalTo(infoView.snp.bottom).offset(24).labeled("variantTop")
            make.leading.trailing.equalToSuperview().inset(16)
        }
        
        // 4. Description
        descriptionView.snp.makeConstraints { make in
            make.top.equalTo(variantView.snp.bottom).offset(24).labeled("descriptionTop")
            make.leading.trailing.equalToSuperview().inset(16)
            make.bottom.equalToSuperview().offset(-24).labeled("descriptionBottom") // Eng muhimi: Contentview height ni belgilaydi
        }
    }
}
```

### B. Custom View komponentlar (Qisqartirilgan, ammo to'liq ishlaydigan)

**ProductInfoView.swift**
```swift
import UIKit
import SnapKit

class ProductInfoView: UIView {
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.text = "Premium Yozgi Krossovka - Qora/Oq rangda, 2026 Model"
        label.font = .systemFont(ofSize: 20, weight: .semibold)
        label.numberOfLines = 0
        return label
    }()
    
    private let priceLabel: UILabel = {
        let label = UILabel()
        label.text = "$129.99"
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textColor = .systemRed
        return label
    }()
    
    private let originalPriceLabel: UILabel = {
        let label = UILabel()
        let attributeString = NSMutableAttributedString(string: "$199.99")
        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: NSRange(location: 0, length: attributeString.length))
        label.attributedText = attributeString
        label.textColor = .gray
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    private func setup() {
        addSubview(titleLabel)
        addSubview(priceLabel)
        addSubview(originalPriceLabel)
        
        priceLabel.snp.makeConstraints { make in
            make.top.leading.equalToSuperview()
        }
        
        originalPriceLabel.snp.makeConstraints { make in
            make.leading.equalTo(priceLabel.snp.trailing).offset(8)
            make.bottom.equalTo(priceLabel.snp.bottom)
        }
        
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(priceLabel.snp.bottom).offset(12)
            make.leading.trailing.equalToSuperview()
            make.bottom.equalToSuperview() // View balandligini yopish
        }
    }
}
```

**StickyBottomBar.swift**
```swift
import UIKit
import SnapKit

class StickyBottomBar: UIView {
    private let separatorView = UIView()
    private let stepper = UIStepper()
    private let addToCartButton = UIButton(type: .system)
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder: NSCoder) { fatalError() }
    
    private func setup() {
        backgroundColor = .white
        separatorView.backgroundColor = .systemGray5
        
        addToCartButton.setTitle("Savatga qo'shish", for: .normal)
        addToCartButton.backgroundColor = .systemBlue
        addToCartButton.setTitleColor(.white, for: .normal)
        addToCartButton.layer.cornerRadius = 8
        addToCartButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
        
        addSubview(separatorView)
        addSubview(stepper)
        addSubview(addToCartButton)
        
        separatorView.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.height.equalTo(1)
        }
        
        stepper.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(16)
            make.leading.equalToSuperview().offset(16)
            // Safe area hisobga olinadi, masalan iPhone 14 Pro
            make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-16)
        }
        
        addToCartButton.snp.makeConstraints { make in
            make.centerY.equalTo(stepper.snp.centerY)
            make.leading.equalTo(stepper.snp.trailing).offset(16)
            make.trailing.equalToSuperview().offset(-16)
            make.height.equalTo(50)
        }
    }
}
```

> **Muhim:** Barcha custom view'larni yasashda **tepadan pastga** constraintlarni ulang va **eng so'nggi elementning bottom'ini** o'z superview'sining bottom'iga ulang. Shunda custom view ichidagi kontentga qarab avtomatik o'z balandligini aniqlaydi (Self-sizing).

---

## 5. Unit Test Yozish (Bonus) {#unit-test}

Constraint'lar kutilgandek ishlayotganligini kod orqali tekshirish (Unit testing) iOS dasturlashda juda muhim.
Tasavvur qiling, tugmaning kengligi `100` ga teng ekanini test qilmoqchisiz:

```swift
import XCTest
import SnapKit
@testable import MyApp

final class MyViewTests: XCTestCase {
    
    @MainActor
    func testButtonConstraints() {
        // 1. Arrange
        let testView = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 812)) // iPhone screen
        let button = UIButton()
        
        testView.addSubview(button)
        button.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.equalTo(150)
            make.height.equalTo(50)
        }
        
        // 2. Act
        // Auto layout'ni majburiy ishga tushirish (frame'larni hisoblash uchun)
        testView.setNeedsLayout()
        testView.layoutIfNeeded()
        
        // 3. Assert
        XCTAssertEqual(button.frame.width, 150)
        XCTAssertEqual(button.frame.height, 50)
        XCTAssertEqual(button.center, CGPoint(x: 375/2, y: 812/2))
    }
}
```

Bunga qo'shimcha ravishda Snapshot Testing (masalan, `swift-snapshot-testing` kutubxonasi) orqali butun ekranning rasmini olib tekshirish layoutlar uchun ancha samarali.

---

## 6. Kurs Yakuniy Xulosa {#yakun}

Siz ushbu kurs orqali 14 ta dars davomida SnapKit bilan ishlash bo'yicha to'liq professionallikka erishdingiz! 

**Biz nimalarni o'rgandik?**
1. **SnapKit asoslari:** `snp.makeConstraints`, anchorlar, offsetlar, insetlar.
2. **Kengaytirilgan layout:** Prioritetlar, Update va Remake constraints, StackView, safeAreaLayoutGuide va ScrollView.
3. **Animation va dinamiklik:** Constraint turidagi o'zgaruvchilarni saqlash, `.offset()` ni animatsiya ichida `layoutIfNeeded()` bilan o'zgartirish.
4. **TableView/CollectionView:** Avtomatik balandlik (self-sizing), contentView.
5. **Debugging va Best Practices:** Hozirgi darsimizdagi to'liq E-Commerce ekrani va arxitektura qoidalari.

**Keyingi qadamlar:**
- Apple endi SwiftUI'ni faol targ'ib qilmoqda. Lekin o'n minglab katta loyihalar va murakkab dizaynlar hali ham UIKit va SnapKit'da yozilgan.
- Siz endi barcha murakkab ekranlarni erkin yoza olasiz. UIKit va SwiftUI'ni birgalikda (interoperability) ishlata bilish qobiliyatini rivojlantiring (masalan `UIHostingController` orqali).
- Hali ham Native `NSLayoutConstraint` (Anchorlar) asoslari va ishlash mexanizmini to'liq tushunib oling, chunki SnapKit ularning ustidagi shunchaki chiroyli "qopqoq" (wrapper) xolos.

---

## Xulosa {#xulosa}

**KURS YAKUNIY XULOSASI:**
SnapKit – iOS dunyosidagi eng mashhur, mustahkam va barqaror Auto Layout kutubxonasidir. Katta E-commerce ilovasini yaratish orqali ko'rdikki, har qanday dizaynni modullarga bo'lib (custom views), ularni scroll view ichiga joylash SnapKit yordamida juda kam kod bilan hal etiladi. `.labeled()` funksiyasi yordamida UI xatolarini zudlik bilan aniqlash, memory retain cycle'larsiz bloklarni erkin ishlatish – developerlarga xotirjamlik va tezlik beradi. Siz ushbu kurs davomida SnapKit noldan boshlab professional darajagacha o'zlashtirdingiz, endi har qanday kompaniyaning UIKit loyihasida erkin va ishonch bilan ishlay olasiz.

## Manbalar {#manbalar}
- [SnapKit Rasmiy Hujjatlari](https://snapkit.github.io/SnapKit/docs/)
- [Apple Auto Layout Documentation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html)
- [WWDC: High Performance Auto Layout](https://developer.apple.com/videos/play/wwdc2018/220/)
- [Debugging Auto Layout - Apple](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/DebuggingTricksandTips.html)
