2

I think is very clear from this dummy example: if you remove the ForEach code row, magically, the propagation will flow and clock will tick, otherwise it will freeze once the detail view is presented.

class ModelView: ObservableObject {
    @Published var clock = Date()
    init() {
        Timer.publish(every: 1, on: .main, in: .default)
            .autoconnect()
            .print()
            .assign(to: &$clock)
    }
}

struct ContentView: View {

    @StateObject var viewModel = ModelView()
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Clock", destination: Text(viewModel.clock, formatter: formatter))
                ForEach(0..<1) { _ in } // <- Remove this row and the clock will work
            }
        }
    }
    
    var formatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.timeStyle = .medium
        return formatter
    }
}
Lorenzo Fiamingo
  • 3,251
  • 2
  • 17
  • 35

1 Answers1

1

I'd say it is rather some kind of coincidence that it works in first case, because destination is (or can be) copied on navigation (that's probably happens in second case).

The correct approach would be to have separated standalone view for details with own observer. Parent view should not update child view property if there is no binding.

Tested and worked with Xcode 12.4 / iOS 14.4

struct ContentView: View {

    @StateObject var viewModel = ModelView()
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Clock", destination: DetailsView(viewModel: viewModel))
                ForEach(0..<1) { _ in } // <- Remove this row and the clock will work
            }
        }
    }
    
}

struct DetailsView: View {
    @ObservedObject var viewModel: ModelView
    
    var body: some View {
        Text(viewModel.clock, formatter: formatter)
    }

    var formatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.timeStyle = .medium
        return formatter
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I think ideally the DetailsView should be redrawn when an update is triggered since it is situated inside the body of the function of state. Very strange because this happens only with the List(or Form)/ForEach combo. Any other pure-SwiftUI combination will work. I think the update is blocked somewhere by the inner UITableView implementation. – Lorenzo Fiamingo Feb 04 '21 at 09:24