Shadowman
Shadowman

Reputation: 12039

SwiftUI: Show "lock screen" on launch/enter foreground?

I am developing an iOS app in SwiftUI that will need to lock/unlock cryptographic keys from the keychain before any operations can be performed. I would like to have those keys in memory while the app is in use, and then removed from memory when the user switches to another app or it goes into the background. When the app is launched or brought back into the foreground, I would like to prompt them for a password to unlock the keys.

I've done something like this in the past when using UIKit, but this app is being written in SwiftUI. The root of my app is a TabView, with numerous other Views displayed in each of the tabs. I would like this "lock screen" to pop up regardless of which View happens to be active at the time. I know that there are numerous Notifications that I can subscribe to in a UIApplicationDelegate. That can handle the task of removing the keys out of memory. What I don't know is how to display the lock screen View over top of any other View that may be active at the time.

Is there a "standard" SwiftUI way of accomplishing this? I'd like to adhere to SwiftUI best practices as much as possible, but haven't seen any good examples of how to do something like this.

Upvotes: 1

Views: 1332

Answers (1)

jnpdx
jnpdx

Reputation: 52387

The simplest version would be to use an if/else block to conditionally display the locked screen based on a @State variable:

struct ContentView: View {
    @State var showLock = false
    
    var body: some View {
        Group {
            if showLock {
                Text("Locked")
            } else {
                Text("Tab view here")
            }
        }.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
            showLock = true
        }
    }
}

The disadvantage of the above approach would be that you might lose some state in your tab view when the locked screen is displayed instead.

To keep the tab view loaded, and keep your state, you could use a fullScreenCover that is displayed on top of your other content:

struct ContentView: View {
    @State var showLock = false
    
    var body: some View {
        Text("Tab view here")
        .fullScreenCover(isPresented: $showLock) {
            VStack {
                Text("Locked")
                Button("Hide") {
                    showLock = false
                }
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
            showLock = true
        }
    }
}

Obviously, where you store the state (locked/unlocked) is inconsequential -- you could store it in an ObservableObject, environment key, etc. -- anywhere that SwiftUI can see it updated. Same for the strategy of getting the background/foreground notifications -- any strategy that sets the state would be workable.

Upvotes: 5

Related Questions