SwiftUI 不更新第二个 NavigationLink 目的地

SwiftUI doesn't update second NavigationLink destination

我有一个包含推送视图的行的列表。该视图有另一个列表,它推动另一个视图。原始List,以及最先推送的List会在数据变化时更新。但是,最后一个视图在推送时不会更新。当我向后滑动时,视图不再更新,尽管它曾经是。

主页视图 > 用户视图 > 项目视图

User 和 Item 是可识别的结构。我试过使它们可散列并使用 id: \.self,但这似乎也不起作用。

class App: ObservableObject {
    @Published var users = [User]()
}

struct HomeView {

    @EnvironmentObject var app: App

    var body {
        List {
            Section {
                ForEach(app.users) { user in
                    NavigationLink(destination: UserView(user: user)) {
                        Text(user.name)
                    }
                }
            }
        }
    }

}

// Updates fine when `app.users` updates
// Stops updating after going back from ItemView
struct UserView {

    let user: User

    var body {
        List {
            Section {
                ForEach(user.items) { item in
                    NavigationLink(destination: ItemView(user: user, item: item)) {
                        Text(item.name)
                    }
                }
            }
        }
    }

}

/// Does not update when app.users updates
struct ItemView {

    let user: User
    let item: Item

    var body {
        List {
            Section {
                ForEach(item.details) { detail in
                    Text(detail)
                }
            }
        }
    }

}

这是我所做的测试,运行良好,一切都按预期更新。

struct User: Identifiable {
    var id: String
    var name: String
    var items: [Item]
}

struct Item: Identifiable {
    var id: String
    var name: String
    var details: [String]
}

class App: ObservableObject {
    @Published var users = [User]()

    init() {
    let items1 = [Item(id: UUID().uuidString, name: "item1", details: ["d1","d2"]), Item(id: UUID().uuidString, name: "item2", details: ["d3","d4"])]
    let items2 = [Item(id: UUID().uuidString, name: "item3", details: ["e1","e2"]), Item(id: UUID().uuidString, name: "item4", details: ["e3","e4"])]
    users.append(User(id: UUID().uuidString, name: "user1", items: items1))
    users.append(User(id: UUID().uuidString, name: "user2", items: items2))
    }
}


struct ContentView: View {

@ObservedObject var app = App()

var body: some View {
    NavigationView {
        List {
            ForEach(app.users) { user in
                NavigationLink(destination: UserView(user: user)) {
                    Text(user.name)
                }
            }
        }
    }
}
}


struct UserView: View {
@State var user: User

var body: some View {
    List {
        ForEach(user.items) { item in
            NavigationLink(destination: ItemView(item: item)) {
                Text(item.name)
            }
        }
    }
}
}

struct ItemView: View {
@State var item: Item

var body: some View {
    List {
        ForEach(item.details, id: \.self) { detail in
            Text(detail)
        }
    }
}
}

好的,我想我开始明白你想要什么了。这样的事情怎么样:

struct ContentView: View {

@EnvironmentObject var app: App

var body: some View {
    NavigationView {
        List(0..<app.users.count) { i in
            NavigationLink(destination: UserView(user: self.$app.users[i])) {
                Text(self.app.users[i].name)
            }
        }
    }
}


struct UserView: View {
@Binding var user: User
var body: some View {
    List(0..<user.items.count) { i in
            NavigationLink(destination:
            ItemView(item: self.$user.items[i])) {
                Text(self.user.items[i].name)
            }
        }

}
}

struct ItemView: View {
@Binding var item: Item
@State var hasChanged = false
var body: some View {
    VStack {
        Button(action: {
            self.item.details.append(contentsOf: ["wx","wy"])
            self.hasChanged.toggle()
        }) {
            Text("Add an item")
        }
        List(0..<item.details.count, id: \.self) { i in
            Text(self.item.details[i])
        }
    }
}
}

经过长时间的搜索,我想出了一些我尚未在 Internet 上的其他地方看到的东西。我正在使用来自父视图的 @State 初始化我的视图,并使用 onReceive 更新它。此外,onReceive/onAppear 检查项目是否仍然有效并在需要时弹出视图。进行初始化以设置状态并将其设为私有可能更正确。

主要原因是删除导致崩溃。这是我用来测试更改和删除源代码的完整示例。

struct Item: Identifiable {
    var id: String
    var name: String
    var accounts: [Account]
}

struct Account: Identifiable {
    var id: String
    var name: String
}

class App: ObservableObject {
    @Published var items: [Item] = [
        Item(id: "a", name: "A", accounts: [
            Account(id: "1", name: "one"),
            Account(id: "2", name: "two"),
            Account(id: "3", name: "three")
        ])
    ]
}

struct RootView: View {
    var body: some View {
        NavigationView {
            ContentView().environmentObject(App())
        }
    }
}

struct ContentView: View {

    @EnvironmentObject var app: App

    var body: some View {
        List {
            ForEach(app.items) { item in
                NavigationLink(destination: ItemView(item: item)) {
                    Text("\(item.id) - \(item.name)")
                }
            }
            Button(action: { self.app.items[0].name = "XXX" }) {
                Text("Change Item Name")
            }
            Button(action: { self.app.items = [] }) {
                Text("Clear")
            }
        }
    }

}

struct ItemView: View {

    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var app: App

    @State var item: Item

    var body: some View {
        List {
            Text("\(item.id) - \(item.name)")
            ForEach(item.accounts) { account in
                NavigationLink(destination: AccountView(item: self.item, account: account)) {
                    Text("\(account.id) - \(account.name)")
                }
            }
            Button(action: { self.app.items[0].name = "XXX" }) {
                Text("Change Item Name")
            }
            Button(action: { self.app.items[0].accounts[0].name = "AAA" }) {
                Text("Change Account Name")
            }
            Button(action: { self.app.items = [] }) {
                Text("Clear")
            }
        }
        .onReceive(app.$items) { items in
            guard let item = items.first(where: { [=10=].id == self.item.id }) else {
                self.presentationMode.wrappedValue.dismiss()
                return
            }
            self.item = item
        }
        .onAppear {
            if !self.app.items.contains(where: { [=10=].id == self.item.id }) {
                self.presentationMode.wrappedValue.dismiss()
            }
        }
    }

}

struct AccountView: View {

    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var app: App

    @State var item: Item
    @State var account: Account

    var body: some View {
        List {
            Text("\(item.id) - \(item.name)")
            Text("\(account.id) - \(account.name)")
            Button(action: { self.app.items[0].name = "XXX" }) {
                Text("Change Item Name")
            }
            Button(action: { self.app.items[0].accounts[0].name = "AAA" }) {
                Text("Change Account Name")
            }
            Button(action: { self.app.items = [] }) {
                Text("Clear")
            }
        }
        .onReceive(app.$items) { items in
            guard
                let item = items.first(where: { [=10=].id == self.item.id }),
                let account = item.accounts.first(where: { [=10=].id == self.account.id })
            else {
                self.presentationMode.wrappedValue.dismiss()
                return
            }
            self.item = item
            self.account = account
        }
    }
}

我遇到了同样的问题,现在我根据本主题中的答案开发如下解决方案

import SwiftUI

struct TItem: Identifiable {
    var id: String
    var name: String
    var accounts: [TAccount]
}

struct TAccount: Identifiable {
    var id: String
    var name: String
}

class TApp: ObservableObject {
    @Published var items: [TItem] = [
        TItem(id: "a", name: "A", accounts: [
            TAccount(id: "1", name: "Alice"),
            TAccount(id: "2", name: "Amazon"),
            TAccount(id: "3", name: "Arfa")
        ]),
        TItem(id: "b", name: "B", accounts: [
            TAccount(id: "1", name: "Boris"),
            TAccount(id: "2", name: "Bob"),
            TAccount(id: "3", name: "Blaster")
        ])
    ]
}

struct TRootView: View {
    var body: some View {
        NavigationView {
            TContentView().environmentObject(TApp())
        }
    }
}

struct TContentView: View {
    
    @EnvironmentObject var app: TApp
    
    var body: some View {
        List {
            ForEach(0..<app.items.count) { index in
                let item = $app.items[index]
                let wItem = item.wrappedValue
                NavigationLink(destination: TItemView(item: item)) {
                    VStack(alignment: .leading) {
                        Text("\(wItem.id) - \(wItem.name)")
                        HStack {
                            ForEach(wItem.accounts) { account in
                                Text("\(account.name)")
                                    .font(.callout)
                                    .foregroundColor(.gray)
                            }
                        }
                    }
                }
            }
            Button(action: { app.items[0].name = "XXX" }) {
                Text("Change Item Name")
            }
        }
    }
    
}

struct TItemView: View {
    
    @Binding var item: TItem

    var body: some View {
        List {
            Text("\(item.id) - \(item.name)")
            ForEach(0..<item.accounts.count) { index in
                NavigationLink(destination: TAccountView(account: $item.accounts[index])) {
                    Text("\(item.accounts[index].id) - \(item.accounts[index].name)")
                }
            }
            Button(action: { self.$item.name.wrappedValue = "XXX" }) {
                Text("Change Item Name")
            }
        }

    }
    
}

struct TAccountView: View {
    
    @Binding var account: TAccount
    
    
    var body: some View {
        List {
            Text("\(account.id) - \(account.name)")
            Button(action: { $account.name.wrappedValue = "CCC" }) {
                Text("Change Account Name")
            }
        }
    }
}


struct TRootView_Previews: PreviewProvider {
    static var previews: some View {
        TRootView()
    }
}