Tüm Yazılar
KategoriSwiftUI
Okuma Süresi
22 dk
Yayın Tarihi
2025-08-05
Kelime Sayısı
1.353kelime

Kahveni hazırla - bu içerikli bir makale!

SwiftUI NavigationStack Deep Dive: Modern Navigation Rehberi

Özet

NavigationStack, NavigationPath, programmatic navigation, deep linking ve coordinator pattern ile modern SwiftUI navigation.

  • NavigationStack, NavigationView'in isActive sınırlamalarını kaldırıp type-safe routing sağlar.
  • @Observable Router sınıfı NavigationPath'i tutar; navigate/pop/popToRoot sunar.
  • onOpenURL ile deep link URL'leri route'lara çevrilip router'a append edilir.
  • NavigationSplitView sidebar/content/detail üç kolonlu iPad navigasyonu sağlar.
SwiftUI NavigationStack Deep Dive: Modern Navigation Rehberi

iOS 16 ile gelen NavigationStack, SwiftUI navigation'da cig atilmasini sagladi. NavigationView'in sinirlamalarini ortadan kaldiran bu yapi, programmatic navigation, type-safe routing ve deep linking destegi sunuyor. Bu rehberde NavigationStack'i tum detaylariyla inceleyecegiz.


Icindekiler


1. NavigationView vs NavigationStack

Karsilastirma Tablosu

Ozellik
NavigationView
NavigationStack
Programmatic Nav
Sinirli (isActive)
Tam (NavigationPath)
Type Safety
Zayif
Guclu
Deep Linking
Zor
Kolay
Pop to Root
Hack gerekli
path = []
State Management
Zor
NavigationPath
Multi-level Push
Her seviye ayri
Tek path yonetir
Performance
Orta
Iyi (lazy loading)
Deprecation
iOS 16+ deprecated
Aktif

2. Temel Kullanim

swift
1import SwiftUI
2 
3struct ContentView: View {
4 let fruits = ["Elma", "Armut", "Portakal", "Cilek", "Muz"]
5 
6 var body: some View {
7 NavigationStack {
8 List(fruits, id: \.self) { fruit in
9 NavigationLink(fruit, value: fruit)
10 }
11 .navigationTitle("Meyveler")
12 .navigationDestination(for: String.self) { fruit in
13 FruitDetailView(name: fruit)
14 }
15 }
16 }
17}
18 
19struct FruitDetailView: View {
20 let name: String
21 
22 var body: some View {
23 VStack(spacing: 20) {
24 Text(name)
25 .font(.largeTitle)
26 Text("Bu bir \(name) detay sayfasidir.")
27 .font(.body)
28 .foregroundStyle(.secondary)
29 }
30 .navigationTitle(name)
31 }
32}

Birden Fazla Tip icin Navigation Destination

swift
1struct AppView: View {
2 var body: some View {
3 NavigationStack {
4 VStack(spacing: 16) {
5 NavigationLink("Profil", value: Route.profile(userId: "123"))
6 NavigationLink("Ayarlar", value: Route.settings)
7 NavigationLink("Blog", value: Route.blog(slug: "swift-regex"))
8 }
9 .navigationDestination(for: Route.self) { route in
10 switch route {
11 case .profile(let userId):
12 ProfileView(userId: userId)
13 case .settings:
14 SettingsView()
15 case .blog(let slug):
16 BlogDetailView(slug: slug)
17 }
18 }
19 }
20 }
21}
22 
23enum Route: Hashable {
24 case profile(userId: String)
25 case settings
26 case blog(slug: String)
27}

3. NavigationPath ile Programmatic Navigation

NavigationPath, navigation stack'in durumunu yoneten type-erased bir koleksiyondur:

swift
1import SwiftUI
2 
3@Observable
4class Router {
5 var path = NavigationPath()
6 
7 func navigate(to route: Route) {
8 path.append(route)
9 }
10 
11 func popToRoot() {
12 path = NavigationPath()
13 }
14 
15 func pop() {
16 if !path.isEmpty {
17 path.removeLast()
18 }
19 }
20 
21 func pop(count: Int) {
22 let removeCount = min(count, path.count)
23 path.removeLast(removeCount)
24 }
25}
26 
27struct RootView: View {
28 @State private var router = Router()
29 
30 var body: some View {
31 NavigationStack(path: $router.path) {
32 HomeView()
33 .navigationDestination(for: Route.self) { route in
34 destinationView(for: route)
35 }
36 }
37 .environment(router)
38 }
39 
40 @ViewBuilder
41 func destinationView(for route: Route) -> some View {
42 switch route {
43 case .profile(let userId):
44 ProfileView(userId: userId)
45 case .settings:
46 SettingsView()
47 case .blog(let slug):
48 BlogDetailView(slug: slug)
49 }
50 }
51}
52 
53// Herhangi bir child view'den navigation
54struct HomeView: View {
55 @Environment(Router.self) private var router
56 
57 var body: some View {
58 VStack {
59 Button("Profile Git") {
60 router.navigate(to: .profile(userId: "123"))
61 }
62 Button("Ayarlar") {
63 router.navigate(to: .settings)
64 }
65 }
66 }
67}

4. Type-Safe Routing

swift
1// Modullere ayrilmis route yapisi
2enum AppRoute: Hashable {
3 case home
4 case productList(category: String)
5 case productDetail(id: Int)
6 case cart
7 case checkout
8 case orderConfirmation(orderId: String)
9 case userProfile(userId: String)
10 case editProfile
11}
12 
13@Observable
14class AppRouter {
15 var path = NavigationPath()
16 
17 // Convenience methods
18 func showProduct(_ id: Int) {
19 path.append(AppRoute.productDetail(id: id))
20 }
21 
22 func showCart() {
23 path.append(AppRoute.cart)
24 }
25 
26 func startCheckout() {
27 path.append(AppRoute.checkout)
28 }
29 
30 func completeOrder(orderId: String) {
31 // Checkout ve cart'i kaldir, confirmation'a git
32 while path.count > 1 {
33 path.removeLast()
34 }
35 path.append(AppRoute.orderConfirmation(orderId: orderId))
36 }
37 
38 func popToRoot() {
39 path = NavigationPath()
40 }
41}

5. Deep Linking Entegrasyonu

swift
1import SwiftUI
2 
3@Observable
4class DeepLinkHandler {
5 var router: AppRouter
6 
7 init(router: AppRouter) {
8 self.router = router
9 }
10 
11 func handle(url: URL) {
12 guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
13 let host = components.host else { return }
14 
15 // URL'i route'a cevir
16 router.popToRoot()
17 
18 switch host {
19 case "product":
20 if let idStr = components.queryItems?.first(where: { $0.name == "id" })?.value,
21 let id = Int(idStr) {
22 router.showProduct(id)
23 }
24 case "profile":
25 if let userId = components.queryItems?.first(where: { $0.name == "user" })?.value {
26 router.path.append(AppRoute.userProfile(userId: userId))
27 }
28 case "cart":
29 router.showCart()
30 default:
31 break
32 }
33 }
34}
35 
36// App'te kullanim
37@main
38struct MyApp: App {
39 @State private var router = AppRouter()
40 
41 var body: some Scene {
42 WindowGroup {
43 RootView()
44 .environment(router)
45 .onOpenURL { url in
46 DeepLinkHandler(router: router).handle(url: url)
47 }
48 }
49 }
50}

6. Coordinator Pattern

swift
1// Protocol-based coordinator
2protocol Coordinator {
3 associatedtype Route: Hashable
4 var path: NavigationPath { get set }
5 func navigate(to route: Route)
6}
7 
8@Observable
9class AuthCoordinator: Coordinator {
10 var path = NavigationPath()
11 
12 enum Route: Hashable {
13 case login
14 case register
15 case forgotPassword
16 case otpVerification(phone: String)
17 }
18 
19 func navigate(to route: Route) {
20 path.append(route)
21 }
22 
23 func completeAuth() {
24 path = NavigationPath()
25 // Ana sayfaya yonlendir
26 }
27}
28 
29struct AuthFlow: View {
30 @State private var coordinator = AuthCoordinator()
31 
32 var body: some View {
33 NavigationStack(path: $coordinator.path) {
34 LoginView()
35 .navigationDestination(for: AuthCoordinator.Route.self) { route in
36 switch route {
37 case .login:
38 LoginView()
39 case .register:
40 RegisterView()
41 case .forgotPassword:
42 ForgotPasswordView()
43 case .otpVerification(let phone):
44 OTPView(phone: phone)
45 }
46 }
47 }
48 .environment(coordinator)
49 }
50}

7. Multi-Column Navigation

swift
1struct iPadNavigation: View {
2 @State private var selectedCategory: Category?
3 @State private var selectedItem: Item?
4 
5 var body: some View {
6 NavigationSplitView {
7 // Sidebar
8 List(Category.allCases, selection: $selectedCategory) { category in
9 Label(category.title, systemImage: category.icon)
10 }
11 .navigationTitle("Kategoriler")
12 } content: {
13 // Content
14 if let category = selectedCategory {
15 List(category.items, selection: $selectedItem) { item in
16 Text(item.name)
17 }
18 .navigationTitle(category.title)
19 } else {
20 ContentUnavailableView("Kategori Secin",
21 systemImage: "sidebar.left",
22 description: Text("Sol menuden bir kategori secin"))
23 }
24 } detail: {
25 // Detail
26 if let item = selectedItem {
27 ItemDetailView(item: item)
28 } else {
29 ContentUnavailableView("Oge Secin",
30 systemImage: "doc.text",
31 description: Text("Listeden bir oge secin"))
32 }
33 }
34 .navigationSplitViewStyle(.balanced)
35 }
36}

8. State Restoration

swift
1@Observable
2class PersistableRouter {
3 var path = NavigationPath()
4 
5 private let key = "navigation_state"
6 
7 func save() {
8 guard let data = path.codable else { return }
9 if let encoded = try? JSONEncoder().encode(data) {
10 UserDefaults.standard.set(encoded, forKey: key)
11 }
12 }
13 
14 func restore() {
15 guard let data = UserDefaults.standard.data(forKey: key),
16 let decoded = try? JSONDecoder().decode(
17 NavigationPath.CodableRepresentation.self, from: data
18 ) else { return }
19 path = NavigationPath(decoded)
20 }
21}

9. Best Practices

Kural
Aciklama
Tek Router kullanin
Uygulama genelinde tek bir router yeterli
Route enum kullanin
String-based routing yerine type-safe enum
Deep link'leri test edin
Her route icin URL handler testi yazin
State restoration ekleyin
Kullanici deneyimini iyilestirir
popToRoot kolayligi
Uzun stack'leri temizlemek icin

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Tebrikler! Bu yaziyi sonuna kadar okudugun icin sana ozel bir hediyem var:

10. Sonuc

NavigationStack, SwiftUI'da navigation'in nasil yapilmasi gerektiginin cevabi. NavigationView'in sinirlamalarini tamamen ortadan kaldiriyor ve modern iOS uygulamalari icin vazgecilmez bir arac. Programmatic navigation, deep linking ve state restoration ozellikleri ile production-ready uygulamalar gelistirmek artik cok daha kolay.

Etiketler

#SwiftUI#Navigation#NavigationStack#Deep Linking#iOS#Architecture
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

12+ yıllık deneyime sahip Lead Mobile Engineer. Swift, SwiftUI, Kotlin ve Flutter ile iOS, Android ve cross-platform mimarilerde uzman. Performanslı ve kullanıcı dostu mobil uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Onay e-postasındaki bağlantıyı açıp “Aboneliğimi onayla” düğmesine bastığında aboneliğin başlar. Bültende açılma/tıklama istatistikleri tutulur; dilediğin an tek tıkla ayrılabilirsin. Gizlilik

Paylaş

İlgili İçerik