Azimjon Abdurasulov

SnapKit 7: Constraint Hayot Sikli — makeConstraints, updateConstraints, remakeConstraints va Constraint Reference

🧩 SnapKit — Zero to Hero

SnapKit 7: Constraint Hayot Sikli — makeConstraints, updateConstraints, remakeConstraints va Constraint Reference

Holat: 2026-yil oktabr. Ushbu dars SnapKit’ning eng muhim mavzularidan biri — constraint’larni dinamik ravishda boshqarish va ularning hayot sikli (lifecycle) haqida. Qachon qaysi metodni ishlatishni bilish iOS dasturlashda muvaffaqiyatli UI yaratishning kalitidir.

Ushbu darsda biz SnapKit’da constraint’larni yaratish, yangilash va o'chirish usullarini, shuningdek, constraint referenslarini saqlab, UI'ni qanday jonlantirish mumkinligini ko'rib chiqamiz.

Mundarija

  1. makeConstraints — yaratish (bir marta!)
  2. updateConstraints — faqat constant'ni yangilash
  3. remakeConstraints — hammasini o'chirib qayta yozish
  4. Uch usulni taqqoslash jadvali
  5. Constraint Reference saqlash (Eng muhim pattern!)
  6. UIView.updateConstraints() lifecycle metodi
  7. Amaliy loyiha: Expandable Card
  8. Amaliy loyiha 2: Keyboard-aware input
  9. Xulosa
  10. Manbalar

1. makeConstraints — yaratish (bir marta!) {#make-constraints}

makeConstraints bloki orqali biz view'ning yangi constraint'larini yaratamiz va avtomatik ravishda activate qilamiz (ishga tushiramiz).

Muhim: makeConstraints funksiyasini faqat view o'z superview'iga qo'shilgandan (addSubview) keyin chaqirish kerak, aks holda crash (xato) yuz beradi!

Yana bir juda muhim narsa: makeConstraints ni ayni bir view uchun qayta-qayta chaqiravermang! Har safar chaqirilganda, u yangi constraint yaratadi. Agar eski constraint'lar mavjud bo'lsa, sizda duplikat constraint'lar (conflict) yuzaga keladi.

import UIKit
import SnapKit

@MainActor
class MakeConstraintsView: UIView {
    let boxView = UIView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    private func setupSubviews() {
        boxView.backgroundColor = .systemRed
        // 1. Dastlab view qo'shilishi kerak!
        addSubview(boxView)
    }
    
    private func setupConstraints() {
        // 2. Keyin constraint yoziladi (bir marta!)
        boxView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(100)
        }
    }
    
    func changeSizeIncorrectly() {
        // XATO YONDASHUV! Bu yerda makeConstraints qayta chaqirilmoqda.
        // Natijada, size ham 100, ham 200 bo'lishga harakat qiladi va auto-layout conflict kelib chiqadi.
        boxView.snp.makeConstraints { make in
            make.size.equalTo(200)
        }
    }
}

2. updateConstraints — faqat constant'ni yangilash {#update-constraints}

updateConstraints avval yaratilgan (masalan, makeConstraints orqali) constraint'ning faqat constant qiymatini (offset, inset, size) yangilash uchun ishlatiladi.

Cheklovlar va xavflar:

  • U orqali mutlaqo YANGI constraint qo'sha olmaysiz (masalan, left bormidi, faqat shuni update qila olasiz. Endi right ni qo'shaman desangiz — CRASH!).
  • Agar munosabatni (relation) o'zgartirmoqchi bo'lsangiz (masalan, equalTo o'rniga greaterThanOrEqualTo) — BU ISHLAMAYDI! Faqat aniq qat'iy (constant) son o'zgaradi.

Qachon ishlatiladi: Tugma bosilganda view'ning offset'ini qisqa ravishda o'zgartirish kerak bo'lganda.

import UIKit
import SnapKit

@MainActor
class UpdateConstraintsView: UIView {
    let movingBox = UIView()
    var isMoved = false
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupSubviews() {
        movingBox.backgroundColor = .systemBlue
        addSubview(movingBox)
    }
    
    private func setupConstraints() {
        movingBox.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(50)
            make.centerX.equalToSuperview()
            make.size.equalTo(100)
        }
    }
    
    func togglePosition() {
        isMoved.toggle()
        let newOffset = isMoved ? 200 : 50
        
        UIView.animate(withDuration: 0.3) {
            // FAQAT avval yozilgan top constraint'ning constant qiymati yangilanadi
            self.movingBox.snp.updateConstraints { make in
                make.top.equalToSuperview().offset(newOffset)
            }
            self.layoutIfNeeded() // Animatsiya bo'lishi uchun kerak
        }
    }
}

3. remakeConstraints — hammasini o'chirib qayta yozish {#remake-constraints}

remakeConstraints — ayni view'dagi barcha SnapKit constraint'larini o'chirib, bloki ichidagilarini noldan yangidan yaratadi.

Bu eng xavfsiz usul (chunki eski constraint'lar qolib ketishi yuzasidan xavotir yo'q), biroq perfomance (ishlash tezligi) jihatdan biroz qimmat — chunki barcha munosabatlar uziladi va qayta quriladi.

Qachon ishlatiladi: View'ning layout'i to'liq o'zgarib ketganda. Masalan, telefonni landscape'dan portrait'ga burganingizda yoki ko'rinish mutlaqo boshqa dizaynga o'tganda.

import UIKit
import SnapKit

@MainActor
class RemakeConstraintsView: UIView {
    let dynamicBox = UIView()
    var isExpandedMode = false
    
    // ... init va setupSubviews ...
    
    func updateLayoutMode() {
        isExpandedMode.toggle()
        
        UIView.animate(withDuration: 0.4) {
            self.dynamicBox.snp.remakeConstraints { make in
                if self.isExpandedMode {
                    // Kengaytirilgan holat: butun ekranni egallaydi
                    make.edges.equalToSuperview().inset(20)
                } else {
                    // Kichik holat: faqat markazda bo'ladi
                    make.center.equalToSuperview()
                    make.size.equalTo(100)
                }
            }
            self.layoutIfNeeded()
        }
    }
}

4. Uch usulni taqqoslash jadvali {#comparison-table}

XususiyatmakeConstraintsupdateConstraintsremakeConstraints
Nima qiladi?Yangi constraint yaratadi va activate qiladi.Mavjud constraint'ning faqat constant (offset/inset/size) qiymatini o'zgartiradi.Barcha eski constraint'larni olib tashlaydi va o'rniga yangilarini yaratadi.
Qachon ishlatiladi?View birinchi marta chizilayotganda (UI qurish bosqichida).Qiymatni ozgina o'zgartirish kerak bo'lganda (masalan, +20px pastga siljitish).Layout to'liq o'zgarib, tamoman boshqa dizayn/holatga o'tganda.
XavfiQayta-qayta chaqirilsa, constraint duplikatlari (conflict) vujudga keladi.Yangi relation (anchor) qo'shib bo'lmaydi. Faqat raqam o'zgaradi, aks holda dastur qulaydi (crash).Deyarli xavfsiz. Biroq keraksiz joyda ko'p ishlatilsa, ishlash tezligini tushirishi mumkin.
PerformanceYaxshi (faqat 1 marta yaratiladi).Eng tez (Optimal) (Auto Layout faqat constant'ni qayta hisoblaydi).Birmuncha sekin (hamma narsa o'chib-yonganligi sababli).

5. Constraint Reference saqlash (Eng muhim pattern!) {#constraint-reference}

SnapKit bilan dinamik layout qilinganda ko'p hollarda updateConstraints yoki remakeConstraints ham kerak bo'lmaydi. Buning eng "pro" usuli — bitta aniq constraint'ning reference'ini saqlab qolish va faqat shuni boshqarish.

Senior eslatmasi: Siz o'zgartirmoqchi bo'lgan qismlarning constraint'larini var ... Constraint? tarzida saqlab oling. Bu updateConstraints'ga qaraganda o'qilishi oson va har xil xatolarning oldini oladi.

import UIKit
import SnapKit

@MainActor
class ConstraintReferenceView: UIView {
    let circleView = UIView()
    
    // O'zgaradigan constraint'lar reference'larini saqlash
    var topConstraint: Constraint?
    var sizeConstraint: Constraint?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupSubviews() {
        circleView.backgroundColor = .systemYellow
        circleView.layer.cornerRadius = 50
        addSubview(circleView)
    }
    
    private func setupConstraints() {
        circleView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            
            // Reference saqlash
            self.topConstraint = make.top.equalToSuperview().offset(50).constraint
            self.sizeConstraint = make.width.height.equalTo(100).constraint
        }
    }
    
    func animateToBottom() {
        UIView.animate(withDuration: 0.5) {
            // Reference orqali to'g'ridan-to'g'ri yangilash
            self.topConstraint?.update(offset: 400)
            self.sizeConstraint?.update(offset: 150)
            
            // Xohlasangiz constraint'ni vaqtincha deactive qilishingiz ham mumkin:
            // self.topConstraint?.deactivate()
            // self.topConstraint?.activate()
            
            self.layoutIfNeeded()
        }
    }
}

Nima uchun bu pattern kuchli?

  1. O'zgartirmoqchi bo'lgan narsangizga to'g'ridan-to'g'ri bog'lanasiz.
  2. update(offset:), update(inset:) va update(priority:) qilish imkoniyati bor.
  3. Kerak bo'lmaganda vaqtincha deactivate() qilib turishingiz mumkin.

6. UIView.updateConstraints() lifecycle metodi {#uiview-lifecycle}

Apple'ning Auto Layout tizimida o'zgarishlar maxsus tsikl asosida boradi: setNeedsUpdateConstraints() -> tizim vaqt topganda updateConstraints() ni chaqiradi -> Auto Layout Pass o'tadi.

Ba'zan layout mantiqi murakkab bo'lsa, constraintlarni saqlab olish o'rniga shu metodni override qilish tavsiya etiladi.

import UIKit
import SnapKit

@MainActor
class CustomLifecycleView: UIView {
    var isRedState: Bool = true {
        didSet {
            // UIView'ga constraint'larni yangilash kerakligini aytamiz
            setNeedsUpdateConstraints()
        }
    }
    let colorView = UIView()
    
    // ... init ... 
    
    override func updateConstraints() {
        // SnapKit bilan bu yerda remake yoki update ishlatamiz
        colorView.snp.remakeConstraints { make in
            make.center.equalToSuperview()
            if isRedState {
                make.size.equalTo(100)
            } else {
                make.size.equalTo(200)
            }
        }
        
        // Super class method'ni chaqirish shart!
        super.updateConstraints()
    }
}

7. Amaliy loyiha: Expandable Card {#expandable-card}

Ushbu amaliyotda foydalanuvchi bosganda kengayadigan karta (card) yaratamiz. Balandlik constraint'i saqlab olinib, animatsiya bilan o'zgartiriladi.

import UIKit
import SnapKit

@MainActor
class ExpandableCardView: UIView {
    
    private let titleLabel = UILabel()
    private let descriptionLabel = UILabel()
    
    private var heightConstraint: Constraint?
    private var isExpanded = false
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        setupConstraints()
        
        // Bosish uchun gesture
        let tap = UITapGestureRecognizer(target: self, action: #selector(toggleCard))
        addGestureRecognizer(tap)
        isUserInteractionEnabled = true
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupView() {
        backgroundColor = .systemBackground
        layer.cornerRadius = 12
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.1
        layer.shadowOffset = CGSize(width: 0, height: 4)
        
        titleLabel.text = "SnapKit sirlari"
        titleLabel.font = .boldSystemFont(ofSize: 18)
        
        descriptionLabel.text = "Constraint reference bilan height'ni o'zgartirish juda zo'r pattern! Karta ochilganda bu text ko'rinadi."
        descriptionLabel.numberOfLines = 0
        descriptionLabel.alpha = 0 // Boshida yashirin
        
        addSubview(titleLabel)
        addSubview(descriptionLabel)
    }
    
    private func setupConstraints() {
        self.snp.makeConstraints { make in
            make.width.equalTo(300)
            // Karta boshida 80 px balandlikka ega
            self.heightConstraint = make.height.equalTo(80).constraint
        }
        
        titleLabel.snp.makeConstraints { make in
            make.top.leading.equalToSuperview().offset(16)
        }
        
        descriptionLabel.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(12)
            make.leading.trailing.equalToSuperview().inset(16)
        }
    }
    
    @objc private func toggleCard() {
        isExpanded.toggle()
        
        UIView.animate(withDuration: 0.4,
                       delay: 0,
                       usingSpringWithDamping: 0.7,
                       initialSpringVelocity: 0.5) {
            
            // Balandlik 200 yoki 80 ga o'zgaradi
            self.heightConstraint?.update(offset: self.isExpanded ? 200 : 80)
            self.descriptionLabel.alpha = self.isExpanded ? 1 : 0
            
            // Superview'dan ham layout yangilanishini so'rashimiz mumkin
            self.superview?.layoutIfNeeded()
        }
    }
}

8. Amaliy loyiha 2: Keyboard-aware input {#keyboard-aware}

TextField ekranning eng pastida turganda, klaviatura ochilsa u yopilib qolmasligi uchun klaviatura balandligi qadar uni tepaga ko'tarish kerak.

+-----------------------+
|                       |
|                       |
|                       |
|                       |
| [____TextField____]   | <--- Keyboard yo'q paytida bottom offset 0
+-----------------------+

+-----------------------+
|                       |
| [____TextField____]   | <--- Keyboard ochilganda bottom offset -(keyboardHeight)
|#######################|
|####### KEYBOARD ######|
|#######################|
+-----------------------+
import UIKit
import SnapKit

@MainActor
class ChatInputViewController: UIViewController {
    
    private let containerView = UIView()
    private let textField = UITextField()
    private var bottomConstraint: Constraint?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemGray6
        setupSubviews()
        setupConstraints()
        setupKeyboardObservers()
    }
    
    private func setupSubviews() {
        containerView.backgroundColor = .white
        
        textField.borderStyle = .roundedRect
        textField.placeholder = "Xabar yozing..."
        
        view.addSubview(containerView)
        containerView.addSubview(textField)
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
        view.addGestureRecognizer(tap)
    }
    
    private func setupConstraints() {
        containerView.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            // Saqlab olinayotgan constraint (safe area'ga nisbatan)
            self.bottomConstraint = make.bottom.equalTo(view.safeAreaLayoutGuide).constraint
            make.height.equalTo(60)
        }
        
        textField.snp.makeConstraints { make in
            make.edges.equalToSuperview().inset(10)
        }
    }
    
    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,
              let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
        
        let safeAreaBottom = view.safeAreaInsets.bottom
        let offset = keyboardSize.height - safeAreaBottom
        
        UIView.animate(withDuration: duration) {
            // Negative qiymat, chunki tepaga ko'tarilyapmiz
            self.bottomConstraint?.update(offset: -offset)
            self.view.layoutIfNeeded()
        }
    }
    
    @objc private func keyboardWillHide(notification: NSNotification) {
        guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
        
        UIView.animate(withDuration: duration) {
            // Joyiga qaytaramiz
            self.bottomConstraint?.update(offset: 0)
            self.view.layoutIfNeeded()
        }
    }
    
    @objc private func dismissKeyboard() {
        view.endEditing(true)
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

Xulosa {#xulosa}

  • makeConstraints – faqat 1 marta ishlatiladigan eng asosiy usul.
  • updateConstraints – eng optimal, lekin cheklangan metod (faqat son/offset o'zgaradi).
  • remakeConstraints – to'liq qayta chizish uchun ishlatiladigan xavfsiz metod.
  • **Constraint Reference (var const: Constraint?)** – arxitekturada eng toza, kerak bo'lganda faqat o'zini o'zgartirish, yoqish/o'chirish (activate/deactivate) uchun xizmat qiladigan eng kuchli pattern hisoblanadi.

Bu bilimlar sizning dasturingizni xatolar va crashlardan asrab, turli xil animatsiyalarni ishonch bilan bajarishingizga zamin yaratadi.

Manbalar {#manbalar}

Markdown manba

iOS Lessons