I have a List with rows which push a View. That view has another List which pushing another View. The original List, and the first pushed List will update when the data chan
Ok, I think I'm starting to understand what you want. How about something like this:
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])
}
}
}
}
This is the test I made and is working well, everything updates as expected.
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)
}
}
}
}
After long searches I've come up with something I have yet to see elsewhere on the internet. I am initializing my views with a @State from the parent view and updating it using onReceive. Further, onReceive/onAppear checks if the item is still valid and pops the view if needed. It might be more correct to make an init to set the State and make that private.
The main reason for this was deleting was causing crashes. Here's a complete example I made to test changing and deleting the source.
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
}
}
}