Implementing Switch to toggle Notifications in App Settings - ios

I'm trying to implemented a Switch in my App's local SettingsVC to toggle Notification On/Off. I've decided for now to leave the user registered with the server (both APNS and AppBoy), and just toggle on/off the local presentation of Notifications.
(That's if the user has registered previously. If they haven't ever registered, this code should register the first time they flip to "on")
Here's my code when the user flips the switch:
func didFlipNotificationSwitch() {
let isEnabled = isEnabledRemoteNotificationTypes()
if (isEnabled) {
if #available(iOS 8.0, *) {
let settings = UIUserNotificationSettings(forTypes: [.None], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
} else {
UIApplication.sharedApplication().registerForRemoteNotificationTypes([.None])
}
} else {
if #available(iOS 8.0, *) {
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
} else {
UIApplication.sharedApplication().registerForRemoteNotificationTypes([.Alert, .Badge, .Sound])
}
}
print("isEnabled \(isEnabled)")
}
isEnabledRemoteNotificationTypes() is a convenience method that checks for registered based on OS - it's works fine. Here it is:
func isEnabledRemoteNotificationTypes() -> Bool {
if #available(iOS 8.0, *) {
let types = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
if (types == UIUserNotificationType.None) {
return false
} else {
return true
}
} else {
let types = UIApplication.sharedApplication().enabledRemoteNotificationTypes()
if (types == UIRemoteNotificationType.None) {
return false
} else {
return true
}
}
}
The problem is that once registered (isEnabled returns true) I'm trying to deregister locally by settings types to .None as you can see in the method above. This doesn't seem to be working. After the line setting them to .None is called, I print out notificationSettings in my appDel didRegisterUserNotificationSettings callback right after setting to .None:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
}
And get this:
Printing description of notificationSettings:
<UIUserNotificationSettings: 0x12684c840; types: (UIUserNotificationTypeAlert UIUserNotificationTypeBadge UIUserNotificationTypeSound);>
Why isn't it registering .None -OR- Is there a better way to turn off Notification presentation while still maintaining server registration?

Related

How can I check if a specific type of local notification is enabled?

If I request authorization for local notifications for alerts and sounds using the code below, the user could go into settings after granting authorization turn off banners and sounds. Authorization would still exist, but there would be no methods for sending notifications. How can I check if specific types of notifications (e.g. alert) is enabled?
#objc func registerLocal() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if granted {
self.notificationsAuth = true
} else {
self.notificationsAuth = false
}
self.notificationAuthUndertermined = false
}
}
For iOS 10.0 and later:
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
// also available for badgeSetting, soundSetting, authorization status and ...
switch settings.alertSetting {
case .notSupported:
// Do stuff
break
case .disabled:
// Do stuff
break
case .enabled:
// Do stuff
break
}
}
We can verify alertSetting, soundSetting, badgeSetting is enabled or not using below code,
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
let isAlertEnabled = settings.alertSetting
let isSoundEnable = settings.soundSetting
let isbadgeEnabled = settings.badgeSetting
if(isAlertEnabled == .enabled && isSoundEnable == .enabled && isbadgeEnabled == .enabled){
print("Alert, Sound and Badges are enabled.")
}else{
//Different action
}
}
}

Pushkit token not getting in some devices iOS

I have implemented PushKit. I've followed these steps:
1.) https://stackoverflow.com/a/38184769/4970453
2.) https://stackoverflow.com/a/28562124
I am able to get didUpdatePushCredentials device token.
working in --> iPhone 5s , iPhone6 Plus
didUpdatePushCredentials Not working in --> iPhone6 and iPhone7
I am using same cerificates and Build for all devices. Don't know the exact issue.
If anyone have faced this kind of problem, please share work-arounds.
My code and Certificate Link
code -> https://www.dropbox.com/sh/x2615t7xn8mavs3/AADbX5nBuF5_08YNPX8wI59ga?dl=0
cer -> https://www.dropbox.com/sh/70l4htj1c46emog/AABxBalaoN1JP22dQp8-mNXGa?dl=0
Solution -----> I have changed Bundle identifier And create New certificate with New BundleId.
Make sure that in the certificates page you have the push notifications enabled in the App ID.
Also try using a 3G/4G connection. Network configuration may be not letting the device to obtain the token.
It should work in all devices when integrated properly. there is nothing changed for any device specific.
You can cross verify your steps.
Swift
import UIKit
import PushKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,PKPushRegistryDelegate {
var window: UIWindow?
var isUserHasLoggedInWithApp: Bool = true
var checkForIncomingCall: Bool = true
var userIsHolding: Bool = true
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if #available(iOS 8.0, *){
let viewAccept = UIMutableUserNotificationAction()
viewAccept.identifier = "VIEW_ACCEPT"
viewAccept.title = "Accept"
viewAccept.activationMode = .Foreground
viewAccept.destructive = false
viewAccept.authenticationRequired = false
let viewDecline = UIMutableUserNotificationAction()
viewDecline.identifier = "VIEW_DECLINE"
viewDecline.title = "Decline"
viewDecline.activationMode = .Background
viewDecline.destructive = true
viewDecline.authenticationRequired = false
let INCOMINGCALL_CATEGORY = UIMutableUserNotificationCategory()
INCOMINGCALL_CATEGORY.identifier = "INCOMINGCALL_CATEGORY"
INCOMINGCALL_CATEGORY.setActions([viewAccept,viewDecline], forContext: .Default)
if application.respondsToSelector("isRegisteredForRemoteNotifications")
{
let categories = NSSet(array: [INCOMINGCALL_CATEGORY])
let types:UIUserNotificationType = ([.Alert, .Sound, .Badge])
let settings:UIUserNotificationSettings = UIUserNotificationSettings(forTypes: types, categories: categories as? Set<UIUserNotificationCategory>)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
}
else{
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
application.registerForRemoteNotificationTypes(types)
}
self.PushKitRegistration()
return true
}
//MARK: - PushKitRegistration
func PushKitRegistration()
{
let mainQueue = dispatch_get_main_queue()
// Create a push registry object
if #available(iOS 8.0, *) {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushTypeVoIP]
} else {
// Fallback on earlier versions
}
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {
// Register VoIP push token (a property of PKPushCredentials) with server
let hexString : String = UnsafeBufferPointer<UInt8>(start: UnsafePointer(credentials.token.bytes),
count: credentials.token.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
print(hexString)
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {
// Process the received push
// Below process is specific to schedule local notification once pushkit payload received
var arrTemp = [NSObject : AnyObject]()
arrTemp = payload.dictionaryPayload
let dict : Dictionary <String, AnyObject> = arrTemp["aps"] as! Dictionary<String, AnyObject>
if isUserHasLoggedInWithApp // Check this flag then only proceed
{
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background || UIApplication.sharedApplication().applicationState == UIApplicationState.Inactive
{
if checkForIncomingCall // Check this flag to know incoming call or something else
{
var strTitle : String = dict["alertTitle"] as? String ?? ""
let strBody : String = dict["alertBody"] as? String ?? ""
strTitle = strTitle + "\n" + strBody
let notificationIncomingCall = UILocalNotification()
notificationIncomingCall.fireDate = NSDate(timeIntervalSinceNow: 1)
notificationIncomingCall.alertBody = strTitle
notificationIncomingCall.alertAction = "Open"
notificationIncomingCall.soundName = "SoundFile.mp3"
notificationIncomingCall.category = dict["category"] as? String ?? ""
//"As per payload you receive"
notificationIncomingCall.userInfo = ["key1": "Value1" ,"key2": "Value2" ]
UIApplication.sharedApplication().scheduleLocalNotification(notificationIncomingCall)
}
else
{
// something else
}
}
}
}
//MARK: - Local Notification Methods
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification){
// Your interactive local notification events will be called at this place
}
}
Objective C
#import <UIKit/UIKit.h>
#import <PushKit/PushKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate,PKPushRegistryDelegate>
{
PKPushRegistry *pushRegistry;
}
#property (strong, nonatomic) UIWindow *window;
#end
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushRegistry.delegate = self;
pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
return YES;
}
#define PushKit Delegate Methods
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
if([credentials.token length] == 0) {
NSLog(#"voip token NULL");
return;
}
NSLog(#"PushCredentials: %#", credentials.token);
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
{
NSLog(#"didReceiveIncomingPushWithPayload");
}
https://github.com/hasyapanchasara/PushKit_SilentPushNotification
Updated answer
https://drive.google.com/file/d/0B7ooURy3zGWKYW5PZE1aN2pObW8/view?usp=sharing
Updated answer
Below are some trouble shooting option.
(1) Change com identifier and try again
(2) Keep puhkit code in app delegeate
I guess, you can change your bundle identifier and try again, it has to be work with all devices.
For PushKit to work properly, here are steps we need to follow:
Enable Push Notifications in project Capabilities
Enable Remote Notifications in Background Modes
Connect device to internet
Allow app to receive push notifications
Please refer to this step-by-step tutorial.
As it is happening for some devices only, may 3rd and 4th steps are responsible.
In my case I wasn't receiving the VoIP token in some of my devices. A HARD RESET of those devices fixed my issue.

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;
}
}

Push Notifications for Swift iOS 10

I'm struggling to get push notifications to work with Swift with iOS 10. Registering seems to be going through successfully, but creating a notificaiton does nothing on the device and returns a nil error. Any ideas what I'm missing?
import Foundation
import UIKit
import UserNotifications
class SPKPushNotifications
{
class func register(application:UIApplication){
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
application.registerForRemoteNotifications()
}
} else {
let notificationTypes: UIUserNotificationType = [UIUserNotificationType.alert, UIUserNotificationType.badge, UIUserNotificationType.sound]
let pushNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)
application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()
}
}
class func unregister(application:UIApplication){
application.unregisterForRemoteNotifications()
}
class func create(title:String, body:String, delay:Double, repeats:Bool){
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = UNNotificationSound.default() //idk if we're gonna want something else
content.badge = NSNumber(value:UIApplication.shared.applicationIconBadgeNumber+1)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval:delay, repeats:repeats)
let request = UNNotificationRequest(identifier:title, content:content, trigger:trigger)
let center = UNUserNotificationCenter.current()
center.add(request){ (error) in
print(error)
}
} else {
// Fallback on earlier versions
}
}
class func delete(){
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications()
} else {
// Fallback on earlier versions
}
}
}
You won't see the notification if the application is in the foreground. Try adding the request to the notification center when the application is in the background.
You can do (for this test only) that by adding a few second sleep and moving your application to the background. Or scheduling the notification to a later time when the application is not running in the foreground.

Wait for push notification response before segue [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