SwiftUI NavigationStack mülakat soruları: Navigasyon kalıpları 2026
iOS mülakatları için NavigationStack, NavigationPath ve modern SwiftUI navigasyon kalıpları üzerine temel sorularla hazırlık.

Navigasyon, herhangi bir iOS uygulamasının temel direklerinden birini oluşturur. iOS 16'dan itibaren NavigationStack, NavigationView'in yerini alır ve navigasyon yığını üzerinde tam programatik kontrol sunar. İşe alımcılar bu kavramların hakimiyetini teknik mülakatlar sırasında düzenli olarak ölçer.
Her soru, ayrıntılı yanıt ve çalışan kod ile birlikte gerçek bir teknik mülakatın formatını yansıtır. Kavramlar temelden ileri seviyeye doğru ilerler.
NavigationStack temelleri
Soru 1: NavigationView ile NavigationStack arasındaki fark nedir?
NavigationView (iOS 16'dan beri kullanım dışı), iç içe geçmiş NavigationLink'lere dayalı örtük bir navigasyon oluşturuyordu. NavigationStack, açık ve programatik olarak değiştirilebilen bir navigasyon yığını ile bildirimsel bir yaklaşım sunar.
// ❌ Old pattern with NavigationView (deprecated)
struct OldNavigation: View {
var body: some View {
NavigationView {
NavigationLink("Details", destination: DetailView())
}
}
}
// ✅ New pattern with NavigationStack
struct ModernNavigation: View {
// Navigation stack is explicit and controllable
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
// NavigationLink with typed value
NavigationLink("User 1", value: User(id: 1, name: "Alice"))
NavigationLink("User 2", value: User(id: 2, name: "Bob"))
}
// Destination defined by value type
.navigationDestination(for: User.self) { user in
UserDetailView(user: user)
}
}
}
}En büyük avantaj, link bildiriminin hedefinden ayrılmasıdır; bu da merkezi ve test edilebilir bir navigasyon olanaklı kılar.
Soru 2: NavigationPath nasıl çalışır?
NavigationPath, navigasyon değerlerini saklayan, türü silinmiş bir konteynerdir. Ekranların kesin türlerini bilmeden yığını yönetmeyi sağlar ve bunu yaparken derleme zamanı tip güvenliğini korur.
struct ContentView: View {
// NavigationPath can contain different Hashable types
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack(spacing: 20) {
Button("View user profile") {
// Adds a User to the stack
path.append(User(id: 1, name: "Alice"))
}
Button("View settings") {
// Adds a Settings enum to the stack
path.append(SettingsRoute.notifications)
}
Button("Back to root") {
// Clears the entire stack
path.removeLast(path.count)
}
}
.navigationDestination(for: User.self) { user in
UserDetailView(user: user)
}
.navigationDestination(for: SettingsRoute.self) { route in
SettingsView(route: route)
}
}
}
}
// Types must be Hashable
struct User: Hashable {
let id: Int
let name: String
}
enum SettingsRoute: Hashable {
case notifications
case privacy
case account
}NavigationPath dahili olarak tip silme kullanır, ancak navigationDestination aracılığıyla tipleri derleme zamanında doğrular. Karşılığı bulunmayan bir değer, sessiz bir çalışma zamanı hatasına yol açar.
Soru 3: Tam programatik bir navigasyon nasıl uygulanır?
Programatik navigasyon, kullanıcı etkileşimi olmadan kodun herhangi bir yerinden yığını kontrol etmeyi sağlar. Deep link'ler, kimlik doğrulama sonrası yönlendirmeler veya çok adımlı akışlar için vazgeçilmezdir.
// Centralized router to manage navigation
@Observable
class NavigationRouter {
var path = NavigationPath()
// Navigate to a specific screen
func navigateTo(_ destination: AppRoute) {
path.append(destination)
}
// Go back one level
func goBack() {
guard !path.isEmpty else { return }
path.removeLast()
}
// Return to root
func popToRoot() {
path.removeLast(path.count)
}
// Navigate to a complete stack (deep link)
func navigateToPath(_ routes: [AppRoute]) {
popToRoot()
for route in routes {
path.append(route)
}
}
}
// Enum defining all app routes
enum AppRoute: Hashable {
case userList
case userDetail(userId: Int)
case userEdit(userId: Int)
case settings
case settingsDetail(SettingsSection)
}
enum SettingsSection: String, Hashable {
case notifications, privacy, account
}
// Usage in main view
struct MainView: View {
@State private var router = NavigationRouter()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
destinationView(for: route)
}
}
.environment(router)
}
@ViewBuilder
private func destinationView(for route: AppRoute) -> some View {
switch route {
case .userList:
UserListView()
case .userDetail(let userId):
UserDetailView(userId: userId)
case .userEdit(let userId):
UserEditView(userId: userId)
case .settings:
SettingsView()
case .settingsDetail(let section):
SettingsDetailView(section: section)
}
}
}Bu kalıp tüm navigasyon mantığını merkezileştirir, birim testlerini ve bakımı kolaylaştırır.
İleri navigasyon kalıpları
Soru 4: NavigationStack ile deep link'ler nasıl uygulanır?
Deep link'ler, harici bir URL'den uygulamayı doğrudan belirli bir ekrana açmayı sağlar. NavigationStack ile yığın programatik olarak yeniden oluşturulabilir.
@Observable
class DeepLinkHandler {
var router: NavigationRouter
init(router: NavigationRouter) {
self.router = router
}
// Parse a URL and navigate to destination
func handle(url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let host = components.host else {
return
}
// Build navigation stack according to URL
let routes = parseRoutes(host: host, path: components.path, queryItems: components.queryItems)
router.navigateToPath(routes)
}
private func parseRoutes(host: String, path: String, queryItems: [URLQueryItem]?) -> [AppRoute] {
// myapp://users/42/edit → [.userList, .userDetail(42), .userEdit(42)]
switch host {
case "users":
return parseUserPath(path)
case "settings":
return parseSettingsPath(path)
default:
return []
}
}
private func parseUserPath(_ path: String) -> [AppRoute] {
let segments = path.split(separator: "/").map(String.init)
var routes: [AppRoute] = [.userList]
if let userIdString = segments.first, let userId = Int(userIdString) {
routes.append(.userDetail(userId: userId))
// /users/42/edit
if segments.count > 1 && segments[1] == "edit" {
routes.append(.userEdit(userId: userId))
}
}
return routes
}
private func parseSettingsPath(_ path: String) -> [AppRoute] {
var routes: [AppRoute] = [.settings]
let segments = path.split(separator: "/").map(String.init)
if let sectionString = segments.first,
let section = SettingsSection(rawValue: sectionString) {
routes.append(.settingsDetail(section))
}
return routes
}
}
// In the main App
@main
struct MyApp: App {
@State private var router = NavigationRouter()
@State private var deepLinkHandler: DeepLinkHandler?
var body: some Scene {
WindowGroup {
MainView()
.environment(router)
.onOpenURL { url in
// Handle deep links
deepLinkHandler?.handle(url: url)
}
.onAppear {
deepLinkHandler = DeepLinkHandler(router: router)
}
}
}
}Soru 5: Navigasyon durumu nasıl kalıcı hale getirilir ve geri yüklenir?
Navigasyon durumunun kalıcı hale getirilmesi, uygulama yeniden başlatıldığında kullanıcının konumunu geri yüklemeyi sağlar. NavigationPath, serileştirme için Codable desteği sunar.
// Extension to make NavigationPath persistable
extension NavigationPath {
// Encode path to Data
func encoded() -> Data? {
guard let representation = self.codable else { return nil }
return try? JSONEncoder().encode(representation)
}
// Decode from Data
static func decoded(from data: Data) -> NavigationPath? {
guard let representation = try? JSONDecoder().decode(
NavigationPath.CodableRepresentation.self,
from: data
) else {
return nil
}
return NavigationPath(representation)
}
}
// Router with persistence
@Observable
class PersistentNavigationRouter {
var path: NavigationPath {
didSet {
saveState()
}
}
private let storageKey = "navigation_path"
init() {
// Restore state at startup
if let data = UserDefaults.standard.data(forKey: storageKey),
let restored = NavigationPath.decoded(from: data) {
self.path = restored
} else {
self.path = NavigationPath()
}
}
private func saveState() {
if let data = path.encoded() {
UserDefaults.standard.set(data, forKey: storageKey)
}
}
func clearPersistedState() {
UserDefaults.standard.removeObject(forKey: storageKey)
}
}NavigationPath.codable'ın çalışması için yola eklenen tüm tiplerin Hashable'a ek olarak Codable olması gerekir. Aksi halde codable özelliği nil döner.
iOS mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Soru 6: Kimlik doğrulama akışlarıyla navigasyon nasıl yönetilir?
Kimlik doğrulama akışları genellikle giriş sonrası korumalı bir ekrana yönlendirme veya çıkış sonrası giriş ekranına dönüş gerektirir. Aşağıdaki kalıp bu geçişleri temiz biçimde yönetir.
enum AuthState {
case unauthenticated
case authenticated(User)
}
@Observable
class AuthManager {
var state: AuthState = .unauthenticated
var pendingDeepLink: URL?
func login(email: String, password: String) async throws {
// Simulated authentication
let user = try await AuthService.shared.login(email: email, password: password)
state = .authenticated(user)
}
func logout() {
state = .unauthenticated
}
}
struct RootView: View {
@State private var authManager = AuthManager()
@State private var router = NavigationRouter()
var body: some View {
Group {
switch authManager.state {
case .unauthenticated:
// Separate navigation stack for auth
AuthNavigationStack(authManager: authManager)
case .authenticated:
// Main app stack
MainNavigationStack(router: router, authManager: authManager)
}
}
.onChange(of: authManager.state) { oldState, newState in
handleAuthStateChange(from: oldState, to: newState)
}
}
private func handleAuthStateChange(from oldState: AuthState, to newState: AuthState) {
switch (oldState, newState) {
case (.unauthenticated, .authenticated):
// Login successful: process pending deep link
if let pendingURL = authManager.pendingDeepLink {
DeepLinkHandler(router: router).handle(url: pendingURL)
authManager.pendingDeepLink = nil
}
case (.authenticated, .unauthenticated):
// Logout: reset navigation
router.popToRoot()
default:
break
}
}
}
struct AuthNavigationStack: View {
let authManager: AuthManager
@State private var authPath = NavigationPath()
var body: some View {
NavigationStack(path: $authPath) {
LoginView(authManager: authManager)
.navigationDestination(for: AuthRoute.self) { route in
switch route {
case .register:
RegisterView(authManager: authManager)
case .forgotPassword:
ForgotPasswordView()
}
}
}
}
}
enum AuthRoute: Hashable {
case register
case forgotPassword
}Soru 7: NavigationStack ile modal navigasyon nasıl uygulanır?
Modal'lar ve sheet'ler kendi navigasyon bağlamlarını gerektirir. NavigationStack'i modal sunumlarla birleştirmek ayrı bir durum yönetimi gerektirir.
struct ParentView: View {
@State private var mainPath = NavigationPath()
@State private var showSettings = false
@State private var showUserProfile: User?
var body: some View {
NavigationStack(path: $mainPath) {
ContentView()
.toolbar {
Button("Settings") {
showSettings = true
}
}
.navigationDestination(for: MainRoute.self) { route in
MainRouteView(route: route)
}
}
// Sheet with its own NavigationStack
.sheet(isPresented: $showSettings) {
SettingsSheet()
}
// Conditional sheet based on item
.sheet(item: $showUserProfile) { user in
UserProfileSheet(user: user)
}
}
}
// Each sheet has its own NavigationStack
struct SettingsSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var settingsPath = NavigationPath()
var body: some View {
NavigationStack(path: $settingsPath) {
SettingsListView()
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Close") {
dismiss()
}
}
}
.navigationDestination(for: SettingsSection.self) { section in
SettingsDetailView(section: section)
}
}
}
}
// Extension to make User identifiable for sheet(item:)
extension User: Identifiable {}Durum yönetimi ve test edilebilirlik
Soru 8: Navigasyon için birim testler nasıl yazılır?
Test edilebilirlik, NavigationStack'in önemli avantajlarından biridir. Router izole edilerek birim testler navigasyon mantığını UI olmadan doğrular.
// Protocol to abstract the router
protocol NavigationRouterProtocol {
var pathCount: Int { get }
func navigateTo(_ destination: AppRoute)
func goBack()
func popToRoot()
}
// Concrete implementation
@Observable
class AppNavigationRouter: NavigationRouterProtocol {
var path = NavigationPath()
var pathCount: Int {
path.count
}
func navigateTo(_ destination: AppRoute) {
path.append(destination)
}
func goBack() {
guard !path.isEmpty else { return }
path.removeLast()
}
func popToRoot() {
path.removeLast(path.count)
}
}
// Unit tests
import XCTest
final class NavigationRouterTests: XCTestCase {
var router: AppNavigationRouter!
override func setUp() {
router = AppNavigationRouter()
}
func testNavigateToAddsToPath() {
// Given
XCTAssertEqual(router.pathCount, 0)
// When
router.navigateTo(.userDetail(userId: 42))
// Then
XCTAssertEqual(router.pathCount, 1)
}
func testGoBackRemovesLastItem() {
// Given
router.navigateTo(.userList)
router.navigateTo(.userDetail(userId: 1))
XCTAssertEqual(router.pathCount, 2)
// When
router.goBack()
// Then
XCTAssertEqual(router.pathCount, 1)
}
func testPopToRootClearsPath() {
// Given
router.navigateTo(.userList)
router.navigateTo(.userDetail(userId: 1))
router.navigateTo(.userEdit(userId: 1))
XCTAssertEqual(router.pathCount, 3)
// When
router.popToRoot()
// Then
XCTAssertEqual(router.pathCount, 0)
}
func testGoBackOnEmptyPathDoesNothing() {
// Given
XCTAssertEqual(router.pathCount, 0)
// When
router.goBack()
// Then
XCTAssertEqual(router.pathCount, 0) // No crash
}
}Soru 9: Birden çok sekmeli karmaşık navigasyon durumları nasıl yönetilir?
TabView içeren uygulamalar her sekme için ayrı bir navigasyon durumu gerektirir. Her sekme kendi bağımsız yığınını korur.
// Navigation state for each tab
@Observable
class TabNavigationState {
var homePath = NavigationPath()
var searchPath = NavigationPath()
var profilePath = NavigationPath()
var selectedTab: Tab = .home
enum Tab: Hashable {
case home, search, profile
}
func resetCurrentTab() {
switch selectedTab {
case .home:
homePath.removeLast(homePath.count)
case .search:
searchPath.removeLast(searchPath.count)
case .profile:
profilePath.removeLast(profilePath.count)
}
}
func resetAllTabs() {
homePath.removeLast(homePath.count)
searchPath.removeLast(searchPath.count)
profilePath.removeLast(profilePath.count)
}
}
struct TabRootView: View {
@State private var tabState = TabNavigationState()
var body: some View {
TabView(selection: $tabState.selectedTab) {
// Home tab
NavigationStack(path: $tabState.homePath) {
HomeView()
.navigationDestination(for: HomeRoute.self) { route in
HomeRouteView(route: route)
}
}
.tabItem { Label("Home", systemImage: "house") }
.tag(TabNavigationState.Tab.home)
// Search tab
NavigationStack(path: $tabState.searchPath) {
SearchView()
.navigationDestination(for: SearchRoute.self) { route in
SearchRouteView(route: route)
}
}
.tabItem { Label("Search", systemImage: "magnifyingglass") }
.tag(TabNavigationState.Tab.search)
// Profile tab
NavigationStack(path: $tabState.profilePath) {
ProfileView()
.navigationDestination(for: ProfileRoute.self) { route in
ProfileRouteView(route: route)
}
}
.tabItem { Label("Profile", systemImage: "person") }
.tag(TabNavigationState.Tab.profile)
}
.environment(tabState)
}
}Soru 10: NavigationStack ile performans sorunlarından nasıl kaçınılır?
Büyük navigasyon yığınları veya karmaşık hedefler performansı olumsuz etkileyebilir. Birkaç teknik render ve bellek kullanımını optimize eder.
struct OptimizedNavigationStack: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
LazyContentView()
// ✅ Lazy-loaded destinations
.navigationDestination(for: HeavyRoute.self) { route in
// View only created on navigation
HeavyDetailView(route: route)
}
}
}
}
// ✅ View with lazy content loading
struct LazyContentView: View {
@State private var items: [Item] = []
var body: some View {
// LazyVStack only creates visible views
ScrollView {
LazyVStack(spacing: 12) {
ForEach(items) { item in
NavigationLink(value: HeavyRoute.detail(item.id)) {
ItemRow(item: item)
}
}
}
}
.task {
items = await loadItems()
}
}
}
// ✅ Detail with progressive loading
struct HeavyDetailView: View {
let route: HeavyRoute
@State private var data: DetailData?
var body: some View {
Group {
if let data {
DetailContent(data: data)
} else {
ProgressView()
}
}
.task {
// Load data only when view appears
data = await loadDetailData(for: route)
}
}
}
// ❌ Avoid: eager creation of heavy views
struct BadNavigationStack: View {
let allItems: [Item]
var body: some View {
NavigationStack {
List(allItems) { item in
// Creates all destinations immediately
NavigationLink {
HeavyDetailView(item: item) // ❌ Created upfront
} label: {
ItemRow(item: item)
}
}
}
}
}Inline hedefli NavigationLink yerine her zaman navigationDestination(for:) kullanılmalıdır. İlk kalıp hedef view'ı yalnızca navigasyon anında yükler.
Sonuç
NavigationStack, tam programatik kontrol sunarak SwiftUI'da navigasyon yönetimini dönüştürür. Temel navigasyondan deep link'lere ve durum kalıcılığına kadar bu kalıpları ustalıkla kullanmak, deneyimli iOS geliştiricilerini mülakatlarda öne çıkarır.
İnceleme listesi
- ✅
NavigationViewileNavigationStackarasındaki farkı anlamak - ✅ Programatik navigasyon için
NavigationPathkullanmayı bilmek - ✅ Navigasyon için merkezi bir router uygulamak
- ✅ Yığın yeniden yapılandırması ile deep link'leri ele almak
- ✅ Navigasyon durumunu
Codableile kalıcı hale getirip geri yüklemek - ✅ Kimlik doğrulama akışlarını ana navigasyondan ayırmak
- ✅ Modal'ları
NavigationStackile doğru biçimde birleştirmek - ✅ Navigasyon mantığı için birim testler yazmak
- ✅
TabViewile çoklu sekme navigasyonunu yönetmek - ✅ Büyük navigasyon yığınları için performansı optimize etmek
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

2026'da MapKit SwiftUI Mülakatı: Açıklamalar, Kaplamalar ve Konum Belirleme
iOS mülakatları için SwiftUI ile MapKit'te uzmanlaşın: özel açıklamalar, kaplamalar, konum belirleme, yer arama ve Maps entegrasyon kalıpları.

StoreKit 2 Mülakatı: Abonelik Yönetimi ve Makbuz Doğrulama
StoreKit 2, abonelik yönetimi, makbuz doğrulama ve uygulama içi satın alma uygulaması hakkında iOS mülakat sorularında pratik Swift kod örnekleriyle uzmanlaşın.

Vision Framework ve CoreML: cihaz üzerinde ML için iOS mülakat soruları
Cihaz üzerinde makine öğrenmesi hakkında iOS mülakat soruları: görüntü tanıma, nesne algılama ve Vision Framework ile CoreML detaylı anlatımı.