Accept notifications later than the first launch - swift

In my application, user can use notifications sent from the app. At first launch, there is an alert with refuse or allow notifications made in the appdelegate.
If I accept notifications, all is OK.
I made a screen / viewcontroller in which user can accept or no notification with a switch. If user refused notifications at first launch, how can I access to the controls in appdelegate like if I was at first launch ?
Thanks

According to Apple's Developer Documentation and this answer (for clarification), push notifications permissions can only be requested once. iOS stores the user's decision after it has been made and there is no way to request again. Do prepare for the fact that some users might not want notifications, you can always check the status of the permission your app received:
let center = UNUserNotificationCenter.current()
center.getNotificationSettings { settings in
guard settings.authorizationStatus == .authorized else { return }
if settings.alertSetting == .enabled {
// Schedule an alert-only notification.
} else {
// Schedule a notification with a badge and sound.
}
}

Related

iOS. How to know if faceid prompt is presented?

Is there any way (in swift) to know if the system faceId prompt is being presented? I can't see any event, notification or delegate method. I should avoid the presentation of a view triggered by an asynchronous event in case the app is trying to authenticate the user.
There is no specific event, notification, or delegate method in Swift that allows you to determine if the system Face ID prompt is being presented. However, you can use the LAContext class to check the availability and state of Face ID on the device, and then use that information to determine if the prompt is likely to be displayed.
You can use the canEvaluatePolicy method of LAContext to check if the device supports Face ID and if the user has configured it.
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
//FaceID is available
}
Then you can use the evaluatePolicy method to check if the user already authenticated recently or not.
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Log in with Face ID") { success, error in
if success {
// Face ID authentication was successful
} else {
// Face ID authentication failed
}
}
It's important to notice that you should avoid presenting a view triggered by an asynchronous event in case the app is trying to authenticate the user.

KVO observe AVAudioSession's recordPermission doesn't work

My application uses the microphone's permission, which is requested in another framework, and in the main application, I wasn't able to observe when microphone permission changed. I tried using KVO's observer but the application doesn't receive any events when the microphone permission is changed.
private var permissionStatusObserver: NSKeyValueObservation?
private func observeRecordPermissionChange() {
    do {
      try audioSession.setActive(true)
      permissionStatusObserver = audioSession.observe(\.recordPermission) { [weak self] _, recordPermissions in
        print("recordPermission changed")
      }
    } catch {
      print("active audio failed \(error.localizedDescription)")
    }
  }
Not going to work.
There are 3 possibilities:
App never asked user for permissions yet. In this case you should present permissions to the user and wait for their response. In this case you need to define requestRecordPermission callback instead of listening to KVO.
App previously asked user for permissions, and user granted them. In this case you can proceed working with microphone.
App previously asked user for permissions, and user denied. Typically in this case apps show the message telling user to go to settings. And user needs to go to Settings -> Privacy -> Microphone and reenable the permissions, at which point the app will be restarted. So nothing to listen to as #cora mentioned.
Something like this:
switch AVAudioSession.sharedInstance().recordPermission {
case .granted:
// start recording
case .denied:
// Present message to user indicating that recording
// can't be performed until they change their preference
// under Settings -> Privacy -> Microphone
case . undetermined:
// Ask for permissions as explained below.
}
Or you can always ask for permissions like Apple tells us to:
// Request permission to record.
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
// The user granted access. Present recording interface.
} else {
// Present message to user indicating that recording
// can't be performed until they change their preference
// under Settings -> Privacy -> Microphone
}
}
This is safe to do even if the permission is already granted (the callback will be back very quickly).

How to control when to ask for notification permission with FCM plugin

In an Ionic 3 app, I am using the Cordova FCM plugin for notifications. I would like to ask for notification permission when my users click a button within the app.
But, now the app asks for notification on launch. I’d rather ask for notifications later. Not right away. I tested this on iOS devices only so far.
I am using the FCM plugin only in app.component.ts. FCM plugin code should run on the condition that a database variable exists and equals to a certain value. This value is changed in the app when the user clicks a button. I was hoping the the plugin would kick in when the button is clicked but not when the app starts. Here is my code. Please help!
app.component.ts
platform.ready().then(() => {
//START: notification operator
const authListener=afAuth.authState.subscribe(user => {
if (user) {
this.userId = user.uid;
//check if user answered yes to the question asked after first clicking a follow button
firebase.database().ref(`/members/${this.userId}/notStats/`).on("value", res => {
if (res.exists()) {
if (res.val().notifAllowed == true ) {
//START: get device token and save in user members
fcm.getToken().then(token => {
afDatabase.object(`/members/${this.userId}/notificationStatus/`).update({deviceToken: token});
});
//START: subscribe to notification listener
fcm.onNotification().subscribe(data => {
if (data.wasTapped) {//Notification tapped by the user
console.log(JSON.stringify(data));
} else { //App was in foreground when notification was sent
console.log(JSON.stringify(data));}
});
} //end: if notifsAllowed saved in database as true
} //end: if exists
});//end: firebase listener
authListener.unsubscribe();} else {authListener.unsubscribe();}});
//notification operator: END
});
How can I ask for notification permission when the button in the app is first clicked? I do not wish the app to ask for permission to send notifications on first launch. It makes for terrible user experience, asking for permission at the beginning before users even get to see the app.
Thanks!

Managing APN notification for multiple users on the same phone

I am wondering how to manage Apple Push Notifications when many users are on the same device for the same app, because the device token is unique per device.
If I have 2 registered users on my app on the same device, how to prevent the push notification to appear if the notification is for userA but userB is currently logged into the app?
If the app is in the foreground, that could be easily managed by an userid that prevents the code to be executed if the APN is not for this user.
But how to handle this if the app is in the background?
The device will received the APN, will sound an alert, and will display the notification whatever user is currently logged into the app...
Based on your comment about being able to handle the situation if the app is in the foreground, I assume the notification has some info identifying the user the notification is for. It seems like if you store some info identifying the current user logged in, in NSUserDefaults, you should be able to retrieve that info in didReceiveRemoteNotification and based on that decide if the notification should be handled or not.
Update adding more info:
You will also need to implement application:didReceiveRemoteNotification:fetchCompletionHandler
and turn your notification into a "silent" notification, then after you have filtered out the ones you want to display from the ones you want to suppress, you would create local notifications for those that you want to show. You also need to add a UIBackgroundModes in your info.plist and include a "content-available", key in your payload. See here for more details.
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
It's critical to implement the version with fetchCompletionHandler since the one without that is not called when the app is in background.
Here is an example from one of my apps, that receives "silent" notifications for location update requests, and also receives some "non-silent" notifications. If the app is active I show a "toast", if the app is in the background I create a local notification from the remote notification and then trigger it.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler handler: (UIBackgroundFetchResult) -> Void ) {
log?.info("******* got a remote message = \(userInfo) *******")
let message: String = (userInfo["message"] ?? "stop") as! String
log?.info("message = \(message)")
switch message {
case "update_locations":
log?.info("starting location update cycle")
locationUploader?.startUploading(handler)
case "micropost_created":
log?.info("got a new micropost notification")
let notification = UILocalNotification()
let userName = userInfo["user_name"] as? String ?? ""
let content = userInfo["content"] as? String ?? ""
notification.alertTitle = "Micropost from \(userName)"
let alertBody = "\(userName) said: \(content)"
notification.alertBody = alertBody
notification.soundName = UILocalNotificationDefaultSoundName
application.presentLocalNotificationNow(notification)
if let topMostView = application.keyWindow {
topMostView.makeToast(message: alertBody)
}
handler(.NewData)
default:
handler(.NoData)
}
}
For resolving the above issue i have two options
1. On logout remove the token from server DB for logged in user and every time when new user gets login into app request for new token and send onto server.
2. On logout app can unregister itself from notification center and on login app has to register it self for notification and update token on server.
Here is the code for register and unregister notification.
On logout: [[UIApplication sharedApplication] unregisterForRemoteNotifications];
On login:
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

APNS notification does not work when app is not running or background

I'm using pyapns to send notification to iPhone.
receiving notification when the app is running was success.
but, when app is not running or app is on background, it can't receive notification.
is it related with URL identifier or scheme? if not, what is the problem..?
Did you get called in
- (void)application:didReceiveRemoteNotification:
while you were in the app?
the problem from notification msg.
among the notify function args, there is notifications list arg.
note that, when you make this parameter, a dictionary of this list should contain below. so that the application can do alert reaction.
thePayLoad = {
'aps': {
'alert':'this option should be contained if you want to see the alert msg',
'sound':'k1DiveAlarm.caf',
'badge':42
},
'test_data': { 'foo': 'bar' }
}
In my case, I just sent msg not containing above. and in didReceiveRemoteNotification function, I made alert msg. but msg not containing alert information. so the device couldn't react.