Search code examples
iosswiftswiftuiswiftui-navigationlinkswiftui-navigationview

SwiftUI NavigationView pop itself when a datasource is finished loading


Let say you are loading some data from network when you are at screen A that it takes sometime. While you are waiting you can navigate to other screens by using NavigationLink. So, at the moment you are at the screen B then the data from network is finished loading and send value back to datasource in screen A. The NavigationView pop itself automatically so you back to screen A by unintentionally. Do you have any idea? Thanks.

Example

struct ContentView: View {
  @ObservedObject var viewModel = ViewModel()
  
  var body: some View {
    NavigationView {
      List(viewModel.dataSource, id: \.self) { item in
        NavigationLink(destination: Text("\(item)")) {
          Text("\(item)")
            .padding()
        }
      }
    }
  }
}

class ViewModel: ObservableObject {
  @Published private(set) var dataSource = [1, 2, 3, 4, 5]
  
  init() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) { // simulate calling webservice
      self.dataSource[0] = 99
    }
  }
}

Solution

  • This happens because you're specifying id as item itself, and when list updated there's no original item anymore, so it closes

    If you just wanna modify items without adding/removing/reordering, you can make index your item id:

    NavigationView {
        List(viewModel.dataSource.indices, id: \.self) { i in
            let item = viewModel.dataSource[i]
            NavigationLink(destination: Text("\(item)")) {
                Text("\(item)")
                    .padding()
            }
        }
    }
    

    But with more complex data you need to have your items Identifiable with unique ids, and you won't have such problem. Check out this example:

    struct ContentView: View {
        @ObservedObject var viewModel = ViewModel()
        
        var body: some View {
            NavigationView {
                List(viewModel.dataSource) { item in
                    NavigationLink(destination: Text("\(item.value)")) {
                        Text("\(item.value)")
                            .padding()
                    }
                }
            }
        }
    }
    
    class ViewModel: ObservableObject {
        @Published private(set) var dataSource: [Item] = [1, 2, 3, 4, 5]
        
        init() {
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [self] in // simulate calling webservice
                // you're modifying value but id stays the same
                self.dataSource[0].value = 99
            }
        }
    }
    
    struct Item: Identifiable, ExpressibleByIntegerLiteral {
        let id = UUID()
        var value: Int
        
        init(integerLiteral value: IntegerLiteralType) {
            self.value = value
        }
    }