SwiftUI View keeps updating even when no longer shown

Viewed 468

I have simple View that is observing an object through the @ObservedObject property. Something like this:

struct BrowserView: View {
    @ObservedObject var fs: FileStore

    var body: some View {
        List(fs.files) { file in
            NavigationLink(destination: BrowserView(fs: self.fs) {
                FileRow(item: file)
            }
        }
        .onAppear(perform: loadFiles)
    }

    private func loadFiles() {
        fs.loadFiles()
    }
}

So what happens is that when a row is tapped I navigate to the next View which is still the same BrowserView using the same dataset (in the real code it's also passing other values as it's like a file browser andc the current path gets passed as an argument, too, but for the sake of the example it's really not relevant).

The problem is that when the next BrowserView appears, the dataset changes and those changes trigger an update of the previous View which is no longer visible and trying to update its UITableView is actually crashing the app.

Is there any way to tell a View to disable its bindings in onDisappear or something like that? If not, what's the correct pattern when there's just a single model (storage) that is shared between different views?

Update

Here is the crashlog of what happens when the new view gets pushed onto the stack and the new data has been retrieved:

Thread 1 Queue : com.apple.main-thread (serial)
#0  0x00000001b9a4643c in AG::Graph::import_attribute(AG::Subgraph*, AG::Graph&, AG::attribute_t, AGTypeID, AG::ClosureFunctionVV<void>, AG::ClosureFunctionFV<void, AG::ClosureFunctionVV<void> >) ()
#1  0x00000001b9a4c76c in AGGraphImportAttribute ()
#2  0x00000001c52b8280 in ViewGraph.import<A>(_:as:from:) ()
#3  0x00000001c53a9a20 in static StyleType.makeViewList<A>(view:style:inputs:) ()
#4  0x00000001c53a9200 in static ResolvedList._makeViewList(view:inputs:) ()
#5  0x00000001c5203544 in closure #2 in static ModifiedContent<>._makeViewList(view:inputs:) ()
#6  0x00000001c51e723c in specialized static MultiViewModifier._makeViewList(modifier:inputs:body:) ()
#7  0x00000001c51e8274 in protocol witness for static ViewModifier._makeViewList(modifier:inputs:body:) in conformance ButtonActionModifier ()
#8  0x00000001c52020d8 in static ModifiedContent<>._makeViewList(view:inputs:) ()
#9  0x00000001c5203544 in closure #2 in static ModifiedContent<>._makeViewList(view:inputs:) ()
#10 0x00000001c51e81dc in static ViewModifier<>._makeViewList(modifier:inputs:body:) ()
#11 0x00000001c52020d8 in static ModifiedContent<>._makeViewList(view:inputs:) ()
#12 0x00000001c51795c0 in static View.makeViewList(view:inputs:) ()
#13 0x00000001c516b42c in static View._makeViewList(view:inputs:) ()
#14 0x00000001c542524c in AnyViewStorage.makeViewList(view:inputs:) ()
#15 0x00000001c54268a8 in closure #1 in AnyViewList.update(context:) ()
#16 0x00000001c5426648 in AnyViewList.update(context:) ()
#17 0x00000001c54289a0 in partial apply for protocol witness for static UntypedAttribute._update(_:graph:attribute:) in conformance AnyViewList ()
#18 0x00000001b9a3b88c in AG::Graph::UpdateStack::update() ()
#19 0x00000001b9a3bd48 in AG::Graph::update_attribute(unsigned int, bool) ()
#20 0x00000001b9a40cb0 in AG::Subgraph::update(unsigned int) ()
#21 0x00000001c52be0f4 in ViewGraph.runTransaction(in:) ()
#22 0x00000001c52bdea8 in closure #1 in ViewGraph.flushTransactions() ()
#23 0x00000001c52bdb20 in ViewGraph.flushTransactions() ()
#24 0x00000001c52bdc98 in closure #1 in closure #1 in ViewGraph.asyncTransaction<A>(_:mutation:style:) ()
#25 0x00000001c55759c0 in ViewRendererHost.updateViewGraph<A>(body:) ()
#26 0x00000001c52bdc60 in closure #1 in ViewGraph.asyncTransaction<A>(_:mutation:style:) ()
#27 0x00000001c52d9a30 in thunk for @escaping @callee_guaranteed () -> () ()
#28 0x00000001c514ff64 in static NSRunLoop.flushObservers() ()
#29 0x00000001c514fed4 in closure #1 in static NSRunLoop.addObserver(_:) ()
#30 0x00000001c5150004 in @objc closure #1 in static NSRunLoop.addObserver(_:) ()
#31 0x000000018d7acee8 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#32 0x000000018d7a7b78 in __CFRunLoopDoObservers ()
#33 0x000000018d7a8018 in __CFRunLoopRun ()
#34 0x000000018d7a78f4 in CFRunLoopRunSpecific ()
#35 0x0000000197bbe604 in GSEventRunModal ()
#36 0x000000019197b358 in UIApplicationMain ()
1 Answers

What you are describing shouldn't cause a crash... try this code:

class Model: ObservableObject {
    @Published var files: [String] = ["red", "black", "blue", "yellow", "orange"]
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            MainView(fs: Model())
        }
    }
}
struct MainView: View {
    @ObservedObject var fs: Model
    
    var body: some View {
        VStack {
            List(fs.files, id: \.self) {  file in
                NavigationLink(destination: MainView(fs: self.fs)) {
                    Text(file)
                }
            }.onAppear {
                self.fs.files.append("New Value")
            }
        }
    }
}

It's observing a model and updating in onAppear, and pushing the same view over and over just updates the list in the next screen as well as the first.

Related