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
- Safe Area nima va nima uchun kerak?
- SnapKit bilan Safe Area ishlatish
- Layout Margins bilan ishlash
- UILayoutGuide — ko'rinmas spacer
- additionalSafeAreaInsets bilan ishlash
- Size Classes va Trait Collection bilan adaptiv layout
- Amaliy loyiha: Universal App ekrani
- Xulosa
- 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 doimsafeAreaLayoutGuidedan 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
| Xususiyat | Manual Offset (.offset(16)) | Layout Margins (.margins) |
|---|---|---|
| Qurilmaga moslashish | Yo'q (har doim 16 bo'ladi) | Ha (iPhone: 16, iPad: 20/24) |
| LTR / RTL qo'llab-quvvatlash | .leading ishlatilsa ha | directionalLayoutMargins orqali to'liq kafolatlangan |
| O'zgartirish | Constraint orqali alohida yangilanadi | view.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
traitCollectionDidChangehamda SnapKit'ningsnp.remakeConstraintsxususiyatlaridan unumli foydalaning.