使用 NavigationStack 恢复导航状态会破坏绑定

问题描述 投票:0回答:1

我正在开发一个 iOS 应用程序,它列出了 NavigationStack 内列表中的项目。当用户单击某个项目时,它会显示其详细信息,如果他单击“编辑”按钮,他可以编辑其值(在另一个视图上)。更新任何值都会刷新详细视图和列表上的值。

一切正常,除了当我在版本视图上杀死应用程序时,然后,在下次执行应用程序时,NavigationStack 正确恢复状态并且用户被带回版本视图,但如果我然后更改项目名称并单击“保存”,更改将被保存,但不会在详细信息视图和列表视图中更新。就像绑定被破坏一样,但如果我返回到详细信息视图或列表并再次手动导航到版本视图,则绑定会再次工作,并且任何编辑的值都会显示在详细信息和列表视图上。

下面是我的代码的简化:

@Observable class Item : Hashable, Equatable, Codable {
    var name:String
    
    init(_ name: String) {
        self.name = name
    }
    
    static func == (lhs: Item, rhs: Item) -> Bool {
        return lhs.name == rhs.name
    }
    
    public func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }
}

@Observable class NavPathStore {
    
    private let savePath = URL.documentsDirectory.appending(path: "SavedPathStore")
    
    public var path = NavigationPath() {
        didSet {
            save()
        }
    }

    init() {
        if let data = try? Data(contentsOf: savePath) {
            if let decoded = try? JSONDecoder().decode(NavigationPath.CodableRepresentation.self, from: data) {
                path = NavigationPath(decoded)
                return
            }
        }
    }

    func save() {
        guard let representation = path.codable else { return }
        do {
            let data = try JSONEncoder().encode(representation)
            try data.write(to: savePath)
        } catch {
            print("Failed to save navigation data")
        }
    }
}


@main
struct TestApp: App {
        
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State var navigation = NavPathStore()
    @State var items: [Item] = [Item("First"), Item("Second")]

    var body: some View {
        NavigationStack(path: $navigation.path) {
            List {
                ForEach(items, id:\.name) { item in
                    NavigationLink(value: Navigation_DetailView(item: item)) {
                        Text("Item \(item.name)")
                    }
                }
            }
            .navigationDestination(for: Navigation_DetailView.self) { nav in
                DetailView(item: nav.item)
            }
        }
    }
}

struct Navigation_DetailView: Hashable, Equatable, Codable {
    var item: Item
}

struct Navigation_EditView: Hashable, Equatable, Codable {}

struct DetailView: View {

    let item: Item
    
    var body: some View {
        Text("Details for item \(item.name)")
            .toolbar {
                NavigationLink("Edit", value: Navigation_EditView())
            }
            .navigationDestination(for: Navigation_EditView.self) { nav in
                EditView(item: self.item)
            }
    }
}

struct EditView : View {
    @Environment(\.dismiss) private var dismiss
    let item: Item
    @State private var name: String

    init(item: Item) {
        self.item = item
        self.name = item.name
    }
    
    var body: some View {
        VStack {
            TextField("Name", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()
            
            Button("Save") {
                item.name = self.name
                dismiss()
            }
        }
        .navigationTitle("Item Edition")
    }
}

我怀疑问题与我调用 NavigationLinks 的方式或使用 NavPathStore 类存储导航的方式有关。

应该如何实现导航才能不破坏绑定?

编辑:我编辑了问题以提供最小的可重现示例。 编辑 2:再次编辑以更好地阐明如何重现问题。

swift swiftui swiftui-navigationlink swiftui-navigationstack swiftui-state
1个回答
0
投票

您在

@State
中初始化了
EditView.init
。您应该将
name
初始化为某个常量字符串,然后将其设置为
onAppear
中所需的值。请参阅此讨论

@State private var name: String = ""

// ...

.onAppear {
    name = item.name
}

现在更改已在

DetailView
中更新,但
ContentView
仍会显示项目的初始名称。这是因为导航路径中的
Item
对象是与此处完全不同的对象

@State var items: [Item] = [Item("First"), Item("Second")]
当您解码存储的导航路径时,您间接创建了一个

Item

对象,该对象存储在
Navigation_DetailView
中。在 
ContentView
 中,由于上面的行,您进一步创建了另外 2 个 
Item
 对象。

您实际上从未将这两个

Item

 对象保存在文件中。您只保存导航路径上的所有 
Item
,这显然不会同时保存这两个项目。

您需要彻底改变设计。以下是如何执行此操作的概述:

    将项目保存在单独的文件中,我们称之为 SavedItemsStore
  • 应用程序启动后,读取 SavedPathStore 和 SavedItemsStore。
  • 然后,将导航路径中的
  • Item
     更改为 SavedItemsStore 中的 
    Item
     对象。您可以通过匹配 
    id
     来找到彼此对应的项目,其中 
    id
     是您将添加到 
    Item
     的新属性。或者您可以只匹配他们的
    name
  • 由于您将读取存储在导航路径中的项目,这意味着您不能再使用异构导航路径。您需要使用要创建的某种类型的数组,可以从中读取
  • Item
作为说明性示例:

@Observable class Item : Identifiable, Hashable, Equatable, Codable { var name:String let id: UUID init(_ name: String, _ id: UUID = UUID()) { self.name = name self.id = id } static func == (lhs: Item, rhs: Item) -> Bool { return lhs.name == rhs.name && lhs.id == rhs.id } public func hash(into hasher: inout Hasher) { hasher.combine(name) hasher.combine(id) } } @Observable class Storage { private let savePath = URL.documentsDirectory.appending(path: "SavedPathStore") private let itemsSavePath = URL.documentsDirectory.appending(path: "SavedItemsStore") var path = [Navigation]() var items = [Item]() init() { print(savePath) let decoder = JSONDecoder() if let data = try? Data(contentsOf: itemsSavePath) { if let decoded = try? decoder.decode([Item].self, from: data) { items = decoded } } else { items = [Item("First"), Item("Second")] } if let data = try? Data(contentsOf: savePath) { if let decoded = try? decoder.decode([Navigation].self, from: data) { path = decoded.compactMap { switch $0 { case .detail(let item): items.first { $0.id == item.id }.map { .detail($0) } case .edit(let item): items.first { $0.id == item.id }.map { .edit($0) } } } } } } func save() { do { let data = try JSONEncoder().encode(path) try data.write(to: savePath) let itemsData = try JSONEncoder().encode(items) try itemsData.write(to: itemsSavePath) } catch { print("Failed to save data") } } } struct ContentView: View { @State var storage = Storage() @State var items: [Item] = [] var body: some View { NavigationStack(path: $storage.path) { List { ForEach(items, id:\.name) { item in NavigationLink(value: Navigation.detail(item)) { Text("Item \(item.name)") } } } .navigationDestination(for: Navigation.self) { nav in switch nav { case .detail(let item): DetailView(item: item) case .edit(let item): EditView(item: item) } } } .onAppear { items = storage.items } .onChange(of: storage.path) { storage.save() } } } enum Navigation: Hashable, Codable { case detail(Item) case edit(Item) } struct DetailView: View { let item: Item var body: some View { Text("Details for item \(item.name)") .toolbar { NavigationLink("Edit", value: Navigation.edit(item)) } } } struct EditView : View { @Environment(\.dismiss) private var dismiss let item: Item @State private var name: String = "" init(item: Item) { self.item = item } var body: some View { VStack { TextField("Name", text: $name) .textFieldStyle(.roundedBorder) .padding() Button("Save") { item.name = self.name dismiss() } } .navigationTitle("Item Edition") .onAppear { name = item.name } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.