Azimjon Abdurasulov

SnapKit 6: Safe Area, Margins va Layout Guides — Notch, Dynamic Island va Universal Layout

🧩 SnapKit — Zero to Hero

SnapKit 6: Safe Area, Margins va Layout Guides — Notch, Dynamic Island va Universal Layout

Holat: 2026-yil oktabr. Zamonaviy iOS qurilmalarida (Notch, Dynamic Island, Home Indicator kabi elementlar mavjudligida) kontentni to'g'ri joylashtirish juda muhim. Ushbu darsda biz SnapKit yordamida Safe Area, margins va Layout Guide'lar bilan to'g'ri ishlashni batafsil o'rganamiz.

Mundarija

  1. Safe Area nima va nima uchun kerak?
  2. SnapKit bilan Safe Area ishlatish
  3. Layout Margins bilan ishlash
  4. UILayoutGuide — ko'rinmas spacer
  5. additionalSafeAreaInsets bilan ishlash
  6. Size Classes va Trait Collection bilan adaptiv layout
  7. Amaliy loyiha: Universal App ekrani
  8. Xulosa
  9. Manbalar

1. Safe Area nima va nima uchun kerak? {#safe-area-nima}

iPhone X dan boshlab iOS qurilmalari dizaynida tub burilish yasaldi: ekranning tepa qismida "notch" (va keyinchalik Dynamic Island), pastki qismida esa Home Indicator (qaytish chizig'i) paydo bo'ldi. Agar ilovangizdagi kontent ushbu hududlarga tushib qolsa, foydalanuvchi ularni bosa olmaydi yoki o'qiy olmaydi.

Safe Area — bu operatsion tizim tomonidan ajratilgan "xavfsiz hudud". Ushbu hudud ichida joylashgan UI elementlar hech qanday tizim interfeysi (status bar, navigation bar, tab bar, dynamic island) tomonidan yopilib qolmasligiga kafolat beriladi.

+-------------------------+
|      [Dynamic Island]   |  <-- Unsafe Top (Status Bar)
|-------------------------|  <-- Safe Area Top
|                         |
|                         |
|      SAFE AREA          |
|      (Kontent bu yerda) |
|                         |
|                         |
|-------------------------|  <-- Safe Area Bottom
|       Home Indicator    |  <-- Unsafe Bottom
+-------------------------+

UIKit orqali biz bu ma'lumotlarni quyidagi xususiyatlardan olamiz:

  • view.safeAreaLayoutGuide — elementlarni xavfsiz hududga bog'lash uchun mo'ljallangan layout guide.
  • view.safeAreaInsets — har bir tomonning chekinish (inset) qiymatlari (UIEdgeInsets). Bu qiymatlar qurilma va orientatsiyaga qarab runtime'da aniqlanadi.

Muhim: Hech qachon Safe Area qiymatlarini hardcode (masalan, top: 44, bottom: 34) qilmang. Har doim safeAreaLayoutGuide dan foydalaning, chunki qurilmalar turli o'lchamlarga ega.


2. SnapKit bilan Safe Area ishlatish {#snapkit-safe-area}

SnapKit da xavfsiz hudud bilan ishlash juda qulay. Buning uchun biz shunchaki .equalTo(view.safeAreaLayoutGuide) dan foydalanamiz.

Qachon Safe Area ishlatish kerak va qachon yo'q?

  • Safe Area KERAK: Matnlar, tugmalar, formalar, ro'yxatlar (qisman) va foydalanuvchi interaksiya qiladigan barcha elementlar.
  • Safe Area KERAK EMAS: Background rasmlar, to'liq ekranli gradient fonlar, ekranning chekkasigacha yetib borishi kerak bo'lgan dizayn bezaklari.
import UIKit
import SnapKit

@MainActor
final class SafeAreaExampleViewController: UIViewController {
    
    private let backgroundImageView = UIImageView()
    private let contentContainerView = UIView()
    private let titleLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        backgroundImageView.backgroundColor = .systemBlue
        view.addSubview(backgroundImageView)
        
        contentContainerView.backgroundColor = .white.withAlphaComponent(0.9)
        contentContainerView.layer.cornerRadius = 16
        view.addSubview(contentContainerView)
        
        titleLabel.text = "Xavfsiz Hudud Ichida"
        contentContainerView.addSubview(titleLabel)
    }
    
    private func setupConstraints() {
        // Fon butun ekranni qoplaydi (Safe Area HISOBGA OLINMAYDI)
        backgroundImageView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        // Kontent faqat xavfsiz hududda turadi
        contentContainerView.snp.makeConstraints { make in
            // Safe Area tepasiga, pastiga va yonlariga bog'lash
            make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(16)
            make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16)
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16)
        }
        
        titleLabel.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
    }
}

Senior eslatmasi: SnapKit'ning make.edges.equalTo(view.safeAreaLayoutGuide) sintaksisi yordamida barcha 4 ta tomonni birdaniga xavfsiz hududga bog'lash mumkin!


3. Layout Margins bilan ishlash {#layout-margins}

Layout Margins — bu Apple tomonidan tavsiya etilgan standart chekinishlar (masalan, iPhone'da yonlardan 16pt, iPad'da 20pt yoki undan ko'p).

Agar siz offset yordamida qattiq 16 qiymatini bersangiz, u barcha qurilmalarda bir xil bo'ladi. Ammo layoutMarginsGuide dan foydalansangiz, tizim o'zi qurilma turiga qarab eng ideal chekinishni tanlaydi. SnapKit'da buni view.snp.margins orqali tezkor qilish mumkin.

Margins vs Manual Offset

XususiyatManual Offset (.offset(16))Layout Margins (.margins)
Qurilmaga moslashishYo'q (har doim 16 bo'ladi)Ha (iPhone: 16, iPad: 20/24)
LTR / RTL qo'llab-quvvatlash.leading ishlatilsa hadirectionalLayoutMargins orqali to'liq kafolatlangan
O'zgartirishConstraint orqali alohida yangilanadiview.directionalLayoutMargins orqali butun view uchun sozlanadi
import UIKit
import SnapKit

@MainActor
final class MarginsExampleView: UIView {
    
    private let cardView = UIView()
    private let descriptionLabel = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        // Asosiy view uchun standart marginlarni o'zgartirishimiz mumkin
        self.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 24, bottom: 20, trailing: 24)
        
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    private func setupSubviews() {
        cardView.backgroundColor = .systemGray6
        addSubview(cardView)
        
        descriptionLabel.text = "Margins orqali moslashtirilgan"
        cardView.addSubview(descriptionLabel)
    }
    
    private func setupConstraints() {
        // cardView'ni self'ning standart marginlariga joylash
        cardView.snp.makeConstraints { make in
            make.edges.equalTo(self.snp.margins)
        }
        
        descriptionLabel.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
    }
}

4. UILayoutGuide — ko'rinmas spacer {#uilayoutguide-spacer}

Ko'pincha elementlar orasida bo'shliq (spacing) yaratish uchun bo'sh UIView (masalan backgroundColor = .clear) ishlatiladi. Lekin har bir UIView o'zining layer'iga ega bo'lib, xotiradan ma'lum joy band qiladi va render qilinadi.

Ushbu muammoni hal qilish uchun Apple UILayoutGuide ni taqdim etgan. U faqat layout algoritmi uchun mavjud bo'lgan, ekranda umuman chizilmaydigan, xotirada yengil obyektdir.

Senariy: 3 ta tugmani ekran bo'ylab shunday joylash kerakki, ular orasidagi bo'shliqlar har doim bir xil (teng) bo'lsin.

|--[Btn1]--[Spacer1]--[Btn2]--[Spacer2]--[Btn3]--|
           <-- W -->          <-- W -->
import UIKit
import SnapKit

@MainActor
final class LayoutGuideExampleViewController: UIViewController {
    
    private let button1 = UIButton(type: .system)
    private let button2 = UIButton(type: .system)
    private let button3 = UIButton(type: .system)
    
    // Ko'rinmas spacer'lar yaratish
    private let spacer1 = UILayoutGuide()
    private let spacer2 = UILayoutGuide()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        view.backgroundColor = .systemBackground
        
        button1.setTitle("Bir", for: .normal)
        button2.setTitle("Ikki", for: .normal)
        button3.setTitle("Uch", for: .normal)
        
        [button1, button2, button3].forEach { view.addSubview($0) }
        
        // Layout Guide'larni view'ga qo'shish kerak!
        view.addLayoutGuide(spacer1)
        view.addLayoutGuide(spacer2)
    }
    
    private func setupConstraints() {
        button1.snp.makeConstraints { make in
            make.leading.equalTo(view.safeAreaLayoutGuide).offset(20)
            make.centerY.equalToSuperview()
        }
        
        spacer1.snp.makeConstraints { make in
            make.leading.equalTo(button1.snp.trailing)
            make.centerY.equalToSuperview()
            make.height.equalTo(1)
        }
        
        button2.snp.makeConstraints { make in
            make.leading.equalTo(spacer1.snp.trailing)
            make.centerY.equalToSuperview()
        }
        
        spacer2.snp.makeConstraints { make in
            make.leading.equalTo(button2.snp.trailing)
            make.centerY.equalToSuperview()
            make.height.equalTo(1)
            // Spacer2 ning kengligi Spacer1 bilan teng bo'lishi kerak!
            make.width.equalTo(spacer1)
        }
        
        button3.snp.makeConstraints { make in
            make.leading.equalTo(spacer2.snp.trailing)
            make.trailing.equalTo(view.safeAreaLayoutGuide).offset(-20)
            make.centerY.equalToSuperview()
        }
    }
}

5. additionalSafeAreaInsets bilan ishlash {#additional-safe-area}

Ba'zida siz ilovangizda Custom TabBar, qalqib chiquvchi pleyer (mini player kabi) yoki maxsus toolbar yaratasiz. Bunday holatda tizimning standart Safe Area'si yetarli bo'lmaydi, chunki kontent siz yaratgan custom UI element tagida qolib ketishi mumkin.

Buning yechimi — additionalSafeAreaInsets. Bu orqali biz tizimga: "Mening o'z custom elementim bor, shuning uchun pastki Safe Area'ni yana 60pt ga ko'paytir!" deymiz.

import UIKit
import SnapKit

@MainActor
final class CustomTabBarHostController: UIViewController {
    
    private let customTabBar = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
        setupConstraints()
        
        // Boshqa child view controller'larning kontenti tab bar tagida qolib ketmasligi uchun
        // pastki safe area'ga qo'shimcha 80pt qo'shamiz
        self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 80, right: 0)
    }
    
    private func setupSubviews() {
        view.backgroundColor = .systemBackground
        
        customTabBar.backgroundColor = .systemTeal
        customTabBar.layer.cornerRadius = 20
        view.addSubview(customTabBar)
    }
    
    private func setupConstraints() {
        customTabBar.snp.makeConstraints { make in
            // E'tibor bering, tab bar o'zi ekranning eng pastiga yopishadi
            make.leading.trailing.equalToSuperview().inset(16)
            make.bottom.equalToSuperview().offset(-34)
            make.height.equalTo(60)
        }
    }
}

6. Size Classes va Trait Collection bilan adaptiv layout {#size-classes}

Ilovani ham iPhone, ham iPad uchun moslashtirish (Universal App) muhim. Buning uchun biz qurilmaning kengligiga qarab (Compact yoki Regular) layout'ni o'zgartirishimiz kerak bo'ladi.

traitCollectionDidChange metodi yordamida biz kenglik yoki balandlik turi o'zgarganda SnapKit constraint'larini moslashtira olamiz.

import UIKit
import SnapKit

@MainActor
final class AdaptiveViewController: UIViewController {
    
    private let mainView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        mainView.backgroundColor = .systemOrange
        view.addSubview(mainView)
    }
    
    private func setupConstraints() {
        // Boshlang'ich (iPhone / Compact) holat
        updateLayoutForCurrentTraits()
    }
    
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        
        // Agar kenglik klassi o'zgargan bo'lsa (masalan iPad split screen)
        if self.traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass {
            updateLayoutForCurrentTraits()
        }
    }
    
    private func updateLayoutForCurrentTraits() {
        mainView.snp.remakeConstraints { make in
            if traitCollection.horizontalSizeClass == .regular {
                // iPad yoki keng ekran: Kichikroq, markazda kvadrat
                make.center.equalToSuperview()
                make.size.equalTo(CGSize(width: 400, height: 400))
            } else {
                // iPhone: Butun safe area'ni egallaydi
                make.edges.equalTo(view.safeAreaLayoutGuide).inset(20)
            }
        }
    }
}

7. Amaliy loyiha: Universal App ekrani {#amaliy-loyiha}

Keling, barcha o'rganganlarimizni birlashtirib, professional ko'rinishga ega kompleks ekran yaratamiz.

  • Orqa fon gradient'i: Butun ekranni egallaydi.
  • Maxsus Navigation Area: Safe Area tepasida.
  • Kontent hududi: Safe Area ichida, Layout Margins bilan.
  • Custom TabBar: Pastda.
import UIKit
import SnapKit

@MainActor
final class UniversalAppViewController: UIViewController {
    
    private let backgroundGradientView = UIView()
    private let customNavBar = UIView()
    private let titleLabel = UILabel()
    private let contentCard = UIView()
    private let customTabBar = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Tab bar tagida kontent qolib ketmasligi uchun qo'shimcha insets
        self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 80, right: 0)
        
        setupSubviews()
        setupConstraints()
    }
    
    private func setupSubviews() {
        // Background
        backgroundGradientView.backgroundColor = .systemIndigo
        view.addSubview(backgroundGradientView)
        
        // Nav Bar
        customNavBar.backgroundColor = .systemBackground.withAlphaComponent(0.8)
        view.addSubview(customNavBar)
        
        titleLabel.text = "Universal Layout"
        titleLabel.font = .boldSystemFont(ofSize: 22)
        customNavBar.addSubview(titleLabel)
        
        // Content Card
        contentCard.backgroundColor = .white
        contentCard.layer.cornerRadius = 24
        view.addSubview(contentCard)
        
        // Tab Bar
        customTabBar.backgroundColor = .systemPurple
        customTabBar.layer.cornerRadius = 30
        view.addSubview(customTabBar)
    }
    
    private func setupConstraints() {
        // 1. Fon butun ekranga
        backgroundGradientView.snp.makeConstraints { make in
            make.edges.equalToSuperview() // Safe area shart emas
        }
        
        // 2. Custom Navigation Bar (Tepadan boshlanib, Safe Area top marginidan 60pt gacha)
        customNavBar.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.top).offset(60)
        }
        
        titleLabel.snp.makeConstraints { make in
            make.bottom.equalToSuperview().offset(-16)
            make.centerX.equalToSuperview()
        }
        
        // 3. Tab Bar (Pastda)
        customTabBar.snp.makeConstraints { make in
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(30)
            make.centerX.equalToSuperview()
            make.width.equalTo(250)
            make.height.equalTo(60)
        }
        
        // 4. Content Card (O'rtada, Safe Area va Margins qoidalariga amal qiladi)
        contentCard.snp.makeConstraints { make in
            make.top.equalTo(customNavBar.snp.bottom).offset(20)
            // LTR/RTL uchun layoutMarginsGuide dan foydalanamiz
            make.leading.trailing.equalTo(view.layoutMarginsGuide)
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20)
        }
    }
}

Xulosa {#xulosa}

  • Safe Area orqali biz UI elementlarimiz Notch, Dynamic Island yoki Home Indicator tegida qolib ketmasligini kafolatlaymiz. SnapKit'ning make.edges.equalTo(view.safeAreaLayoutGuide) sintaksisi bu jarayonni osonlashtiradi.
  • Standart spacing uchun oddiy insets/offsets dan ko'ra Layout Margins (view.snp.margins) ishlatish dizaynni Apple tizim standartlariga ko'proq moslashtiradi.
  • Elementlar orasida bo'sh joy qoldirish uchun UIView o'rniga har doim UILayoutGuide ishlating, bu qurilma xotirasini tejaydi.
  • Ilovada maxsus pastki elementlar bo'lsa, additionalSafeAreaInsets yordamida tizimga xabar bering.
  • iPad va iPhone ni bir kodda qo'llab-quvvatlash uchun traitCollectionDidChange hamda SnapKit'ning snp.remakeConstraints xususiyatlaridan unumli foydalaning.

Manbalar {#manbalar}

  1. Apple Human Interface Guidelines - Layout
  2. SnapKit rasmiy hujjatlari: Safe Area
  3. WWDC 2017: Safe Area Insets
  4. Apple Documentation: UILayoutGuide

Markdown manba

iOS Lessons