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
- UIScrollView + Auto Layout = Bosh og'rig'i?
- SnapKit bilan Vertikal Scroll
- Gorizontal Scroll
- Amaliy loyiha 1: Registration Form
- Klaviatura bilan ishlash (Juda Muhim!)
- Amaliy loyiha 2: Gorizontal Image Scroller
- Nested ScrollView (Advanced)
- Xulosa
- 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:
- FRAME (Ramka): ScrollView ekranda qancha joy egallaydi?
- 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,UIViewyokiUIStackView) yarating, uni ScrollView ichiga qo'ying va barcha boshqa elementlarni shucontentViewichiga 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 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
.bottomqismicontentViewning.bottomqismiga ulanmasa, iOScontentViewqancha 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 shunchakicontentInsetvascrollIndicatorInsetsni 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:
- Ikkala ScrollView'da ham
contentViewto'g'ri sozlangan bo'lishi. - Ichki gorizontal ScrollView'ning
heighto'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}
UIScrollViewSnapKit bilan ishlatilganda har doim 2 ta guide yodda tutilishi kerak:contentLayoutGuide(ichki elementlar uchun) vaframeLayoutGuide(ScrollView'ning o'zi uchun).- Vertikal scroll uchun
contentViewningwidthini, gorizontal scroll uchun esaheightini cheklang. - Hech qachon elementlarni
contentViewning tagiga (yoki oxiriga) yopishtirishni unutmang. Bu o'lchamni aniqlash uchun zarur. - Klaviatura paydo bo'lishiga moslashish uchun
contentInsetbilan ishlash eng qulay va silliq usul.