full-cover PIN screen in SwiftUI App Lifecycle?

I'm working on an app using the new SwiftUI App lifecycle for iOS and need to pop up a PIN entry privacy screen when the app is backgrounded.

I've hooked into the ScenePhase and can catch the right moment and raise the screen over the main UI. But if the user was in any of the half a dozen modals (my SettingsView is a fullScreenCover, several other data entry views are sheets), then the PIN screen pops up underneath these modals.

This was always a bit of a dodgy thing to accomplish in UIKIt (walking the UIViewController graph to find the "top-most" to present from), but is it even possible in SwiftUI?

I've tried everything I can think of, even swapping out the root contents of the WindowGroup, but it seems that modally presented views are in separate system-managed window groups?

Has anyone done this successfully? or am I tilting at windmills at this point?

Accepted Reply

Hi karim,

Here is a simple solution, just to give you a hint.

Code Block
struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    @State private var coverIsPresented = false
    @State private var password = ""
    
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onChange(of: scenePhase, perform: { value in
                coverIsPresented = true
            })
            .fullScreenCover(isPresented: $coverIsPresented) {
                TextField("Enter Password", text: $password)
                Button("OK") {
                    if password == "1234" {
                        coverIsPresented = false
                    }
                }
            }
    }
}

Replies

Hi karim,

Here is a simple solution, just to give you a hint.

Code Block
struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    @State private var coverIsPresented = false
    @State private var password = ""
    
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onChange(of: scenePhase, perform: { value in
                coverIsPresented = true
            })
            .fullScreenCover(isPresented: $coverIsPresented) {
                TextField("Enter Password", text: $password)
                Button("OK") {
                    if password == "1234" {
                        coverIsPresented = false
                    }
                }
            }
    }
}

That doesn't really answer my specific question, but I'm inclined to give you the checkmark because it eventually led me to catch the real problem, which was that I was attaching my fullScreenCover to the NavigationView, not the contents of the NavigationView.

However, for anyone else trying to do this, it's critical to note that simply putting a fullScreenCover over the app contents isn't enough. Because the fullScreenCover is animated. This means that all someone has to do to see the main screen of the app for a moment (long enough to read some content!) is kill the app and restart it.

Anyone know of a way to tell fullScreenCover to not animate?