- Published on
Coordinator pattern — navigatsiya boshqaruvi
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Coordinator Pattern — navigatsiya logikasini View lardan ajratadi. Kichik ilovada View ichida NavigationLink yetarli. Lekin 10+ ekranli ilovada navigatsiya oqimini boshqarish murakkablashadi — Coordinator buni markazlashtiradi.
Coordinator tuzilmasi
// ═══════════════════════════════════════════════════════════════
// 🗺 ROUTE — barcha mumkin bo'lgan ekranlar
//
// Route = "manzil". Ilovadagi har ekran bu enum ning bitta case i.
// Hashable — NavigationPath da saqlash uchun
//
// Nima uchun enum? Chunki:
// 1. Barcha ekranlar BIR JOYDA ko'rinadi
// 2. Yangi ekran qo'shsangiz — case qo'shasiz
// 3. Compiler o'tkazib yuborilgan case ni aytadi (switch exhaustive)
// 4. Associated value — ekranga ma'lumot uzatish
// ═══════════════════════════════════════════════════════════════
enum AppRoute: Hashable {
case boshSahifa // Asosiy ekran
case maqolaTafsiloti(maqolaId: Int) // Maqola tafsiloti — ID kerak
case profil // Foydalanuvchi profili
case sozlamalar // Sozlamalar ekrani
case maqolaMuharriri(maqolaId: Int?) // nil = yangi maqola yaratish
// Int? — Optional: bor = tahrirlash, nil = yangi
}
// ═══════════════════════════════════════════════════════════════
// 🎯 COORDINATOR — navigatsiya markaziy boshqaruvchisi
//
// Coordinator — "yo'l ko'rsatuvchi". U:
// ✅ Qaysi ekranga o'tishni boshqaradi (push, pop, sheet)
// ✅ NavigationPath ni egalaydi va boshqaradi
// ✅ Deep link larni parse qiladi
// ✅ View → Route → Coordinator → View ochish
//
// View lar bir-birini BILMAYDI:
// ❌ BoshSahifa → ProfilView() emas
// ✅ BoshSahifa → coordinator.push(.profil)
//
// Bu Single Responsibility Principle:
// • View = UI ko'rsatish
// • ViewModel = logika
// • Coordinator = NAVIGATSIYA
//
// @MainActor — UI thread da ishlash
// ObservableObject — path o'zgarganda View yangilanadi
// ═══════════════════════════════════════════════════════════════
@MainActor
class AppCoordinator: ObservableObject {
// NavigationPath — navigatsiya stacki (yo'l)
// path = [.boshSahifa, .profil, .sozlamalar]
// → Bosh sahifa → Profil → Sozlamalar (3 ekran)
@Published var path = NavigationPath()
// Sheet va fullscreen uchun alohida holat
@Published var sheet: AppRoute?
@Published var fullScreenCover: AppRoute?
// ── PUSH — yangi ekranni stack ga qo'shish ──
// NavigationLink kabi, lekin dasturiy boshqaruv bilan
func push(_ route: AppRoute) {
path.append(route)
// path: [..., .profil] — profil ekrani ochiladi
}
// ── POP — oxirgi ekranni yopish ──
func pop() {
guard !path.isEmpty else { return }
// guard — bo'sh stack da crash oldini olish
path.removeLast()
}
// ── POP TO ROOT — hammani yopib bosh sahifaga qaytish ──
func popToRoot() {
path.removeLast(path.count)
// Barcha ekranlarni stack dan olib tashlash
// Faqat bosh sahifa qoladi
}
// ── SHEET — modal oyna ochish ──
func present(_ route: AppRoute) {
sheet = route
// sheet != nil → .sheet ko'rsatiladi
}
// ── FULL SCREEN — to'liq ekranli modal ──
func presentFullScreen(_ route: AppRoute) {
fullScreenCover = route
}
// ── DISMISS — modal oynani yopish ──
func dismiss() {
sheet = nil
fullScreenCover = nil
}
// ══════════════════════════════════════
// DEEP LINKING — tashqi URL dan ekranga o'tish
//
// Misol URL: meningIlovam://maqola?id=42
// → Maqola tafsiloti ekrani ochiladi (id=42)
//
// Bu push notification, QR kod, veb sahifadan
// ilovaning aniq ekraniga tushish imkoni beradi
// ══════════════════════════════════════
func handleDeepLink(url: URL) {
// URLComponents — URL ni qismlarga ajratish
guard let components = URLComponents(
url: url,
resolvingAgainstBaseURL: false
) else { return }
// URL path bo'yicha route aniqlash
switch components.path {
case "/maqola":
// URL: /maqola?id=42
if let idString = components.queryItems?
.first(where: { $0.name == "id" })?.value,
let id = Int(idString) {
popToRoot() // Avval bosh sahifaga
push(.maqolaTafsiloti(maqolaId: id)) // Keyin maqolaga
}
case "/profil":
popToRoot()
push(.profil)
default:
break // Noma'lum URL — hech narsa qilmaymiz
}
}
// ══════════════════════════════════════
// ROUTE → VIEW — qaysi ekran ko'rsatiladi?
//
// @ViewBuilder — switch ichida View qaytarish
// Bu funksiya Route ni View ga "tarjima qiladi"
// Barcha ekranlar SHU YERDA boshqariladi
// ══════════════════════════════════════
@ViewBuilder
func view(for route: AppRoute) -> some View {
switch route {
case .boshSahifa:
BoshSahifaKorinishi()
case .maqolaTafsiloti(let id):
MaqolaTafsilotiKorinishi(maqolaId: id)
case .profil:
ProfilKorinishi()
case .sozlamalar:
SozlamalarKorinishi()
case .maqolaMuharriri(let id):
MaqolaMuharrirKorinishi(maqolaId: id)
// id = nil → yangi maqola
// id = 42 → maqola #42 ni tahrirlash
}
}
}
// ═══════════════════════════════════════════════════════════════
// 🏠 APP ROOT — Coordinator ni NavigationStack ga ulash
//
// Bu ilovaning "ildiz" View i.
// NavigationStack(path:) — Coordinator ning path ni ishlatadi
// .navigationDestination — Route → View aylantirish
// .sheet — modal oynalar
// .environmentObject — barcha child View larga Coordinator uzatish
// .onOpenURL — deep link qabul qilish
// ═══════════════════════════════════════════════════════════════
struct ContentView: View {
@StateObject private var coordinator = AppCoordinator()
var body: some View {
// NavigationStack — Coordinator ning path ni boshqaradi
NavigationStack(path: $coordinator.path) {
// Bosh sahifa — stack ning "pastki" qismi
coordinator.view(for: .boshSahifa)
// .navigationDestination — push qilingan route → View
.navigationDestination(for: AppRoute.self) { route in
coordinator.view(for: route)
// Route ni View ga "tarjima qiladi"
}
}
// Sheet — modal oyna (pastdan chiqadi)
.sheet(item: $coordinator.sheet) { route in
coordinator.view(for: route)
}
// Barcha child View larga Coordinator ni uzatish
// Istalgan View da @EnvironmentObject var coordinator
.environmentObject(coordinator)
// Deep link — tashqi URL qabul qilish
.onOpenURL { url in
coordinator.handleDeepLink(url: url)
}
}
}
// ═══════════════════════════════════════════════════════════════
// 👁 VIEW — Coordinator ORQALI navigatsiya
//
// View boshqa View larni bilmaydi!
// View faqat coordinator.push(.route) deydi
// Coordinator qaysi View ochishni HAL QILADI
//
// Bu nimaga yaxshi?
// 1. Navigatsiya tartibini O'ZGARTIRISH oson
// 2. A/B testing — boshqa oqim sinash
// 3. Deep linking — istalgan ekranga tushish
// 4. View lar mustaqil — alohida test qilish mumkin
// ═══════════════════════════════════════════════════════════════
struct BoshSahifaKorinishi: View {
// @EnvironmentObject — ContentView dan uzatilgan Coordinator
@EnvironmentObject var coordinator: AppCoordinator
var body: some View {
List {
// Push navigatsiya — stack ga qo'shish
Button("Maqolaga o'tish") {
coordinator.push(.maqolaTafsiloti(maqolaId: 1))
// Stack: [.boshSahifa, .maqolaTafsiloti(1)]
}
Button("Profilni ochish") {
coordinator.push(.profil)
// Stack: [.boshSahifa, .profil]
}
// Sheet navigatsiya — modal oyna
Button("Sozlamalar (sheet)") {
coordinator.present(.sozlamalar)
// Sheet ochiladi — back button yo'q, swipe down bilan yopiladi
}
// Full screen — to'liq ekranli modal
Button("Yangi maqola (fullscreen)") {
coordinator.presentFullScreen(.maqolaMuharriri(maqolaId: nil))
// nil = yangi maqola yaratish
}
}
.navigationTitle("Bosh sahifa")
}
}
Coordinator diagrammasi
┌──────────────┐
│ AppCoordinator│
│ │
│ path: [...] │
│ sheet: ? │
└──────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ BoshSahifa│ │ Tafsilot │ │ Profil │
│ (push) │ │ (push) │ │ (sheet) │
└──────────┘ └──────────┘ └──────────┘
Navigatsiya oqimi:
View → coordinator.push(.route) → Coordinator → path.append → NavigationStack
AppRoute ni Hashable va Identifiable qilish
// Sheet uchun Identifiable kerak
extension AppRoute: Identifiable {
var id: String {
switch self {
case .boshSahifa: return "boshSahifa"
case .maqolaTafsiloti(let id): return "maqola-\(id)"
case .profil: return "profil"
case .sozlamalar: return "sozlamalar"
case .maqolaMuharriri(let id): return "muharrir-\(id?.description ?? "yangi")"
}
}
}
🎯 Topshiriq: Coordinator qo'shish
Ilovangizga AppCoordinator qo'shing. Kamida 4 ta ekran Route ni aniqlang. Bosh sahifada tugmalar orqali push, sheet va fullscreen navigatsiyani sinab ko'ring. Deep link handler qo'shing — URL dan ekranga o'tishni amalga oshiring.