Azimjon Abdurasulov

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

🧩 SnapKit — Zero to Hero

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?
  2. SnapKit bilan Vertikal Scroll
  3. Gorizontal Scroll
  4. Amaliy loyiha 1: Registration Form
  5. Klaviatura bilan ishlash (Juda Muhim!)
  6. Amaliy loyiha 2: Gorizontal Image Scroller
  7. Nested ScrollView (Advanced)
  8. Xulosa
  9. 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.
+-----------------------------------+  <-- 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.

GuideSnapKit'da ishlatilishiVazifasi
contentLayoutGuidescrollView.contentLayoutGuide.snp...Content View'ning chekkalarini scroll view ichida belgilash.
frameLayoutGuidescrollView.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 contentViewning kengligini (width) scrollViewning kengligiga (frameLayoutGuide) tenglashimiz kerak! Va eng muhimi, eng pastdagi element contentViewning tagiga (bottom) ulanishi SHART.

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 contentViewning .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) frameLayoutGuidega tenglanishi kerak va elementlar chapdan o'ngga qarab tiziladi.

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.

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 UIScrollViewning contentInset'ini o'zgartirishimiz kerak.

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 constraintni yangilash ham mumkin (bottomConstraint?.update(offset: -keyboardSize.height)), biroq UIScrollView bilan ishlaganda shunchaki contentInset va scrollIndicatorInsetsni o'zgartirish ancha samarali va silliq ishlaydi.


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

Gorizontal scroll bilan rasmlar galereyasini qilish (Paging enabled).

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.
// 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 contentViewning widthini, gorizontal scroll uchun esa heightini cheklang.
  • Hech qachon elementlarni contentViewning 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}

Markdown manba

iOS Lessons