You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SwiftUI NavigationStack中导航子视图且避免重复初始化

SwiftUI NavigationStack 视图重复初始化问题

导航目标

  • 功能1:SelectDepartureArrivalView -> CommutingView -> ArrivedView
  • 功能2:ScheduleView

功能说明:功能1包含3个视图,功能2仅包含1个视图。

预期与实际结果

  • 预期:每个视图仅初始化一次
  • 实际结果:每次导航操作都会触发视图初始化,更严重的是进入ArrivedView时存在2个存活的SelectDepartureArrivalViewModel实例。

复现代码

//
//  ContentView.swift
//  NavigationStackExample
//
//  Created by Jason Rich Darmawan Onggo Putra on 29/07/23.
//

import SwiftUI

enum Page {
    // 功能1
    case SelectDepartureArrivalView
    case CommutingView
    case ArrivedView
    
    // 功能2
    case ScheduleView
}

class SelectDepartureArrivalViewModel: ObservableObject {
    init() { print("\(type(of: self)) \(#function)") }
    
    deinit { print("\(type(of: self)) \(#function)") }
}

struct SelectDepartureArrivalView: View {
    @EnvironmentObject var coordinator: Coordinator
    
    @ObservedObject var viewModel: SelectDepartureArrivalViewModel
    
    init(viewModel: SelectDepartureArrivalViewModel = SelectDepartureArrivalViewModel()) {
        self.viewModel = viewModel
        print("\(type(of: self)) \(#function)")
    }
    
    var body: some View {
        VStack {
            Text("SelectDepartureArrivalView")
            Button {
                coordinator.path.append(.CommutingView)
            } label: {
                Text("模拟通勤")
            }
        }
        .onAppear { print("\(type(of: self)) appear ")}
        .onDisappear { print("\(type(of: self)) disappear") }
    }
}

class CommutingViewModel: ObservableObject {
    init() { print("\(type(of: self)) \(#function)") }
    
    deinit { print("\(type(of: self)) \(#function)") }
}

struct CommutingView: View {
    @EnvironmentObject var coordinator: Coordinator
    
    @ObservedObject var viewModel: CommutingViewModel
    
    init(viewModel: CommutingViewModel = CommutingViewModel()) {
        self.viewModel = viewModel
        print("\(type(of: self)) \(#function)")
    }
    
    var body: some View {
        VStack {
            Text("CommutingView")
            Button {
                coordinator.path.append(.ArrivedView)
            } label: {
                Text("模拟到达")
            }
        }
        .onAppear { print("\(type(of: self)) appear ")}
        .onDisappear { print("\(type(of: self)) disappear") }
    }
}

class ArrivedViewModel: ObservableObject {
    init() { print("\(type(of: self)) \(#function)") }
    
    deinit { print("\(type(of: self)) \(#function)") }
}

struct ArrivedView: View {
    @ObservedObject var viewModel: ArrivedViewModel
    
    @EnvironmentObject var coordinator: Coordinator
    
    init(viewModel: ArrivedViewModel = ArrivedViewModel()) {
        self.viewModel = viewModel
        print("\(type(of: self)) \(#function)")
    }
    
    var body: some View {
        VStack {
            Text("ArrivedView")
            Button {
                coordinator.path = .init()
            } label: {
                Text("模拟退出")
            }
        }
        .onAppear { print("\(type(of: self)) appear ")}
        .onDisappear { print("\(type(of: self)) disappear") }
    }
}

class ScheduleViewModel: ObservableObject {
    init() { print("\(type(of: self)) \(#function)") }
    
    deinit { print("\(type(of: self)) \(#function)") }
}

struct ScheduleView: View {
    @ObservedObject var viewModel: ScheduleViewModel
    
    init(viewModel: ScheduleViewModel = ScheduleViewModel()) {
        self.viewModel = viewModel
        print("\(type(of: self)) \(#function)")
    }
    
    var body: some View {
        VStack {
            Text("ScheduleView")
        }
        .onAppear { print("\(type(of: self)) appear ")}
        .onDisappear { print("\(type(of: self)) disappear") }
    }
}

class Coordinator: ObservableObject {
    @Published var path: [Page]
    
    init(path: [Page] = [Page]()) {
        self.path = path
    }
}

struct ContentView: View {
    @StateObject private var coordinator: Coordinator
    
    init(coordinator: Coordinator = Coordinator()) {
        self._coordinator = StateObject(wrappedValue: coordinator)
        
        print("\(type(of: self)) \(#function)")
    }
    
    var body: some View {
        NavigationStack(path: $coordinator.path) {
            VStack {
                Button {
                    coordinator.path.append(.SelectDepartureArrivalView)
                } label: {
                    Text("模拟选择出发/到达")
                }
                
                Button {
                    coordinator.path.append(.ScheduleView)
                } label: {
                    Text("模拟跳转到日程视图")
                }
            }
            .navigationDestination(for: Page.self) { page in
                switch page {
                case .SelectDepartureArrivalView: SelectDepartureArrivalView()
                case .CommutingView: CommutingView()
                case .ArrivedView: ArrivedView()
                
                case .ScheduleView: ScheduleView()
                }
            }
        }
        .onAppear { print("\(type(of: self)) appear ")}
        .onDisappear { print("\(type(of: self)) disappear") }
        .environmentObject(coordinator)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

操作日志与状态

步骤1:Root -> coordinator.path.append(.SelectDepartureArrivalView)

ContentView init(coordinator:)
ContentView appear
SelectDepartureArrivalViewModel init()
SelectDepartureArrivalView init(viewModel:)
SelectDepartureArrivalView appear 
SelectDepartureArrivalViewModel init()
SelectDepartureArrivalView init(viewModel:)
SelectDepartureArrivalViewModel deinit

当前状态:

  • 1个ContentView处于显示状态
  • 1个SelectDepartureArrivalViewModel实例存活
  • 1个SelectDepartureArrivalView处于显示状态

步骤2:SelectDepartureArrivalView -> coordinator.path.append(.CommutingView)

SelectDepartureArrivalViewModel init()
SelectDepartureArrivalView init(viewModel:)
CommutingViewModel init()
CommutingView init(viewModel:)
CommutingView appear 
SelectDepartureArrivalViewModel init()
SelectDepartureArrivalView init(viewModel:)
CommutingViewModel init()
CommutingView init(viewModel:)
SelectDepartureArrivalViewModel deinit
SelectDepartureArrivalViewModel deinit
CommutingViewModel deinit
SelectDepartureArrivalView disappear

当前状态:

  • 1个ContentView处于显示状态
  • 1个SelectDepartureArrivalViewModel实例存活
  • 1个CommutingViewModel实例存活
  • 1个CommutingView处于显示状态

步骤3:CommutingView -> coordinator.path.append(.ArrivedView)

SelectDepartureArrivalViewModel init()
SelectDepartureArrivalView init(viewModel:)
CommutingViewModel init()
CommutingView init(viewModel:)
ArrivedViewModel init()
ArrivedView init(viewModel:)
ArrivedView appear 
SelectDepartureArrivalViewModel init()
SelectDepartureArrivalView init(viewModel:)
CommutingViewModel init()
CommutingView init(viewModel:)
ArrivedViewModel init()
ArrivedView init(viewModel:)
CommutingViewModel deinit
CommutingViewModel deinit
ArrivedViewModel deinit
SelectDepartureArrivalViewModel deinit
CommutingView disappear

当前状态:

  • 1个ContentView处于显示状态
  • 2个SelectDepartureArrivalViewModel实例存活
  • 1个CommutingViewModel实例存活
  • 1个ArrivedViewModel实例存活
  • 1个ArrivedView处于显示状态

步骤4:ArrivedView -> coordinator.path = .init()

SelectDepartureArrivalViewModel deinit
SelectDepartureArrivalViewModel deinit
CommutingViewModel deinit
ArrivedView disappear
ArrivedViewModel deinit

当前状态:

  • 1个ContentView处于显示状态

内容的提问来源于stack exchange,提问作者Jason Rich Darmawan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 00:02:32