# SnapKit 10: UIStackView + SnapKit Kombinatsiyasi — Tez, Oson va Moslashuvchan Layoutlar

> **Holat:** Ushbu darslik 2026-yil oktabr oyi holatiga ko'ra eng so'nggi iOS va SnapKit amaliyotlari asosida tayyorlangan.

## Mundarija
1. [UIStackView nima va nima uchun kerak?](#stackview-nima)
2. [SnapKit bilan UIStackView joylash](#snapkit-bilan-joylash)
3. [Nested StackView'lar (Ichma-ich)](#nested-stackview)
4. [Dinamik StackView](#dinamik-stackview)
5. [Custom Spacing (iOS 11+)](#custom-spacing)
6. [Amaliy loyiha 1: Settings ekrani](#loyiha-settings)
7. [Amaliy loyiha 2: Tag Cloud / Chip Layout](#loyiha-tag-cloud)
8. [StackView vs Manual Constraints qachon qaysi biri?](#taqqoslash)
9. [Xulosa](#xulosa)
10. [Manbalar](#manbalar)

## 1. UIStackView nima va nima uchun kerak? {#stackview-nima}

`UIStackView` — bir qancha view'larni gorizontal yoki vertikal qatorda avtomatik tartibga soluvchi kuchli konteyner view hisoblanadi. Unda har bir ichki element (`arrangedSubview`) uchun alohida constraint yozish shart emas; StackView ularni quyidagi xususiyatlar (properties) asosida o'zi boshqaradi:

- **axis**: `.horizontal` (yonma-yon) yoki `.vertical` (ketma-ket, yuqoridan pastga).
- **distribution**: Elementlar o'q bo'ylab qanday yoyilishini belgilaydi (`.fill`, `.fillEqually`, `.fillProportionally`, `.equalSpacing`, `.equalCentering`).
- **alignment**: Elementlar o'qqa nisbatan perpendikulyar joylashishini belgilaydi (`.fill`, `.center`, `.leading`, `.trailing`, `.top`, `.bottom`).
- **spacing**: Elementlar orasidagi bo'shliq miqdori (points).

```text
+-------------------+
| UIStackView       |
|  [ View 1 ]       |
|    | spacing      |
|  [ View 2 ]       |
|    | spacing      |
|  [ View 3 ]       |
+-------------------+
```

## 2. SnapKit bilan UIStackView joylash {#snapkit-bilan-joylash}

StackView ham xuddi oddiy `UIView` kabi superview'ga joylanadi va **SnapKit** aynan shu yerda juda qulay:

1. StackView'ning *o'zini* ekranga (superview'ga) SnapKit orqali yopishtirasiz.
2. Ichidagi (`arrangedSubviews`) elementlarga alohida position (x, y) constraintlari **BERMANG** — buni stack boshqaradi!
3. **LEKIN** `arrangedSubviews`'larning o'lchamlarini (width, height) SnapKit orqali bersangiz bo'ladi.

> **Senior eslatmasi:** StackView ichidagi elementga `.top`, `.left` kabi constraint bermang. Faqatgina `.width` yoki `.height` bersangiz bo'ladi.

```swift
import UIKit
import SnapKit

class StackViewExampleVC: UIViewController {
    
    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 16
        stack.alignment = .fill
        stack.distribution = .fill
        return stack
    }()
    
    private let button1: UIButton = {
        let btn = UIButton(type: .system)
        btn.setTitle("Tugma 1", for: .normal)
        btn.backgroundColor = .systemBlue
        return btn
    }()
    
    private let button2: UIButton = {
        let btn = UIButton(type: .system)
        btn.setTitle("Tugma 2", for: .normal)
        btn.backgroundColor = .systemGreen
        return btn
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        view.backgroundColor = .white
        view.addSubview(stackView)
        
        stackView.addArrangedSubview(button1)
        stackView.addArrangedSubview(button2)
    }
    
    private func setupConstraints() {
        // 1. StackView'ning o'zini joylashtiramiz
        stackView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.leading.trailing.equalToSuperview().inset(20)
        }
        
        // 2. Elementlarning faqatgina balandligini (height) belgilaymiz
        button1.snp.makeConstraints { make in
            make.height.equalTo(50)
        }
        
        button2.snp.makeConstraints { make in
            make.height.equalTo(50)
            // Yoki stackview'ga nisbatan kenglikni proporsional berish mumkin:
            // make.width.equalTo(stackView).multipliedBy(0.5) 
        }
    }
}
```

## 3. Nested StackView'lar (Ichma-ich) {#nested-stackview}

Murakkab layoutlarni yaratishda bitta StackView yetarli bo'lmaydi. Biz bitta katta vertikal stack ichiga kichkina gorizontal stacklarni joylashimiz mumkin. Masalan, Forma yoki Profil ekrani.

```text
[ Vertical StackView ]
  |-- [ Horizontal StackView: Icon | Label | Switch ]
  |-- [ Horizontal StackView: Icon | Label | Textfield ]
```

```swift
import UIKit
import SnapKit

class ProfileRowView: UIView {
    
    private let horizontalStack: UIStackView = {
        let stack = UIStackView()
        stack.axis = .horizontal
        stack.spacing = 12
        stack.alignment = .center
        return stack
    }()
    
    private let iconImageView: UIImageView = {
        let iv = UIImageView()
        iv.backgroundColor = .lightGray
        iv.layer.cornerRadius = 8
        return iv
    }()
    
    private let titleLabel: UILabel = {
        let lbl = UILabel()
        lbl.font = .systemFont(ofSize: 16, weight: .medium)
        return lbl
    }()
    
    private let toggleSwitch = UISwitch()
    
    init(title: String) {
        super.init(frame: .zero)
        titleLabel.text = title
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupSubviews() {
        addSubview(horizontalStack)
        horizontalStack.addArrangedSubview(iconImageView)
        horizontalStack.addArrangedSubview(titleLabel)
        horizontalStack.addArrangedSubview(toggleSwitch)
    }
    
    private func setupConstraints() {
        horizontalStack.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(16)
        }
        
        iconImageView.snp.makeConstraints { make in
            make.size.equalTo(30)
        }
        
        // titleLabel va toggleSwitch o'lchamlari avtomatik aniqlanadi (intrinsic content size)
    }
}
```

## 4. Dinamik StackView {#dinamik-stackview}

`UIStackView` ning eng kuchli tomonlaridan biri — runtime davomida (dastur ishlash jarayonida) elementlarni qo'shish, olib tashlash va yashirish (`isHidden`). Siz elementni yashirganingizda (`isHidden = true`), StackView qolgan elementlarni avtomatik ravishda qayta joylashtiradi (collapse qiladi).

### Animatsiya bilan yashirish:

```swift
UIView.animate(withDuration: 0.3) {
    // 3-elementni yashiramiz
    self.stackView.arrangedSubviews[2].isHidden = true
    self.view.layoutIfNeeded()
}
```

### Dinamik qo'shish va olib tashlash:

```swift
// Oxiriga qo'shish
stackView.addArrangedSubview(newView)

// Aniq indeksga qo'shish
stackView.insertArrangedSubview(newView, at: 1)

// Olib tashlash (Ikkala qadam ham muhim!)
stackView.removeArrangedSubview(oldView)
oldView.removeFromSuperview()
```

## 5. Custom Spacing (iOS 11+) {#custom-spacing}

Odatda `stackView.spacing` barcha elementlar o'rtasida bir xil bo'shliq qoldiradi. Agar qaysidir elementdan keyin ko'proq joy tashlash kerak bo'lsa, `setCustomSpacing(_:after:)` metodi ishlatiladi:

```swift
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let contentText = UILabel()

stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(subtitleLabel)
stackView.addArrangedSubview(contentText)

// Title va subtitle orasida default spacing ishlaydi
// Lekin subtitle va contentText orasida 24 point joy qoldiramiz:
stackView.setCustomSpacing(24, after: subtitleLabel)
```

## 6. Amaliy loyiha 1: Settings ekrani {#loyiha-settings}

Endi o'rganganlarimizni birlashtirib, oddiy Sozlamalar (Settings) ekranini yaratamiz. Biz buni ScrollView > Vertical StackView > Custom Row View uslubida qilamiz.

```swift
import UIKit
import SnapKit

class SettingsViewController: UIViewController {
    
    private let scrollView = UIScrollView()
    
    private let mainStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 0 // Elementlar orasida chiziq (separator) chizamiz
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Sozlamalar"
        setupSubviews()
        setupConstraints()
        populateSettings()
    }
    
    private func setupSubviews() {
        view.backgroundColor = .systemBackground
        view.addSubview(scrollView)
        scrollView.addSubview(mainStackView)
    }
    
    private func setupConstraints() {
        scrollView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        mainStackView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
            make.width.equalTo(scrollView) // Vertical scroll uchun kenglik fix qilinishi kerak
        }
    }
    
    private func populateSettings() {
        let settings = ["Profil", "Xavfsizlik", "Bildirishnomalar", "Tizim haqida"]
        
        for (index, title) in settings.enumerated() {
            let row = SettingsRowView(title: title)
            mainStackView.addArrangedSubview(row)
            
            // Oxirgi elementdan tashqari hammasiga chiziq qo'shamiz
            if index < settings.count - 1 {
                let separator = UIView()
                separator.backgroundColor = .separator
                mainStackView.addArrangedSubview(separator)
                
                separator.snp.makeConstraints { make in
                    make.height.equalTo(0.5)
                }
            }
        }
    }
}

// Row component
class SettingsRowView: UIView {
    private let titleLabel = UILabel()
    private let chevronIcon = UIImageView(image: UIImage(systemName: "chevron.right"))
    
    init(title: String) {
        super.init(frame: .zero)
        titleLabel.text = title
        chevronIcon.tintColor = .tertiaryLabel
        
        let stack = UIStackView(arrangedSubviews: [titleLabel, chevronIcon])
        stack.axis = .horizontal
        stack.alignment = .center
        
        addSubview(stack)
        stack.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16))
        }
    }
    
    required init?(coder: NSCoder) { fatalError() }
}
```

## 7. Amaliy loyiha 2: Tag Cloud / Chip Layout {#loyiha-tag-cloud}

Agar sizda "Swift", "UIKit", "SnapKit" kabi turli uzunlikdagi tag'lar bo'lsa va ular ekranga sig'magan joyda keyingi qatorga tushishi kerak bo'lsa, **UIStackView bunga mos kelmaydi**. StackView faqat bir qatorda yoki bir ustunda joylashtira oladi (wrapping yo'q).

Bunday layout (Tag Cloud) uchun **UICollectionView** (FlowLayout bilan) yoki maxsus o'rab oluvchi (wrapping) logic ishlatish kerak. SnapKit esa cell ichidagi elementlarni joylash uchun xizmat qiladi:

```swift
import UIKit
import SnapKit

class TagCell: UICollectionViewCell {
    let titleLabel = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.backgroundColor = .systemBlue.withAlphaComponent(0.1)
        contentView.layer.cornerRadius = 12
        titleLabel.textColor = .systemBlue
        
        contentView.addSubview(titleLabel)
        titleLabel.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12))
        }
    }
    
    required init?(coder: NSCoder) { fatalError() }
}
```
> *Cell o'lchamini `UICollectionViewDelegateFlowLayout` da label'ning intrinsic size'iga qarab berasiz.*

## 8. StackView vs Manual Constraints qachon qaysi biri? {#taqqoslash}

| Xususiyat | UIStackView + SnapKit | Faqat SnapKit (Manual Constraints) |
| :--- | :--- | :--- |
| **Qo'llanilishi** | Yonma-yon, ketma-ket, formalar, ro'yxatlar | Bir-birining ustiga chiqadigan (overlapping), erkin joylashuv |
| **Moslashuvchanlik** | Elementlar osongina yashiriladi va animatsiya qilinadi | Har bir constraint'ni manual update yoki deactivate qilish kerak |
| **Tezlik (Dev)** | Juda tez va o'qish oson | Ko'p chiziq kod, murakkablik yuqori bo'lishi mumkin |
| **Performance** | Bir nechta ichma-ich stack'lar CPU uchun ozgina og'irroq | To'g'ridan-to'g'ri bog'lanish tufayli bir oz samaraliroq |

> **Muhim:** Har doim oddiy layoutlar uchun `UIStackView` dan boshlang. Qachonki StackView siz xohlagan dizaynni chiqara olmasa, undan keyingina sof SnapKit constraintlariga o'ting.

## Xulosa {#xulosa}

- `UIStackView` va `SnapKit` — iOS dasturlashdagi eng kuchli juftlik.
- StackView elementlarni taqsimlaydi, SnapKit esa ularning aniq o'lchamlarini (width/height) yoki stack'ning o'zining superview'dagi o'rnini belgilaydi.
- `isHidden` orqali elementlarni yashirish StackView'da mukammal ishlaydi va avtomatik qisqaradi.
- StackView bilan "qotib qolgan" (rigid) layoutlardan qochamiz va barcha ekran o'lchamlariga mos dizayn yaratamiz.

## Manbalar {#manbalar}

- [Apple Documentation: UIStackView](https://developer.apple.com/documentation/uikit/uistackview)
- [SnapKit GitHub Repository](https://github.com/SnapKit/SnapKit)
- [Hacking with Swift: How to use UIStackView](https://www.hackingwithswift.com)
