I have code that puts a blurred UIVisualEffectView over the current window when the didEnterBackgroundNotification notification is fired. It then tries to reverse this when willEnterForegroundNotification is fired (simplified for easy reading):
class AutoBlur {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appDidEnterBackground() {
blur()
}
#objc func appWillEnterForeground() {
guard UserState.isScreenLockEnabled else {
unblur()
return
}
let authContext = LAContext()
authContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "test") { success, error in
DispatchQueue.main.async {
guard success else {
return
}
unblur()
}
}
}
//...
}
While this approach generally works, when testing the Face ID functionality of app I found the following anomaly:
Open the app
Turn the screen off on the device
Wake the device
Swipe up on the device's lock-screen
Expected results: The device should unlock, and then my app should run its Face ID evaluate code to un-blur the view.
Actual results: The swipe-up action is undercut by my app's Face ID evaluation code. It prevents the user from being able to swipe up on their lock-screen, effectively locking them out of their device.
Is there some state I should be monitoring or some other event that's safer to respond to for triggering my Face ID evaluation?
Related
I have Login screen and I would like to present it or just add it to navigation, it doesn't matter right now, still not sure which way I would like it to be, everytime my app comes back to foreground. Code below partially works, it does start face id, but actually whole login screen isn't shown, I can still see my last screen that was opened before entering background. I would like to hide everything with login screen. When I print childrens in appMovedToBackground function, correct view controller is printed, which means its added, but as I said, its not showing, only icon from face id can be seen. Code below is on one view controller only, currently, I would like this to be applied to all my view controllers, should I move functionality to appdelegate? Thanks in advance.
let notificationCenter = NotificationCenter.default
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToBackground() {
print("its in background")
let authVC = AuthViewController()
navigationController?.topViewController?.addChild(authVC)
}
#objc func appCameToForeground() {
print("app enters foreground")
if let authVC = navigationController?.topViewController?.children.first as? AuthViewController {
print("willEnterForegroundNotification")
authVC.checkBiometricsAndStart()
}
}
Here you need to correctly add the vc
#objc func appMovedToBackground() {
print("its in background")
guard let added = navigationController?.topViewController else { return }
let authVC = AuthViewController()
authVC.view.frame = UIScreen.main.bounds
added.view.addSubview(authVC.view)
added.addChild(authVC)
}
Regarding that you need it for all vcs , yes implementing it inside AppDelegate is the easiest way
My application has a menu with option "Contacts". When user selects Contacts for the first time, application asks user permission to use device Contacts. If user allows access, opens a view controller with contacts of his/her device, if he doesn't, nothing happens. After that every time they select "Contacts" option, it heads to Device Settings where they should allow access to contacts or dismiss it. When they allow access and go back to the app, I want it to open the view controller with contacts, but it doesn't.
I've tried to use NotificationCenter with didBecomeActiveNotification option, but it does not seem to help.
func checkPermissionToDeviceContacts() {
switch CNContactStore.authorizationStatus(for: .contacts) {
case .notDetermined:
self.requestContactsAccess()
case .authorized:
self.openContactsViewController()
case .denied:
self.headToSettingsOfPermissions()
default: return
}
}
func headToSettingsOfPermissions() {
if let bundleId = Bundle.main.bundleIdentifier,
let url = URL(string: "\(UIApplication.openSettingsURLString)&path=APPNAME/\(bundleId)")
{
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
self.setupNotifications()
}
func setupNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(self.applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
}
#objc func applicationDidBecomeActive() {
if CNContactStore.authorizationStatus(for: .contacts) == .authorized {
self.openContactsViewController()
}
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}
I also cannot debug it, because when Device Settings are opened and I change the access status of Contacts, my console prints a message "Message from debugger: Terminated due to signal 9".
The problem is that the selector function works when I do not change the authorization status, but when I do, it does not work anyway.
P.S. This is my first time to use NotificationCenter, maybe I do not use it properly.
NotificationCenter sends always the affected notification as parameter, you have to declare
#objc func applicationDidBecomeActive(_ notification : Notification) {
and remove self in the selector parameter
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
I'm working on SDK and try to catch app termination Notification. It's easy to get this as closure for (ex) NSNotification.Name.UIApplicationWillResignActive
self.resignActiveNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillResignActive,
object: nil,
queue: nil) { _ in
//something goes here and it works like a charm.
}
So I want to have similar behavior for termination:
self.terminateNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillTerminate,
object: nil,
queue: nil) { _ in
NSLog("%#",#function)
}
And this one never gets called!
Of course if I put in AppDelegate:
func applicationWillTerminate(_ application: UIApplication) {
//termination
}
It will work, but since I'm building an SDK I cannot have AppDelegate methods. There is a way to get termination closure call? Or any other way to know that application is about to terminate?
In Apple documentation you can find:
After calling this method, the app also posts a
UIApplicationWillTerminate notification to give interested objects a
chance to respond to the transition.
However this seems to be broken
This seems working for me:
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillTerminate(notification:)),
name: UIApplication.willTerminateNotification,
object: nil)
}
#objc func applicationWillTerminate(notification: Notification) {
// Notification received.
}
deinit {
NotificationCenter.default.removeObserver(self)
}
I need to check if the app is moved to the background. Why?
Well because my app works with bluetooth and only one person can be connected to this device at a time. Therefore if they are not using it and the app is in the background, disconnect them and send them to the connect main page.
Now I have accomplished this. I have a selector in the main first class and a function to disconnect and send to first page. But what I didn't realise is that if the control panel is dragged up, the app is in the 'background'.
From looking around there doesn't seem to be a way to detect if the control panel is brought up. So does anyone have any ideas on how I can do this differently?
Realistically I just want it so if the app is moved to the background for any other reason than the control panel being brought up, disconnect from the device.
Selector:
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
Function:
#objc func appMovedToBackground() {
if (ViewController.connectedPeripheral != nil) {
print("App moved to background!")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "connectView") as! ViewController
self.navigationController?.pushViewController(nextViewController, animated: true)
ViewController.centralManager.cancelPeripheralConnection(ViewController.connectedPeripheral!)
}
else {
print("App moved to background but no device is connected so no further action taken")
}
}
This is not a duplicate of other questions. I know how to check if app is in background state. I just don't want to disconnect when the control panel is brought up...
In Swift:
if UIApplication.shared.applicationState == .background {
// Add code here...
}
In Objective-C:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// Add code here...
}
Hope it works!
Have you tried with adding observer to willResignActive in your view controller?
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: .UIApplicationWillResignActive, object: nil)
func willResignActive(_ notification: Notification) {
// code to execute
}
Then you can use this also. After entering in background state , app will be moved to inactive state.
override func viewDidLoad() {
super.viewDidLoad()
let app = UIApplication.shared
//Register for the applicationWillResignActive anywhere in your app.
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.applicationWillResignActive(notification:)), name: NSNotification.Name.UIApplicationWillResignActive, object: app)
}
func applicationWillResignActive(notification: NSNotification) {
}
I am building a game with SpriteKit that requires use of the proximity sensor. I have a swipe gesture that turns proximity sensing on, when the proximity sensor senses something it turns off. My problem is that the first time I swipe and put my hand over the sensor, the functions runs and everything works fine. The second time I swipe and put my hand over the sensor the screen just turns black, when I move my hand away from the proximity sensor it then runs the function and everything works. I want it to activate and run the function when I put my hand over the sensor instead of when I remove my hand. Does anybody no how I could fix this?
Proximity sensor code:
func setProximitySensorEnabled(_ enabled: Bool) {
let device = UIDevice.current
device.isProximityMonitoringEnabled = enabled
if device.isProximityMonitoringEnabled == true {
NotificationCenter.default.addObserver(self, selector: #selector(proximityChanged), name: .UIDeviceProximityStateDidChange, object: device)
print("added observer!!!")
} else {
NotificationCenter.default.removeObserver(self, name: .UIDeviceProximityStateDidChange, object: nil)
print("removed observer!!!")
}
}
func proximityChanged(_ notification: Notification) {
if let device = notification.object as? UIDevice {
print("\(device) detected!")
print("An object is near!!")
vibrate()
setProximitySensorEnabled(false)
}
}
Swipe function:
func swipe() {
print("swipe detected")
setProximitySensorEnabled(true)
}
In my case, I found that somewhere in my project set UIDevice.current.isProximityMonitoringEnabled = false after I add the observer. The selector method would not get any notification then.