# SnapKit 9: UIScrollView Sirlari — SnapKit bilan Scrollable Content, Dinamik Formalar va Klaviatura

> **Holat:** 2026-yil oktabr holatiga ko'ra, SnapKit yordamida UIScrollView bilan ishlash va Auto Layout muammolarini hal qilish iOS dasturchilar uchun eng kerakli ko'nikmalardan biridir.

Ushbu darsda biz iOS'dagi eng qiyin mavzulardan biri — `UIScrollView`'ni SnapKit bilan ishlatish sirlarini o'rganamiz.

## Mundarija
1. [UIScrollView + Auto Layout = Bosh og'rig'i?](#bosh-ogrigi)
2. [SnapKit bilan Vertikal Scroll](#vertikal-scroll)
3. [Gorizontal Scroll](#gorizontal-scroll)
4. [Amaliy loyiha 1: Registration Form](#registration-form)
5. [Klaviatura bilan ishlash (Juda Muhim!)](#keyboard-handling)
6. [Amaliy loyiha 2: Gorizontal Image Scroller](#gorizontal-scroller)
7. [Nested ScrollView (Advanced)](#nested-scrollview)
8. [Xulosa](#xulosa)
9. [Manbalar](#manbalar)

---

## 1. UIScrollView + Auto Layout = Bosh og'rig'i? {#bosh-ogrigi}

UIScrollView bilan Auto Layout ishlatganda dasturchilar eng ko'p **"Ambiguous scrollable content size"** xatosiga duch kelishadi. Sababi: `UIScrollView` Auto Layout'dan **ikkita turli o'lchamni** kutadi:
1. **FRAME (Ramka):** ScrollView ekranda qancha joy egallaydi?
2. **CONTENT (Ichki qism):** ScrollView ichidagi narsalarning umumiy o'lchami qancha? (Ushbu o'lcham scroll qilinishi uchun qancha masofa kerakligini aniqlaydi).

iOS 11 dan boshlab Apple bu muammoni osonlashtirish uchun ikkita maxsus "Guide" qo'shgan:
- `contentLayoutGuide` — ichki kontent o'lchamini belgilash uchun.
- `frameLayoutGuide` — UIScrollView'ning ekrandagi o'z ramkasi (frame) uchun.

```text
+-----------------------------------+  <-- ScrollView Frame (frameLayoutGuide)
|                                   |      (Masalan: Ekranga yopishtirilgan)
|  +-----------------------------+  |  <-- Content View (contentLayoutGuide)
|  | Element 1 (height: 200)     |  |      (Masalan: width = Frame width,
|  +-----------------------------+  |                height = 800)
|  | Element 2 (height: 300)     |  |
|  +-----------------------------+  |
|  | Element 3 (height: 300)     |  |
|  +-----------------------------+  |
+-----------------------------------+
```

> **Senior eslatmasi:** Har doim bitta asosiy `contentView` (masalan, `UIView` yoki `UIStackView`) yarating, uni ScrollView ichiga qo'ying va barcha boshqa elementlarni shu `contentView` ichiga joylashtiring.

| Guide | SnapKit'da ishlatilishi | Vazifasi |
|-------|-------------------------|----------|
| `contentLayoutGuide` | `scrollView.contentLayoutGuide.snp...` | Content View'ning chekkalarini scroll view ichida belgilash. |
| `frameLayoutGuide` | `scrollView.frameLayoutGuide.snp...` | Content View kengligini (yoki balandligini) cheklash. |

---

## 2. SnapKit bilan Vertikal Scroll {#vertikal-scroll}

Vertikal scroll (tepaga-pastga aylantirish) eng ko'p ishlatiladigan turdir. Bunda muhim jihat shuki, **gorizontal scroll bo'lib ketmasligi uchun** `contentView`ning kengligini (width) `scrollView`ning kengligiga (`frameLayoutGuide`) tenglashimiz kerak! Va eng muhimi, eng pastdagi element `contentView`ning tagiga (bottom) ulanishi **SHART**.

```swift
import UIKit
import SnapKit

class VerticalScrollViewController: UIViewController {
    
    private let scrollView = UIScrollView()
    private let contentView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        // Misol uchun elementlar qo'shamiz
        var previousView: UIView? = nil
        
        for i in 0..<10 {
            let box = UIView()
            box.backgroundColor = i % 2 == 0 ? .systemBlue : .systemRed
            contentView.addSubview(box)
            
            box.snp.makeConstraints { make in
                make.left.right.equalToSuperview().inset(20)
                make.height.equalTo(150)
                
                if let previous = previousView {
                    make.top.equalTo(previous.snp.bottom).offset(20)
                } else {
                    make.top.equalToSuperview().offset(20) // Birinchi element
                }
                
                // Eng oxirgi element bottom'ga ulanishi shart!
                if i == 9 {
                    make.bottom.equalToSuperview().offset(-20)
                }
            }
            previousView = box
        }
    }
    
    private func setupConstraints() {
        // 1. ScrollView'ni ekranga yopishtiramiz
        scrollView.snp.makeConstraints { make in
            make.edges.equalTo(view.safeAreaLayoutGuide)
        }
        
        // 2. ContentView'ni contentLayoutGuide'ga yopishtiramiz
        contentView.snp.makeConstraints { make in
            make.edges.equalTo(scrollView.contentLayoutGuide)
            
            // 3. MUHIM: Vertikal scroll bo'lishi uchun width'ni frameLayoutGuide'ga tenglaymiz
            make.width.equalTo(scrollView.frameLayoutGuide)
        }
    }
}
```

> **Muhim:** Agar oxirgi elementning `.bottom` qismi `contentView`ning `.bottom` qismiga ulanmasa, iOS `contentView` qancha balandlikda bo'lishi kerakligini bilolmaydi va scroll ishlamaydi.

---

## 3. Gorizontal Scroll {#gorizontal-scroll}

Gorizontal scroll qilinganda, xuddi yuqoridagidek, faqat endi **balandlik (height)** `frameLayoutGuide`ga tenglanishi kerak va elementlar chapdan o'ngga qarab tiziladi.

```swift
import UIKit
import SnapKit

class HorizontalScrollViewController: UIViewController {
    
    private let scrollView = UIScrollView()
    private let contentView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        var previousView: UIView? = nil
        
        for i in 0..<10 {
            let box = UIView()
            box.backgroundColor = .systemGreen
            contentView.addSubview(box)
            
            box.snp.makeConstraints { make in
                make.top.bottom.equalToSuperview().inset(20)
                make.width.equalTo(200)
                
                if let previous = previousView {
                    make.left.equalTo(previous.snp.right).offset(20)
                } else {
                    make.left.equalToSuperview().offset(20)
                }
                
                if i == 9 {
                    make.right.equalToSuperview().offset(-20) // Eng o'ngdagi element ulanadi
                }
            }
            previousView = box
        }
    }
    
    private func setupConstraints() {
        scrollView.snp.makeConstraints { make in
            make.left.right.equalToSuperview()
            make.centerY.equalToSuperview()
            make.height.equalTo(250)
        }
        
        contentView.snp.makeConstraints { make in
            make.edges.equalTo(scrollView.contentLayoutGuide)
            // MUHIM: Gorizontal scroll uchun height cheklanadi
            make.height.equalTo(scrollView.frameLayoutGuide)
        }
    }
}
```

---

## 4. Amaliy loyiha 1: Registration Form {#registration-form}

Endi haqiqiy vaziyatga yaqin Registration formani tuzamiz. StackView va ScrollView birgalikda eng kuchli vositaga aylanadi.

```swift
import UIKit
import SnapKit

class RegistrationViewController: UIViewController {
    
    private let scrollView = UIScrollView()
    private let contentView = UIView()
    
    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 20
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        contentView.addSubview(stackView)
        
        // Form qismlari
        let profileImage = UIImageView()
        profileImage.backgroundColor = .lightGray
        profileImage.layer.cornerRadius = 50
        
        let nameField = UITextField()
        nameField.borderStyle = .roundedRect
        nameField.placeholder = "Name"
        
        let bioTextView = UITextView()
        bioTextView.layer.borderColor = UIColor.gray.cgColor
        bioTextView.layer.borderWidth = 1
        
        let registerButton = UIButton(type: .system)
        registerButton.setTitle("Register", for: .normal)
        registerButton.backgroundColor = .systemBlue
        registerButton.setTitleColor(.white, for: .normal)
        registerButton.layer.cornerRadius = 8
        
        stackView.addArrangedSubview(profileImage)
        stackView.addArrangedSubview(nameField)
        stackView.addArrangedSubview(bioTextView)
        stackView.addArrangedSubview(registerButton)
        
        // Elementlarning balandligi
        profileImage.snp.makeConstraints { make in
            make.width.height.equalTo(100)
        }
        stackView.alignment = .center // rasmni markazlash uchun
        
        nameField.snp.makeConstraints { make in
            make.width.equalToSuperview()
            make.height.equalTo(44)
        }
        
        bioTextView.snp.makeConstraints { make in
            make.width.equalToSuperview()
            make.height.equalTo(150)
        }
        
        registerButton.snp.makeConstraints { make in
            make.width.equalToSuperview()
            make.height.equalTo(50)
        }
        
        // Constraints
        scrollView.snp.makeConstraints { make in
            make.edges.equalTo(view.safeAreaLayoutGuide)
        }
        
        contentView.snp.makeConstraints { make in
            make.edges.equalTo(scrollView.contentLayoutGuide)
            make.width.equalTo(scrollView.frameLayoutGuide)
        }
        
        stackView.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(20)
        }
    }
}
```

---

## 5. Klaviatura bilan ishlash (Juda Muhim!) {#keyboard-handling}

Forma yaratilganda, ekranning pastki qismidagi text field'larga yozish paytida **klaviatura ularni to'sib qo'yishi** mumkin. Buni oldini olish uchun klaviatura balandligiga qarab `UIScrollView`ning `contentInset`'ini o'zgartirishimiz kerak.

```swift
import UIKit
import SnapKit

class KeyboardScrollViewController: UIViewController {
    
    private let scrollView = UIScrollView()
    private let contentView = UIView()
    private var activeField: UITextField?
    
    // ... setupSubviews xuddi oldingidek ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupKeyboardObservers()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    private func setupKeyboardObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    @objc private func keyboardWillShow(notification: NSNotification) {
        guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
        
        // ScrollView'ning pastiga klaviatura balandligicha probel qo'shamiz
        let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
        scrollView.contentInset = contentInsets
        scrollView.scrollIndicatorInsets = contentInsets
        
        // Aktiv text field'ni klaviatura to'sib qolmasligi uchun u tomonga surish:
        if let activeField = activeField {
            var rect = self.view.frame
            rect.size.height -= keyboardSize.height
            
            if !rect.contains(activeField.frame.origin) {
                scrollView.scrollRectToVisible(activeField.frame, animated: true)
            }
        }
    }
    
    @objc private func keyboardWillHide(notification: NSNotification) {
        // Klaviatura yo'qolganda insets'ni asliga qaytaramiz
        let contentInsets = UIEdgeInsets.zero
        scrollView.contentInset = contentInsets
        scrollView.scrollIndicatorInsets = contentInsets
    }
}
```

> **Senior eslatmasi:** SnapKit orqali `bottom constraint`ni yangilash ham mumkin (`bottomConstraint?.update(offset: -keyboardSize.height)`), biroq UIScrollView bilan ishlaganda shunchaki `contentInset` va `scrollIndicatorInsets`ni o'zgartirish ancha samarali va silliq ishlaydi.

---

## 6. Amaliy loyiha 2: Gorizontal Image Scroller {#gorizontal-scroller}

Gorizontal scroll bilan rasmlar galereyasini qilish (Paging enabled).

```swift
import UIKit
import SnapKit

class ImageScrollerViewController: UIViewController {
    
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.isPagingEnabled = true
        scroll.showsHorizontalScrollIndicator = false
        return scroll
    }()
    
    private let contentView = UIView()
    private let pageControl = UIPageControl()
    
    let images: [UIColor] = [.red, .green, .blue, .yellow, .purple]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        
        setupUI()
        scrollView.delegate = self
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        view.addSubview(pageControl)
        
        pageControl.numberOfPages = images.count
        
        scrollView.snp.makeConstraints { make in
            make.center.width.equalToSuperview()
            make.height.equalTo(300)
        }
        
        contentView.snp.makeConstraints { make in
            make.edges.equalTo(scrollView.contentLayoutGuide)
            make.height.equalTo(scrollView.frameLayoutGuide)
        }
        
        pageControl.snp.makeConstraints { make in
            make.top.equalTo(scrollView.snp.bottom).offset(20)
            make.centerX.equalToSuperview()
        }
        
        var previousImage: UIView? = nil
        
        for (index, color) in images.enumerated() {
            let imageView = UIView()
            imageView.backgroundColor = color
            contentView.addSubview(imageView)
            
            imageView.snp.makeConstraints { make in
                make.top.bottom.equalToSuperview()
                make.width.equalTo(view.snp.width) // Har bir rasm ekran kengligidek
                
                if let previous = previousImage {
                    make.left.equalTo(previous.snp.right)
                } else {
                    make.left.equalToSuperview()
                }
                
                if index == images.count - 1 {
                    make.right.equalToSuperview()
                }
            }
            previousImage = imageView
        }
    }
}

extension ImageScrollerViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let pageIndex = round(scrollView.contentOffset.x / view.frame.width)
        pageControl.currentPage = Int(pageIndex)
    }
}
```

---

## 7. Nested ScrollView (Advanced) {#nested-scrollview}

Ba'zida vertikal ScrollView ichida gorizontal ScrollView (masalan, App Store'dagi karusellar) qo'yish kerak bo'ladi. Auto Layout (xususan SnapKit) bilan buni amalga oshirishda ikkita narsaga e'tibor qaratish kerak:
1. Ikkala ScrollView'da ham `contentView` to'g'ri sozlangan bo'lishi.
2. Ichki gorizontal ScrollView'ning `height` o'lchami qat'iy belgilangan (`equalTo(Constant)`) bo'lishi.

```swift
// Vertikal StackView yoki ContentView ichiga qo'shilayotganda:
let horizontalScroll = UIScrollView()
contentView.addSubview(horizontalScroll)

horizontalScroll.snp.makeConstraints { make in
    make.left.right.equalToSuperview()
    make.height.equalTo(200) // MUSTAHKAM O'LCHAM!
    // top va bottom boshqa elementlarga ulanadi
}

let innerContentView = UIView()
horizontalScroll.addSubview(innerContentView)

innerContentView.snp.makeConstraints { make in
    make.edges.equalTo(horizontalScroll.contentLayoutGuide)
    make.height.equalTo(horizontalScroll.frameLayoutGuide) // Gorizontal scroll siri!
}
// Ichki elementlar xuddi 3-bo'limdagidek joylanadi...
```

Gestures (imo-ishoralar) avtomatik ravishda to'g'ri ishlaydi: foydalanuvchi chapga/o'ngga tortsa gorizontal, tepaga/pastga tortsa vertikal scroll harakatlanadi.

---

## Xulosa {#xulosa}

- `UIScrollView` SnapKit bilan ishlatilganda har doim 2 ta guide yodda tutilishi kerak: `contentLayoutGuide` (ichki elementlar uchun) va `frameLayoutGuide` (ScrollView'ning o'zi uchun).
- Vertikal scroll uchun `contentView`ning `width`ini, gorizontal scroll uchun esa `height`ini cheklang.
- Hech qachon elementlarni `contentView`ning tagiga (yoki oxiriga) yopishtirishni unutmang. Bu o'lchamni aniqlash uchun zarur.
- Klaviatura paydo bo'lishiga moslashish uchun `contentInset` bilan ishlash eng qulay va silliq usul.

## Manbalar {#manbalar}

- [SnapKit rasmiy hujjatlari](http://snapkit.io/docs/)
- [Apple Developer: UIScrollView](https://developer.apple.com/documentation/uikit/uiscrollview)
- [Working with Scroll Views in Auto Layout](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html)
