Check when a system alert is shown in iOS - ios

I have the following usecase.
I want to show an overlay over the current window when the application moves to the task manager.
Currently I am doing the following:
func applicationWillResignActive(_ application: UIApplication) {
guard let window = application.keyWindow else { return }
ApplicationPrivacyProtector.showOverlay(inKeyWindow: window)
}
func applicationDidBecomeActive(_ application: UIApplication) {
guard let window = application.keyWindow else { return }
ApplicationPrivacyProtector.hideOverlay(inKeyWindow: window)
}
This works fine but
applicationWillResignActive is also called when a system alert (eg. faceID, touchID, etc.) is shown which triggers this overlay view as well.
Is there a way to distinguish between system alert and application moves to taskmanager?

Related

iOS app black screen when app coming from background

I am trying to find out what is the cause of a black screen that a comparatively small set of users are facing. Most of them are coming from the background. The app uses ReSwift for data refreshing, although that does not trigger the re run of the didFinishLaunchingWithOptions. I also thought might be OOM (Out Of Memory), but if I provoque it and test it, the app just crashes. For what is worth, I have also observed that is happening to 6s and SE (2nd edition) users. What can I try next?
This is the code that runs on changing its status:
func applicationWillEnterForeground(_ application: UIApplication) {
let internetConnectionStatus = InternetConnectionStatus()
_ = internetConnectionStatus.isOk().subscribe(onNext: { connected in
if !connected {
self.showFloatr(withBody: "ctkit.error.no-internet")
}
})
}
func applicationDidBecomeActive(_ application: UIApplication) {
if let keyWindow = UIApplication.shared.keyWindow,
keyWindow.rootViewController is AuthPasswordRecoveryViewController ||
keyWindow.rootViewController is AuthNavigationViewController {
return
}
UIApplication.shared.applicationIconBadgeNumber = 0
UIApplication.shared.registerForRemoteNotifications()
}
The affected users are running iOS 15.

HomeKit - How to update accessory isReachable value, when i come back from background to foreground?

Situation: i am developing an app, which manages HomeKit accessories. For example i do that:
Accessory is power on, and i see it via app. Also in foreground mode HMAccessoryDelegate method:
func accessoryDidUpdateReachability(HMAccessory)
works fine and i can handle status of my accessory.
I switch app to background mode.
I turn off accessory (i mean completely power off) so it must be unreachable.
I switch app to foreground mode, but accessory is still reachable.
method func accessoryDidUpdateReachability(HMAccessory) — not called.
value accessory.isReachable not updated.
Example of code when i go to foreground:
func applicationDidBecomeActive(_ application: UIApplication) {
if let home = HomeStore.sharedStore.home {
for accessory in home.accessories {
print(accessory.isReachable) //not updated
for service in accessory.services {
for characteristic in service.characteristics {
characteristic.readValue { (error) in //updated
if error == nil {
let notification = Notification(name: Notification.Name(rawValue: "UpdatedCharacteristic"))
NotificationCenter.default.post(notification)
}
}
}
}
}
}
}
Question: how to update isReachable values of accessories, when i come back from background mode to foreground?
You can create a function in the ViewController that implements HMHomeManagerDelegate:
func startHomeManager() {
manager = HMHomeManager()
manager.delegate = self
// do something here
}
and add a call to startHomeManager() to your viewDidLoad(). That will refresh your HMHome object. Then call this func in your AppDelegate:
func applicationWillEnterForeground(_ application: UIApplication) {
viewController.startHomeManager()
viewController.didRestartHomeManager()
}
A bonus is that you can call startHomeManager() for pull to refresh, etc.

How to fix IUnityGraphicsMetal error when I run Xcode project

I work to integrate an amazing AR model from Unity into Swift 4.
I finally did that using Unity 2018 (version 2018.2.4) and Swift 4.1 (Xcode 9.4.1) but when I run the project on my device (iPhone 7 Plus) is crushing and the error is coming from the path MyProjectName/Unity/Classes/Unity/IUnityGraphicsMetal.h
The error is: Thread 1: EXC_BAD_ACCESS (code=1, address=0x8).
I exported the project from Unity selecting Graphics API as Metal and also as OpenGLES3, none of this helped me.
Also in XCode -> Edit Scheme -> Run -> Options Tab -> Metal API Validation -> I set this one to Disabled, and I still get the same error.
I also update ARKit Plugin (in Unity) to the latest version (1.5) hoping that will fix the problem with UnityGraphicsMetal but apparently not.
Can anyone help me please to fix this error or to guide me to the wright way ?
Here I have 3 screenshots with the errors which maybe can help you more.
Thank you if you are reading this !
EDIT:
Here is the source code for my ViewController which is managing the bridge between Swift and Unity:
import UIKit
class ViewController: UIViewController {
#IBOutlet var rotateSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.startUnity()
NotificationCenter.default.addObserver(self, selector: #selector(handleUnityReady), name: NSNotification.Name("UnityReady"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleUnityToggleRotation(_:)), name: NSNotification.Name("UnityToggleRotation"), object: nil)
}
}
#objc func handleUnityReady() {
showUnitySubView()
}
#objc func handleUnityToggleRotation(_ n: NSNotification) {
if let isOn = n.userInfo?["isOn"] as? NSNumber {
rotateSwitch.isOn = isOn.boolValue
}
}
#IBAction func handleSwitchValueChanged(sender: UISwitch) {
UnityPostMessage("NATIVE_BRIDGE", "PlayHologram", sender.isOn ? "start" : "stop")
}
func showUnitySubView() {
if let unityView = UnityGetGLView() {
// insert subview at index 0 ensures unity view is behind current UI view
view?.insertSubview(unityView, at: 0)
unityView.translatesAutoresizingMaskIntoConstraints = false
let views = ["view": unityView]
let w = NSLayoutConstraint.constraints(withVisualFormat: "|-0-[view]-0-|", options: [], metrics: nil, views: views)
let h = NSLayoutConstraint.constraints(withVisualFormat: "V:|-75-[view]-0-|", options: [], metrics: nil, views: views)
view.addConstraints(w + h)
}
}
}
Here is the AppDelegate file:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var application: UIApplication?
#objc var currentUnityController: UnityAppController!
var isUnityRunning = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.application = application
unity_init(CommandLine.argc, CommandLine.unsafeArgv)
currentUnityController = UnityAppController()
currentUnityController.application(application, didFinishLaunchingWithOptions: launchOptions)
// first call to startUnity will do some init stuff, so just call it here and directly stop it again
startUnity()
stopUnity()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
if isUnityRunning {
currentUnityController.applicationWillResignActive(application)
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if isUnityRunning {
currentUnityController.applicationDidEnterBackground(application)
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
if isUnityRunning {
currentUnityController.applicationWillEnterForeground(application)
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
if isUnityRunning {
currentUnityController.applicationDidBecomeActive(application)
}
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
if isUnityRunning {
currentUnityController.applicationWillTerminate(application)
}
}
func startUnity() {
if !isUnityRunning {
isUnityRunning = true
currentUnityController.applicationDidBecomeActive(application!)
}
}
func stopUnity() {
if isUnityRunning {
currentUnityController.applicationWillResignActive(application!)
isUnityRunning = false
}
}
}
This seems to be a tricky problem, here's a few things which may help, please try them:
In the Unity Editor -> Project Settings -> Player, there is also a checkbox option called "Metal API Validation", you said that you only set the Xcode option to disabled, perhaps this one needs to be disabled as well. Or a combination of on/off for the two.
Instead of selecting Metal and OpenGLES3, try ticking the checkbox "Auto Graphics API". In one of my projects, I left it on manual with Metal selected, but it wouldn't render at all on iOS, fixed it by turning on automatic.
In Color Gamut, have both sRGB and P3 Wide-colour selected. The iPhone 7 and newer all have displays rated for P3 wide-colour.
Hope this helps!

set static image to snapshotView(afterScreenUpdates:)

When apps goes background mode, I want to show a default image in capture screen. If user may try to kill the application with double press the home button which is show the list of application, there also need to show my static image.
I tried with this process, If any thing wrong please correct me.
func applicationDidEnterBackground(_ application: UIApplication) {
if((self.imgViewStaticImage) != nil)
{
self.imgViewStaticImage?.removeFromSuperview()
}
self.imgViewStaticImage = UIImageView(frame: (self.window?.bounds)!)
self.imgViewStaticImage?.image = UIImage(named: "banner")
self.window?.addSubview(self.imgViewStaticImage!)
self.window?.bringSubview(toFront: self.imgViewStaticImage!)
}
func applicationWillEnterForeground(_ application: UIApplication) {
if((self.imgViewStaticImage) != nil)
{
self.imgViewStaticImage?.removeFromSuperview()
}
}

Detect Apple system prompt is visible or not on screen [duplicate]

My problem is I want to show a loading screen for the initial Push Notification Prompt "The app wants to send you push notifications."
So if the user hits yes I can proceed and start the app in the then invoked delegate methods:
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
[self hideLoadingScreen];
}
However if the user hits no, none of these methods get called, which makes sense. My question is, is there a different delegate method that gets fired if he declines?
My problem is if no is selected, the loading screens never disappear. So I somehow need to know when the user is done with the selection.
In iOS 7, when the system's push notification prompt appears, the app becomes inactive and UIApplicationWillResignActiveNotification fires. Similarly when the user responds to the prompt (pressing either Yes or No), the app becomes active again and UIApplicationDidBecomeActiveNotification fires.
So you can listen for this notification, and then hide your loading screen.
Note: While the prompt is displayed, the Home button, Notification Center, and Control Center are disabled so they cannot trigger a false-positive UIApplicationDidBecomeActiveNotification. However if the user presses Lock button it will trigger UIApplicationDidBecomeActiveNotification.
You can always get current allowed notification types from:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
Keep in mind user can also disable notification in phone settings.
If you check that on didRegisterForRemoteNotificationsWithDeviceToken you should see if types you asked for are enabled.
Some of the answers here are not relevant anymore, or are more complicated than it should be, since UserNotifications framework and iOS 10 you can easily get this data like so:
let center = UNUserNotificationCenter.current()
// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound])
{ (granted, error) in
// Enable or disable features based on authorization.
}
Couldn't you just do the following:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
BOOL pushEnabled = notificationSettings.types & UIUserNotificationTypeAlert;
}
This method should be the callback to that push notifications prompt, and from there, you can check the bitmask to see if push notifications were enabled or not.
Here's how I did it in Swift 3. They key here is to keep track of the application's lifecycle state internally. When the push prompt is presented, the application resigns active, but does not enter the background. This is all in my AppDelegate.swift.
This is a really big hack and is not recommended in production. Apple could change the way these alerts are presented and this could break at any time. This was tested using various iPhones and iPads running iOS 9 and 10.
/// An internal value used to track application lifecycle state
enum ApplicationLifecycleState {
case willResignActive
case didEnterBackground
case willEnterForeground
case didBecomeActive
case unknown
}
/// This is used purely for tracking the application lifecycle for handling the system push notification alert
var internalLifecycleState: ApplicationLifecycleState = .unknown {
didSet {
// If we're not in the middle of asking for push permissions, none of the below applies, just bail out here
if !isAskingForPushPermissions { return }
// WARNING: Application lifecycle trickery ahead
// The normal application lifecycle calls for backgrounding are as follows:
// applicationWillResignActive -> applicationDidEnterBackground -> applicationWillEnterForeground -> applicationDidBecomeActive
// However, when the system push notification alert is presented, the application resigns active, but does not enter the background:
// applicationWillResignActive -> [user taps on alert] -> applicationDidBecomeActive
// We can use this discrepancy to our advantage to detect if the user did not allow push permissions
// If applicationDidBecomeActive
// AND the previous state was applicationWillResignActive
// AND the notification types bitmask is 0, we know that the user did not allow push permissions
// User denied permissions
if internalLifecycleState == .didBecomeActive
&& oldValue == .willResignActive
&& UIApplication.shared.currentUserNotificationSettings?.types.rawValue == 0 {
// We're done
firePushCompletionBlockAndCleanup(registered: false)
} else {
// The state below can only be entered on iOS 10 devices.
// If the user backgrounds the app while the system alert is being shown,
// when the app is foregrounded the alert will dismiss itself without user interaction.
// This is the equivalent of the user denying push permissions.
// On iOS versions below 10, the user cannot background the app while a system alert is being shown.
if #available(iOS 10, *), internalLifecycleState == .didBecomeActive {
firePushCompletionBlockAndCleanup(registered: false)
}
}
}
}
/// Used internally to track if the system push notification alert is currently being presented
var isAskingForPushPermissions = false
typealias PushNotificationRegistrationCompletionBlock = ((_ registered: Bool) -> Void)
// ...
func applicationWillResignActive(_ application: UIApplication) {
internalLifecycleState = .willResignActive
}
func applicationDidEnterBackground(_ application: UIApplication) {
internalLifecycleState = .didEnterBackground
}
func applicationWillEnterForeground(_ application: UIApplication) {
internalLifecycleState = .willEnterForeground
}
func applicationDidBecomeActive(_ application: UIApplication) {
internalLifecycleState = .didBecomeActive
}
// ...
func setupPushNotifications(_ application: UIApplication = UIApplication.shared, completion: #escaping PushNotificationRegistrationCompletionBlock) {
isAskingForPushPermissions = true
pushCompletionBlock = completion
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
fileprivate func firePushCompletionBlockAndCleanup(registered: Bool) {
pushCompletionBlock?(registered)
pushCompletionBlock = nil
isAskingForPushPermissions = false
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// application:didRegisterForRemoteNotificationsWithDeviceToken may be called more than once (once for each notification type)
// By checking that the notification types bitmask is greater than 0, we can find the final time this is called (after the user actually tapped "allow")
// If the user denied push permissions, this function is never called with a positive notification type bitmask value
if UIApplication.shared.currentUserNotificationSettings?.types.rawValue ?? 0 > 0 {
firePushCompletionBlockAndCleanup(registered: true)
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for notifications with error: " + error.localizedDescription)
firePushCompletionBlockAndCleanup(registered: false)
}
Usage:
appDelegate.setupPushNotifications(completion: { [weak self] (registered) in
// If registered is false, the user denied permissions
})
For Swift 3 and Swift 4.0
Using NotificationCenter and the AppDelegate method didRegister notificationSettings. NotificationSettings show whether the users opted for badges, sounds, etc. and will be an empty array if they declined push notifications. It is fired specifically when users respond to the push notifications prompt and seems to be what most devs use, since it's more specific than checking didBecomeActive. But Apple might change this. Who knows?
Unfortunately, NotificationCenter does not have a preset notification name so you either have to setup and extension (see end) or use the raw value in (SO has more on this).
In AppDelegate:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
// if not registered users will have an empty set of settings
let accepted: Bool = !notificationSettings.types.isEmpty
NotificationCenter.default.post(name: Notification.Name(rawValue: "didRespondToPrompt"), object: self, userInfo: ["didAccept" : accepted])
}
Then observe wherever you need to, for example in a view controller:
class MyViewController: UIViewController {
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(MyViewController.didRespondToPushPrompt(_:)), name: NSNotification.Name(rawValue: "didRespondToPrompt"), object: nil)
}
#objc func didRespondToPushPrompt(_ notification: Notification) {
if let userInfo: [AnyHashable : Any] = notification.userInfo, let didAccept: Bool = userInfo[NSNotificationKeyNames.didAccept] as? Bool, !didAccept {
//if user doesn't accept, do this...
} else {
//all other situations code goes here
}
}
}
Couple of things: First, for Swift 4.0, I'm using "#objc" in front of one method, but it's not necessary for Swift 3.
Also, for using NotificationCenter, in practice I did not use "rawValue". Instead I made an extension like so:
import Foundation
extension NSNotification.Name {
static let DidRegisterForPushNotifications = NSNotification.Name("DidRegisterForPushNotifications")
}
Which I could then use like so:
NotificationCenter.default.post(name: Notification.Name.DidRegisterForPushNotifications, object: self, userInfo: ["didAccept" : myBool])
etc., etc.
2nd May 2019
This is the implementation to check if notifications are authorized any time in your app, Simple call this function.
private func checkNotificationsAuthorizationStatus() {
let userNotificationCenter = UNUserNotificationCenter.current()
userNotificationCenter.getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .authorized:
print("The app is authorized to schedule or receive notifications.")
case .denied:
print("The app isn't authorized to schedule or receive notifications.")
case .notDetermined:
print("The user hasn't yet made a choice about whether the app is allowed to schedule notifications.")
case .provisional:
print("The application is provisionally authorized to post noninterruptive user notifications.")
}
}
}
I guess you can have a BOOL variable to check it in your AppDelegate because there seems to be no way other than using external APIs. See this.
AppDelegate.m
// declare a BOOL
BOOL allow = NO;
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
allow = YES;
[self hideLoadingScreen];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
allow = YES;
[self hiedLoadingScreen];
}
Now I guess you can access this BOOL variable to differentiate when Don't allow is pressed or not.
Here is a SWIFT 2 code example for you guys ... It's complicated little bit ,but I hope my comments will help you understand it.
Define variables
var appDidBecomeActiveCount = 0
var userDefaults:NSUserDefaults!
AppDelegate - didFinishLaunchingWithOptions
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("FirstLaunche") == nil {
userDefaults.setBool(true, forKey: "FirstLaunche")
userDefaults.synchronize()
}
// Register for notification
//iOS 8+
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [UIUserNotificationType.Alert , UIUserNotificationType.Badge ,UIUserNotificationType.Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
}
AppDelegate - applicationDidBecomeActive
func applicationDidBecomeActive(application: UIApplication) {
//Delay until alert get dismissed and notification type setted in app
delay(0.5, closure: { () -> () in
self.checkTheDilemma()
})
}
//I love this short method <3_<3
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Check action
func checkTheDilemma (){
//Checking if this user turned off push notifications or didn't allow it at all
let notificationType = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
if userDefaults.valueForKey("FirstLaunche") as! Bool == true {
//User now is asked for notification permission because it's app's first launche
// if appDidBecomeActiveCount == 0 --> Pop up message will appeare
// if appDidBecomeActiveCount == 1 --> Pop up message dismissed
// if notificationType?.rawValue == 0 --> Notifications off
// if notificationType?.rawValue > 0 --> Notifications on
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 1 { //If user disabled notifications from pop up alert
// ** User just tapped "Don't allow" btn :\
// Do what ever you are here for
//Now set FirstLaunche = false
userDefaults.setBool(false, forKey: "FirstLaunche")
userDefaults.synchronize()
}
} else {
if notificationType?.rawValue == 0
&& appDidBecomeActiveCount == 0 { // This guy is not registered for push notification
// ** User disabled notifications in past (because this is not his first launch)
}
}
appDidBecomeActiveCount++
}
You can detect if user has cancelled the notification prompt in didRegisterUserNotificationSettings method that fires after calling registerForRemoteNotificationTypes by checking the notificationSettings.types.
If you have requested a number of settings but notificationSettings.types == UIUserNotificationTypeNone means, that user has cancelled the prompt.
But don't forget that registerForRemoteNotificationTypes method is now deprecated!
A great way using C# Xamarin as of iOS 13
I put it in a timer on the page where I prompt and check the authorization status
It may be possible to get the actual callback but this way works for me
using System.Timers;
Timer notificationsPermissionTimer = new Timer();
public override void ViewDidLoad()
{
SetupNotificationsPermissionTimer();
base.ViewDidLoad();
}
public override void ViewWillDisappear(bool animated)
{
this.notificationsPermissionTimer.Elapsed -= NotificationsPermissionTimer_Elapsed;
base.ViewWillDisappear(animated);
}
private void SetUpNotificationsPermissionTimer()
{
this.notificationsPermissionTimer = new Timer();
this.notificationsPermissionTimer.Interval = 500;
this.notificationsPermissionTimer.Start();
this.notificationsPermissionTimer.Elapsed += NotificationsPermissionTimer_Elapsed;
}
private void NotificationsPermissionTimer_Elapsed(object sender, ElapsedEventArgs e)
{
Task.Run(CheckNotificationsAuthorizationStatus);
}
private async Task CheckNotificationsAuthorizationStatus()
{
var userNotificationCenter = await UNUserNotificationCenter.Current.GetNotificationSettingsAsync();
switch(userNotificationCenter.AuthorizationStatus)
{
case UNAuthorizationStatus.Authorized:
// Do Something
break;
case UNAuthorizationStatus.Denied:
// Do Something
break;
case UNAuthorizationStatus.NotDetermined:
// Do Nothing
break;
case UNAuthorizationStatus.Provisional:
break;
}
}

Resources