SwiftUI не обновляет второй пункт назначения NavigationLink - PullRequest
0 голосов
/ 07 апреля 2020

У меня есть список со строками, которые pu sh a View. Это представление имеет другой список, который выдвигает другой вид. Первоначальный список и первый нажатый список будут обновляться при изменении данных. Однако последнее представление не обновляется при нажатии. И когда я отмахиваюсь назад, представление больше не обновляется, даже если раньше.

HomeView> UserView> ItemView

Пользователь и элемент являются структурами, которые можно идентифицировать. Я пытался сделать их Hashable и использовать 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)
                }
            }
        }
    }

}

Ответы [ 3 ]

1 голос
/ 07 апреля 2020

Хорошо, я думаю, что начинаю понимать, что вы хотите. Как насчет такого:

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])
        }
    }
}
}
0 голосов
/ 12 апреля 2020

После долгих поисков я нашел кое-что, что мне еще предстоит увидеть в другом месте на rnet. Я инициализирую свои представления с помощью @State из родительского представления и обновляю их с помощью onReceive. Кроме того, onReceive / onAppear проверяет, является ли элемент все еще действительным, и отображает представление при необходимости. Возможно, было бы правильнее сделать init для установки состояния и сделать его закрытым.

Основной причиной этого было удаление, вызывающее сбои. Вот полный пример, который я сделал для проверки изменения и удаления источника.

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: { $0.id == self.item.id }) else {
                self.presentationMode.wrappedValue.dismiss()
                return
            }
            self.item = item
        }
        .onAppear {
            if !self.app.items.contains(where: { $0.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: { $0.id == self.item.id }),
                let account = item.accounts.first(where: { $0.id == self.account.id })
            else {
                self.presentationMode.wrappedValue.dismiss()
                return
            }
            self.item = item
            self.account = account
        }
    }
}
0 голосов
/ 07 апреля 2020

Это тест, который я сделал и работает хорошо, все обновляется, как и ожидалось.

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)
        }
    }
}
}
...