0

I am developing an app which uses UIKit. I have integrated a UIKit UIViewController inside SwiftUI and everything works as expected. I am still wondering if there is a way to 'know' when a SwiftUI View is completely gone.

My understanding is that a @StateObject knows this information. I now have some code in the deinit block of the corresponding class of the StateObject. There is some code running which unsubscribes the user of that screen.

The problem is that it is a fragile solution. In some scenario's the deinit block isn't called.

Is there any recommended way to know if the user pressed the back button in a SwiftUI View (or swiped the view away)? I don't want to get notified with the .onDisppear modifier because that is also called when the user taps somewhere on the screen which adds another view to the navigation stack. I want to run some code once when the screen is completely gone.

J. Doe
  • 12,159
  • 9
  • 60
  • 114
  • 1
    IMO there is no generic golden-rule for this. Even onDisappear not always can be reliable. In every specific scenario/flow it might be needed different solution. So needed MRE of your issue to find appropriate solution. – Asperi Jun 22 '22 at 11:47
  • Well I am showing a chat screen and when the user leaves (back button/swipe), only than should the user send an unsubscribe event to the server – J. Doe Jun 22 '22 at 11:52
  • This case probably can be handled with onDisappear in NavigationLink, like in https://stackoverflow.com/a/61971653/12299030. – Asperi Jun 22 '22 at 11:56
  • @Asperi this will also be called when a user taps on a user profile in the chat screen and a view will be added to the navigation stack, that is not what I want – J. Doe Jun 22 '22 at 12:00
  • KVO on the viewControllers property on the Navigation controller, if that part is UIKit? – Shadowrun Jun 22 '22 at 12:49
  • There is a kind of `NavigationLink` that takes a binding and you can monitor it with `onChanged` which will happen when the user taps the back button. But if you have UIViewController then you can also just use `viewWillDisappear` in that. – malhal Jun 22 '22 at 19:12

1 Answers1

1

Is there any recommended way to know if the user pressed the back button in a SwiftUI View (or swiped the view away)?

This implies you're using a NavigationView and presenting your view with a NavigationLink.

You can be notified when the user goes “back” from your view by using one of the NavigationLink initializers that takes a Binding. Create a custom binding and in its set function, check whether the old value is true (meaning the child view was presented) and the new value is false (meaning the child view is now being popped from the stack). Example:

struct ContentView: View {
    @State var childIsPresented = false
    @State var childPopCount = 0

    var body: some View {
        NavigationView {
            VStack {
                Text("Child has been popped \(childPopCount) times")
                NavigationLink(
                    "Push Child",
                    isActive: Binding(
                        get: { childIsPresented },
                        set: {
                            if childIsPresented && !$0 {
                                childPopCount += 1
                            }
                            childIsPresented = $0
                        }
                    )
                ) {
                    ChildView()
                }
            }
        }
    }
}

struct ChildView: View {
    var body: some View {
        VStack {
            Text("Sweet child o' mine")

            NavigationLink("Push Grandchild") {
                GrandchildView()
            }
        }
    }
}

struct GrandchildView: View {
    var body: some View {
        VStack {
            Text("")
                .font(.system(size: 100))
        }
    }
}

Note that these initializers, and NavigationView, are deprecated if your deployment target is iOS 16. In that case, you'll want to use a NavigationStack and give it a custom Binding that performs the pop-detection.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848