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

SwiftUI含`dismiss`环境值的视图点击NavigationLink时冻结

SwiftUI NavigationStack点击NavigationLink后应用冻结的解决方案(保留@Environment(.dismiss))

问题背景

在嵌套了NavigationSplitView和NavigationStack的SwiftUI应用中,当MainView持有@Environment(\.dismiss)环境变量时,点击NavigationLink会导致应用无限冻结。移除@Environment(\.dismiss)后恢复正常,但会失去视图关闭能力。经排查,问题根源是DismissAction不遵循Equatable协议,导致视图在导航触发时陷入无限重绘循环:

  • NavigationLink的destination闭包捕获了MainView的状态和dismiss环境值
  • dismiss每次重绘都会被视为新值,触发destination重新计算
  • destination中的ChildView通过闭包调用foo()更新MainView的action状态,引发MainView重绘
  • 循环往复最终导致应用冻结

解决办法

方案1:使用navigationDestination替代内嵌destination(推荐)

这是SwiftUI 4及以上版本官方推荐的导航方式,能从根源避免闭包捕获带来的循环依赖问题:

struct MainView: View {
    @Environment(\.dismiss) private var dismiss
    @State var action: String = "Open child"
    @State private var showChild = false
    
    var body: some View {
        List {
            NavigationLink(action: { showChild = true }) {
                Text(action)
            }
        }
        .navigationDestination(isPresented: $showChild) {
            ChildView(action: { _ in foo() })
        }
    }

    func foo() {
        self.action = "Updated action"
    }
}

方案2:用LazyView延迟加载目标视图

通过LazyView包装目标视图,避免提前创建视图时捕获到不稳定的环境值,打断重绘循环:
首先定义LazyView:

struct LazyView<Content: View>: View {
    private let content: () -> Content
    
    init(_ content: @autoclosure @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        content()
    }
}

然后修改MainView的目标视图创建逻辑:

@ViewBuilder func listDestination() -> some View {
    LazyView(ChildView(action: { _ in foo() }))
}

方案3:封装DismissAction为ObservableObject

将dismiss环境值封装到可观察对象中,减少环境值变化对视图重绘的触发频率:

class DismissHandler: ObservableObject {
    private let dismiss: DismissAction
    
    init(dismiss: DismissAction) {
        self.dismiss = dismiss
    }
    
    func dismissCurrentView() {
        dismiss()
    }
}

struct MainView: View {
    @Environment(\.dismiss) private var dismiss
    @StateObject private var dismissHandler: DismissHandler!
    @State var action: String = "Open child"
    
    var body: some View {
        List {
            NavigationLink {
                ChildView(action: { _ in foo() })
            } label: {
                Text(action)
            }
        }
        .onAppear {
            dismissHandler = DismissHandler(dismiss: dismiss)
        }
    }

    func foo() {
        self.action = "Updated action"
    }
}

后续需要关闭视图时,调用dismissHandler.dismissCurrentView()即可。

内容的提问来源于stack exchange,提问作者Brandon Stillitano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 18:52:46