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

SwiftUI新NavigationStack结合@State异常:是Bug还是用法有误?

SwiftUI NavigationStack 中@State未重置的问题分析

将SwiftUI应用迁移至使用NavigationStack(path: $visibilityStack)进行程序化导航后,发现@State变量存在异常行为:替换栈中视图时,@State会保留之前的值,而非重新初始化,同时.onAppear也不会触发。但使用旧版NavigationView配合isPresented时,视图能正确初始化并重置状态。

复现步骤

  • 点击NavigationLink进入子视图FruitView
  • 点击“Show other fruits”按钮,将showMoreText设为true
  • 点击任意水果按钮切换视图,新视图的showMoreText仍为true(尽管初始化时声明为false),且.onAppear未执行

完整代码示例

Fruit模型与导航控制器

class Fruit: Hashable, Identifiable {
    var id: String
    
    init(name: String) {
        self.id = name
    }
    
    static func == (lhs: Fruit, rhs: Fruit) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}
let fruitList = [Fruit(name: "Banana"), Fruit(name: "Strawberry"), Fruit(name: "Pineapple")]

class FruitViewController: ObservableObject {
    @Published var visibilityStack : [Fruit] = []

    func openView(fruit: Fruit) {
        visibilityStack.removeAll()
        visibilityStack.append(fruit)
        // visibilityStack[0] = fruit    // 效果相同
    }
    
    func bananaAndStrawberry() {
        visibilityStack.append(fruitList[0])
        visibilityStack.append(fruitList[1])
    }
}

主ContentView(含NavigationStack)

struct ContentView: View {
    @StateObject private var fruitViewController = FruitViewController()
    
    var body: some View {
        NavigationStack(path: $fruitViewController.visibilityStack) {
            VStack {
                Button("Pile Banana and Strawberry", action: bananaAndStrawberry)
                    .padding(40)
                List(fruitList) { fruit in
                    NavigationLink(value: fruit) {
                        Text(fruit.id)
                    }
                }
            }
            .navigationDestination(for: Fruit.self) { fruit in
                FruitView(fruitViewController: fruitViewController, fruit: fruit)
            }
        }
    }
    
    func bananaAndStrawberry() {
        fruitViewController.bananaAndStrawberry()
    }
}

子视图FruitView(存在@State重置问题)

struct FruitView: View {
    @State private var showMoreText = false
    @ObservedObject var fruitViewController: FruitViewController
    var fruit: Fruit
    
    var body: some View {
        Text("Selected fruit: " + fruit.id)
        if showMoreText {
            Text("The text should disappear when moving to another fruit")
            HStack(spacing: 10) {
                ForEach(fruitList) { aFruit in
                    Button(aFruit.id) {
                        fruitViewController.openView(fruit: aFruit)
                    }
                }
            }
        } else {
            Button("Show other fruits", action: showButtons)
        }
    }
    
    func showButtons() {
        showMoreText = true
    }
}

补充测试发现

通过栈操作进一步验证问题:

  1. 调用visibilityStack.append添加3个视图,初始状态均为showMoreText = false:
    View 1 - state 0
    View 2 - state 0
    View 3 - state 0(当前显示)
    
  2. 修改View 3的状态为showMoreText = true:
    View 1 - state 0
    View 2 - state 0
    View 3 - state 1(当前显示)
    
  3. 调用visibilityStack.remove(at: 1)移除View 2,结果栈结构变为:
    View 1 - state 0
    View 3 - state 0(当前显示)
    

可见实际被销毁的是栈最后一个视图,而非指定的View 2,说明NavigationStack的视图复用逻辑并非完全基于栈的结构,而是结合了模型标识进行视图实例的复用或销毁。

临时解决思路

目前可行的方案是将原本存放在@State中的状态迁移到外部的ObservableObject(如FruitViewController)中,通过绑定或同步的方式管理视图状态,确保切换视图时能正确重置状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 08:15:41