Popped navigation view is still being updated

I'm having a problem with old views that have been popped from the navigation stack still seems to be "updated" in the background even though they are not visible or on the navigation stack. I do the following:
  1. The main view is pushing a sub view on the navigation stack. The sub view becomes visible.

  2. The user uses the back arrow to go back, popping the sub view from the navigation stack. The main view is now visible.

  3. The main view updates and this seems to cause the sub view to also update, even though it shouldn't be active.

The problem I have is that when the data model is being updated the sub view crashes since it's trying to access data that doesn't exist anymore.

Sample code:

Code Block language
import SwiftUI
struct ContentView: View {
    var body: some View {
        TestView(items: [1, 2, 3, 4, 5])
    }
}
struct TestView: View {
    @State var items: [Int]
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello")
                List {
                    ForEach(items, id: \.self) { item in
                        NavigationLink(destination: SubView(items: self.$items, number: item)) {
                            Text("\(item)")
                        }
                    }
                }
                Button(action: {
                    self.items.append(self.items.count + 1)
                }) {
                    Text("Add")
                }
            }
        }
    }
}
struct SubView: View {
    @Binding var items: [Int]
    var number: Int
    var body: some View {
        Group {
            Print("\(number)")
            Text("\(number)")
        }
    }
}
extension View {
    func Print(_ items: Any..., separator: String = " ", terminator: String = "\n") -> some View {
        let output = items.map { "\($0)" }.joined(separator: separator)
        Swift.print("→ " + output, terminator: terminator)
        return EmptyView()
    }
}


In the sample, clicking a row in the list simply uses SubView to show the number and also log it to the console. If I return to the main view and click the Add button to force an update of the binding variable, the log will show that the SubView is being updated again.

It seems that I can fix it using:

Code Block language
struct SubView: View {
@Binding var items: [Int]
var number: Int
@State var active = true
var body: some View {
Group {
if active {
Print("\(number)")
Text("\(number)")
}
}
.onDisappear { self.active = false }
}
}


But that just seems silly and I'm not sure it will work in all situations.

Am I doing something wrong? Is this expected behaviour? Is there any way I can stop the SubView from updating after it has been dismissed?

Not sure what the purpose of passing in all the items into each sub view. I’m not able to reproduce your crash in a playground, but i was getting a lot of calls to sub views when adding an item because you’re passing in the items as a binding. I removed the binding from the SubView and only passed in the number for each SubView. That stopped the Subviews from updating when adding items:

Code Block
import SwiftUI
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
    var body: some View {
        TestView(items: [1, 2, 3, 4, 5])
    }
}
struct TestView: View {
    @State var items: [Int]
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello")
                List {
                    ForEach(items, id: \.self) { item in
                        NavigationLink(destination: SubView(number: item)) {
                            Text("\(item)")
                        }
                    }
                }
                Button(action: {
                    self.items.append(self.items.count + 1)
                }) {
                    Text("Add")
                }
            }
        }
    }
}
struct SubView: View {
    var number: Int
    var body: some View {
        Group {
            Print("\(number)")
            Text("\(number)")
        }
    }
}
extension View {
    func Print(_ items: Any..., separator: String = " ", terminator: String = "\n") -> some View {
        let output = items.map { "\($0)" }.joined(separator: separator)
        Swift.print("→ " + output, terminator: terminator)
        return EmptyView()
    }
}
PlaygroundPage.current.setLiveView(ContentView())


I think it might be that you’re doing something that holds on to the reference of the items longer than you should or. You might try two things.
  1. If possible, don’t pass in those items and remove the binding in the Subview

  2. If that is needed for some reason further down look at any blocks you have where you might be creating a retain cycle that would keep those subviews alive longer than they should. Look up retain cycles in blocks for help, also look in to weak vs strong references. You might have some objects that point to each and never get released.

good luck
I'm sorry, I should have clarified that the code is just an example showing the problem. The code I have in my app is a lot more complex so I reduced it into a much more simple example. The "Add" button in the sample is just a simple way to force an updated on the main view. I understand that removing the binding into the SubView stops the problem but in the real app I need that binding.

I have now fixed the problem by implementing the workaround using a boolean to keep track of when the view receives an onDisappear().

But the question remains: why does SwiftUI keep updating a view that is no longer showing on screen? There's something fundamental about the lifecycle of a SwiftUI view that I don't understand.

There are good chances that something is retaining your View, so when it gets popped out, it's still there and updating. You should make sure that you do not keep any strong references to self, to start with.
Popped navigation view is still being updated
 
 
Q