1

I currently have an app that's fetching data from an API, In the root view (let's call it Home) everything works as expected, in the second view (let's call it User View) everything works as expected but now on the third view (Team View) the ObservedObject for this view only is not working.

The strangest part is that if the user navigates directly to the Team View, again every thing works as expected.

Each view has it's own ObservedObject has the data being loaded belongs only to that view

The navigation between each view is made by the NavigationLink

Heres an exemple of how I'm doing the loading and navigation.

struct HomeView: View {
   @ObservedObject var viewModel = HomeViewModel()
   var body: some View {
       VStack {
           NavigationLink(destination: UserView(userId: viewModel.userId))
           NavigationLink(destination: TeamView(teamId: viewModel.teamId))
       }
   }
}

struct TeamView: View {
    @ObservedObject var viewModel = TeamViewModel()
    @State var teamId: String = ""
    var body: some View {
        Text(viewModel.name)
            .onAppear() { viewModel.loadData(id: teamId) }
    }
}

struct UserView: View {
    @ObservedObject var viewModel = UserViewModel()
    @State var userId: String = ""
    var body: some View {
        VStack {
            Text(viewModel.name)
            NavigationLink(destination: TeamView(teamId: viewModel.teamId))
        }
            .onAppear() { viewModel.loadData(id: userId) }
    }
}

From the example you can see that the function to load the data is in the view model and is loaded when the view appears

Everything works just fine but when I reach the 3rd level in the stack the data does not get updated in the view. I thought It might be the thread but I'm using DispatchQueue.main.async when the fetch is complete.

All necessary variables on the Model are marked as @Published

In sum the following flows work

HomeView -> TeamView
HomeView -> UserView

But this one on the last view it does load the data but it does not update the view

HomeView -> UserView -> TeamView
Pedro Cavaleiro
  • 835
  • 7
  • 21

1 Answers1

1

I replicated your code behaviour and the issue is due to fast navigation. Here is what's going on

if you would do

HomeView [tap] -> UserView -> [wait for user loaded] -> TeamView // no issue

but you do

HomeView [tap] -> UserView [tap] -> TeamView // got defect

The defect is because UserView is updated in background when the data got loaded, so body rebuilt, so link is recreated, so TeamView is reconstructed, but .onAppear is not called, because such kind of view is already on screen.

(I'm not sure if this is SwiftUI bug, because there is logic in such behaviour).

So here is a solution for this case. Tested with Xcode 11.5b.

struct TeamView: View {
    @ObservedObject var viewModel = TeamViewModel()
    var teamId: String  // << state is not needed

    init(teamId: String) {
        self.teamId = teamId
        viewModel.loadData(id: teamId)    // initiate load here !!
    }

    var body: some View {
        Text(viewModel.name)
    }
}

struct UserView: View {
    @ObservedObject var viewModel = UserViewModel()
    @State var userId: String = ""
    var body: some View {
        VStack {
            Text(viewModel.name)

            // used DeferView to avoid in-advance constructions
            NavigationLink(destination: DeferView { TeamView(teamId: viewModel.teamId) })
        }
            .onAppear() { viewModel.loadData(id: userId) }
    }
}

DeferView is taken from this my post.

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks a lot, it works perfectly. But just a note (in my code) you could only navigate when the UserView completed is loading, or else it would know the teamId. Anyway your solution works perfectly. Thx a lot I was already ripping my hair off – Pedro Cavaleiro May 11 '20 at 11:16