View.onReceive doesn't cancel subscription when the view removed from hierarchy

Viewed 286

It seems View.onReceive doesn't cancel subscription after that view disappeared. Action keeps getting called when no Child view on screen. How can I keep that subscription alive while the view is visible only?

struct BugDemo: View {
    var body: some View {
        NavigationView {
            NavigationLink("Go to child", destination: Child())
        }
    }
}

struct Child: View {
    @State private var date: Date = Date()

    var body: some View {
        Text(DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium))
            .navigationBarTitle("Child")
            .onReceive(PublisherHolder.shared.publisher) {
                print("Child.onReceive")
                self.date = $0
            }
            .onDisappear {
                print("Child.onDisappear")
            }
    }
}

private class PublisherHolder {
    static let shared = PublisherHolder()
    lazy var publisher: AnyPublisher<Date, Never> = {
        Timer.publish(every: 1, on: .main, in: .default)
            .autoconnect()
            .print()
            .eraseToAnyPublisher()
    }()
}
1 Answers

This is not .onReceive. This is NavigationView behaviour. Fortunately or not, it caches navigation stack for possible reuse.

If you instead use the following show/hide for Child you'll see that everything cancelled correctly.

Tested with Xcode 11.4

    @State private var showChild = false
    var body: some View {

        VStack {
            Button("Toggle View") { self.showChild.toggle() }
            if showChild {
                Child()
            }
        }
//            NavigationView {
//                NavigationLink("Go to child", destination: Child())
//            }
    }
Related