Search code examples
swiftswiftuiswiftui-navigationlinkobservableobject

Load different data by ID in the same View


I want to load different data in the same View using an ObservableObject with an Index.

As you can see in the simple demo example attached when I go through the views changing the id in order to load different data it works.

But when in any point I change another data from another observable object (in this case AnotherObservable) then my view breaks and any data is found at all.

Navigation works...

Works

Once I press Change Title button...

Issue

To test de issue just simple copy and paste the code and navigate through the data with the <- -> and then press Change title button

class DemoItemsLoader: ObservableObject {
    @Published var items = [1, 2, 3, 4]
}

class DemoArticleLoader: ObservableObject {
    @Published var items = [1 : "One", 2 : "Two" , 3 : "Three", 4 : "Four"]
    @Published var realData: String?

    func loadItemForID(id: Int) {
        realData = items[id] ?? "Not found"
    }
}
class AnotherObservable: ObservableObject {
    @Published var title: String = "Title"
}


struct Class1: View {
    @StateObject var anotherObservable = AnotherObservable()
    @StateObject var itemsLoader = DemoItemsLoader()
    var body: some View {
        NavigationView{
            VStack {
                ForEach(itemsLoader.items, id: \.self) { item in
                    NavigationLink(
                        destination: Class2(anotherObservable: anotherObservable, articleLoader: DemoArticleLoader(), id: item)) {
                        Text("\(item)")
                    }
                }
            }
        }
    }
}

struct Class2: View {
    @ObservedObject var anotherObservable: AnotherObservable
    @ObservedObject var articleLoader: DemoArticleLoader
    @State var id: Int

    var body: some View {
        VStack {
            Text(anotherObservable.title)
            Text(articleLoader.realData ?? "Not found")
            HStack {
                Spacer()
                Button(action: {
                    if id > 1 {
                        id = id - 1
                        articleLoader.loadItemForID(id: id)
                    }
                }) {
                    Text("<-")
                }
                Button(action: {
                    if id < articleLoader.items.count {
                        id = id + 1
                        articleLoader.loadItemForID(id: id)
                    }

                }) {
                    Text("->")
                }
                Button(action: {
                    anotherObservable.title = "Another Title"
                }) {
                    Text("Change title")
                }
                Spacer()
            }
        }
        .onAppear { articleLoader.loadItemForID(id: id)}
    }
}

Solution

  • The issue is that you're calling loadItemForID in onAppear only:

    .onAppear { articleLoader.loadItemForID(id: id)}
    

    And when you change title, the above function is not called again.

    The easiest solution is probably to store DemoArticleLoader() as a @StateObject, so it's not recreated every time:

    struct Class1: View {
        @StateObject var anotherObservable = AnotherObservable()
        @StateObject var itemsLoader = DemoItemsLoader()
        @StateObject var articleLoader = DemoArticleLoader() // create once
    
        var body: some View {
            NavigationView {
                VStack {
                    ForEach(itemsLoader.items, id: \.self) { item in
                        NavigationLink(
                            // pass here
                            destination: Class2(anotherObservable: anotherObservable, articleLoader: articleLoader, id: item)) {
                            Text("\(item)")
                        }
                    }
                }
            }
        }
    }