Reputation: 4330
I'm trying to understand how to load a SwiftUI view from Swift function code. In this case specifically, I want to load a view when returning from the background state to cover sensitive data. I have created a biometric login and that works fine - pure SwiftUI views for the app. When I put the app into the background and return, the FaceID works as expected, but the underlying screen is visible. This is a generalized question too - how can you load any SwiftUI view from any Swift function.
func sceneDidBecomeActive(_ scene: UIScene) {
if userDefaultsManager.wentToBackground {
if userDefaultsManager.enableBiometrics {
BiometricsLogin(userDefaultsManager: userDefaultsManager).authenticate()
//what I want is something like:
//BiometricsLogin(userDefaultsManager: userDefaultsManager)
//kinda like you would do in a TabView
//that would run the authentication just like starting the app
userDefaultsManager.wentToBackground = false
}
}
}
And the login code is pretty generic:
struct BiometricsLogin: View {
@ObservedObject var userDefaultsManager: UserDefaultsManager
var body: some View {
NavigationView {
VStack {
Image("CoifMeCrop180")
.onAppear {
self.authenticate()
}
}//vstack
}//nav
}
func authenticate() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "The app uses Biometrics to unlock you data"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { (success, authenticationError)
in
DispatchQueue.main.async {
if success {
self.userDefaultsManager.isAuthenticated = true
self.userDefaultsManager.selectedTab = 1
} else {
if self.userDefaultsManager.enableBiometrics {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 3
} else {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 1
}
}
}
}
} else {
//no biometrics - deal with this elsewhere
//consider an alert here
}
}//authenticate
}
I also tried using a hosting controller like this, but it did not work either. Same issue, the authentication worked but the data was visible.
//this does not work
let controller = UIHostingController(rootView: BiometricsLogin(userDefaultsManager: userDefaultsManager))
self.window!.addSubview(controller.view)
self.window?.makeKeyAndVisible()
Any guidance would be appreciated. Xcode 11.3.1 (11C504)
Upvotes: 3
Views: 1873
Reputation: 258365
Here is possible approach
In sceneDidBecomeActive
add presenting new controller with authentication in full screen to hide sensitive content
func sceneDidBecomeActive(_ scene: UIScene) {
let controller = UIHostingController(rootView: BiometricsLogin(userDefaultsManager: userDefaultsManager))
controller.modalPresentationStyle = .fullScreen
self.window?.rootViewController?.present(controller, animated: false)
}
now it needs to dismiss it when authentication will be done, so add notification for that purpose...
extension SceneDelegate {
static let didAuthenticate = Notification.Name(rawValue: "didAuthenticate")
}
... and subscriber for it in SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private var authenticateObserver: AnyCancellable?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
self.authenticateObserver = NotificationCenter.default.publisher(for: SceneDelegate.didAuthenticate)
.sink { _ in
self.window?.rootViewController?.dismiss(animated: true)
}
let window = UIWindow(windowScene: windowScene)
...
when authentication is done just post didAuthenticate
notification to dismiss top controller
DispatchQueue.main.async {
if success {
self.userDefaultsManager.isAuthenticated = true
self.userDefaultsManager.selectedTab = 1
} else {
if self.userDefaultsManager.enableBiometrics {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 3
} else {
self.userDefaultsManager.isAuthenticated = false
self.userDefaultsManager.selectedTab = 1
}
}
NotificationCenter.default.post(name: SceneDelegate.didAuthenticate, object: nil)
}
Upvotes: 1