209

When trying to register for push notifications under iOS 8.x:

application.registerForRemoteNotificationTypes(UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound)

I get the following error:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Any ideas what is the new way of doing it? It does work when I run this Swift app on iOS 7.x.

EDIT

On iOS 7.x when I include the conditional code I get (either SystemVersion conditional or #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings
Blazej SLEBODA
  • 6,503
  • 3
  • 35
  • 69
Wojtek Turowicz
  • 3,978
  • 2
  • 23
  • 35
  • 1
    Look at the documentation of UIApplication, I think you're supposed to use registerUserNotificationSettings and registerForRemoteNotifications. – Skyte Jun 27 '14 at 14:59
  • 3
    thanks, I will check that on monday – Wojtek Turowicz Jun 28 '14 at 11:21
  • @Skyte: That method is only available in iOS 8+ – user102008 Jul 04 '14 at 23:41
  • anyone knows why still works with an app thats already in the app store, but not if I try to test it locally? – 最白目 Sep 23 '14 at 12:41
  • 1
    Does it depend on with which xCode Version the binary was built? Pardon for 2 comments in a row, I was too late for editing my above comment. – 最白目 Sep 23 '14 at 12:59
  • @dan Apps which are already in the store are built with existing libraries; apps run locally are typically being linked with the latest libraries. An 2013 build of an app is linked to the iOS7 libraries; such an app will still run on an iOS8 phone since the phone is backward compatible. Right now, if you run your app in xcode 5, you will link against iOS7 libraries, but when you switch to xcode 6, you will link against iOS8 libs. Note - after June 1, all app updates require xcode 6 and iOS 8 libraries. – Robert Altman Feb 02 '15 at 16:20

15 Answers15

334

For iOS<10

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    //-- Set Notification
    if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) 
    {
           // iOS 8 Notifications
           [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];

           [application registerForRemoteNotifications];
    }
    else
    {
          // iOS < 8 Notifications
          [application registerForRemoteNotificationTypes:
                     (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
    }

     //--- your custom code
     return YES;
}

For iOS10

https://stackoverflow.com/a/39383027/3560390

Community
  • 1
  • 1
PrasathBabu
  • 3,864
  • 3
  • 13
  • 28
  • How about calling registerForRemoteNotifications from registerUserNotificationSettings's callback, if you really want to make sure that you don't send your first notification before you get user permissions to show alerts? – Mohamed Hafez Sep 16 '14 at 10:20
  • 5
    Rather than check the `systemVersion`, you should check `[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]` – Andy Sep 20 '14 at 01:44
  • 1
    `[[UIApplication sharedApplication] registerForRemoteNotifications];` won't neither go to `application:didRegisterForRemoteNotificationsWithDeviceToken:` or `application:didFailToRegisterForRemoteNotificationsWithError:` if a user disabled "Allow Notifications" in Settings -> Notifications -> . – Protocole Sep 22 '14 at 09:49
  • IMO Apple should have removed the function entirely in iOS 8 instead of deprecating, or provided for backwards compatibility. The way it is now, push notifications are failing silently in many apps and developers are now scrambling to patch the issue. – Josh Liptzin Oct 18 '14 at 05:18
  • 7
    IMO they shouldn't have broken backwards compatibility. Look how ugly your code has to be to support both versions, as opposed to one line before. Transparently re-implementing your old APIs in terms of new ones is a solid technique and results in far fewer annoyed developers. Apple's attitude means it's a pain to support iOS apps, where the effort required to keep the same level of functionality over as little as 2 years is non-trivial. – robbie_c Nov 07 '14 at 11:55
145

As you described, you will need to use a different method based on different versions of iOS. If your team is using both Xcode 5 (which doesn't know about any iOS 8 selectors) and Xcode 6, then you will need to use conditional compiling as follows:

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
    // use registerUserNotificationSettings
} else {
    // use registerForRemoteNotificationTypes:
}
#else
// use registerForRemoteNotificationTypes:
#endif

If you are only using Xcode 6, you can stick with just this:

if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
    // use registerUserNotificationSettings
} else {
    // use registerForRemoteNotificationTypes:
}

The reason is here is that the way you get notification permissions has changed in iOS 8. A UserNotification is a message shown to the user, whether from remote or from local. You need to get permission to show one. This is described in the WWDC 2014 video "What's New in iOS Notifications"

Cœur
  • 32,421
  • 21
  • 173
  • 232
matt---
  • 2,420
  • 2
  • 16
  • 19
  • 11
    @Matt - Do you have a reference as to why apple has broken the previous API to get permissions for sending push in iOS8 ? I have done the same thing in my code, but I need to share some official doc to explain this to others in my company. – Kris Subramanian Jul 24 '14 at 18:10
  • 3
    @KrisSubramanian Best reference I have is the [pre-release documentation](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIUserNotificationSettings_class/index.html#//apple_ref/occ/cl/UIUserNotificationSettings): "Apps that use visible or audible alerts in conjunction with a local or push notification must register the types of alerts they employ." As to "why," I only have my interpretation: end-user convenience to not be bothered by messages, regardless of source. – matt--- Jul 24 '14 at 18:42
  • 2
    You can't use `__IPHONE_OS_VERSION_MAX_ALLOWED` to check for this because it's a compile-time check. – Rob Keniger Sep 12 '14 at 10:13
  • 5
    A compile-time check is what you need in the case of Xcode 5. – matt--- Sep 12 '14 at 18:39
  • if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) part is wrong, it must be : if ([application respondsToSelector:@selector(registerUserNotificationSettings)]) – woheras Nov 03 '14 at 08:50
  • 1
    @woheras `registerUserNotificationSettings:` is documented [here](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html#//apple_ref/occ/instm/UIApplication/registerUserNotificationSettings:) – matt--- Nov 03 '14 at 19:18
  • 1
    `- (void)registerForRemoteNotifications` is only available from __iOS 8__ – Iulian Onofrei Feb 13 '15 at 17:20
  • How can we check if the user already Rejected it previously? currentUserNotificationSettings.types collection returns none in both cases: when user never was prompted before, and after he already rejected it. I need a way to pop up a custom Alert Box, telling them to go to Settings and enable it, after they have previously turned it off already. – FranticRock Jul 23 '15 at 20:04
23

Building on @Prasath's answer. This is how you do it in Swift:

if application.respondsToSelector("isRegisteredForRemoteNotifications")
{
    // iOS 8 Notifications
    application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: (.Badge | .Sound | .Alert), categories: nil));
    application.registerForRemoteNotifications()
}
else
{
    // iOS < 8 Notifications
    application.registerForRemoteNotificationTypes(.Badge | .Sound | .Alert)
}
Austen Chongpison
  • 3,956
  • 1
  • 17
  • 17
14

iOS 8 has changed notification registration in a non-backwards compatible way. While you need to support iOS 7 and 8 (and while apps built with the 8 SDK aren't accepted), you can check for the selectors you need and conditionally call them correctly for the running version.

Here's a category on UIApplication that will hide this logic behind a clean interface for you that will work in both Xcode 5 and Xcode 6.

Header:

//Call these from your application code for both iOS 7 and 8
//put this in the public header
@interface UIApplication (RemoteNotifications)

- (BOOL)pushNotificationsEnabled;
- (void)registerForPushNotifications;

@end

Implementation:

//these declarations are to quiet the compiler when using 7.x SDK
//put this interface in the implementation file of this category, so they are
//not visible to any other code.
@interface NSObject (IOS8)

- (BOOL)isRegisteredForRemoteNotifications;
- (void)registerForRemoteNotifications;

+ (id)settingsForTypes:(NSUInteger)types categories:(NSSet*)categories;
- (void)registerUserNotificationSettings:(id)settings;

@end

@implementation UIApplication (RemoteNotifications)

- (BOOL)pushNotificationsEnabled
{
    if ([self respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
    {
        return [self isRegisteredForRemoteNotifications];
    }
    else
    {
        return ([self enabledRemoteNotificationTypes] & UIRemoteNotificationTypeAlert);
    }
}

- (void)registerForPushNotifications
{
    if ([self respondsToSelector:@selector(registerForRemoteNotifications)])
    {
        [self registerForRemoteNotifications];

        Class uiUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings");

        //If you want to add other capabilities than just banner alerts, you'll need to grab their declarations from the iOS 8 SDK and define them in the same way.
        NSUInteger UIUserNotificationTypeAlert   = 1 << 2;

        id settings = [uiUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:[NSSet set]];            
        [self registerUserNotificationSettings:settings];

    }
    else
    {
        [self registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert];
    }
}

@end
Jeff Holliday
  • 750
  • 6
  • 7
  • 5
    I just can't believe why doesn't make this things Apple and the developers have to make things like this whenever Apple deprecate a method. Every new version of iOS it's the same. It's sad to be rewriting code just because Apple deprecate older methods. – iVela Sep 22 '14 at 19:57
  • 2
    I think it is so that things get better over time instead of just adding bandaids on top of old scabs like other operating systems I could think of. – Paul Bruneau Oct 24 '14 at 17:17
  • From my tests (that took an entire day), if I go to the `Settings` and disable notifications, `isRegisteredForRemoteNotifications` still returns `YES` – Iulian Onofrei Feb 13 '15 at 17:25
  • Thumbs up for adding a proper solution: another layer of indirection! – berkus Jun 21 '15 at 13:18
6

I think this is the better way to keep backwards compatibility if we go with this approach, it is working for my case and hope will work for you. Also pretty easy to understand.

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
    [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}
else
{
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
         (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
  • Better use `if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])` as shown [here](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-1114537-BABHHJBC) – Iulian Onofrei Feb 13 '15 at 17:28
5

For the Swift-inclined:

if let registration: AnyObject = NSClassFromString("UIUserNotificationSettings") { // iOS 8+
    let notificationTypes: UIUserNotificationType = (.Alert | .Badge | .Sound)
    let notificationSettings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)

    application.registerUserNotificationSettings(notificationSettings)
} else { // iOS 7
    application.registerForRemoteNotificationTypes(.Alert | .Badge | .Sound)
}
AstroCB
  • 11,800
  • 20
  • 54
  • 68
  • 3
    In Swift 2.0 as I understand you should provide Options in set [.Alert, .Badge, .Sound] because (.Alert | .Badge | .Sound) didn't worked for me. – Apan Jun 26 '15 at 13:11
3

I couldn't figure out what the "categories" NSSet variable should be set to, so if someone could fill me in I will gladly edit this post. The following does, however, bring up the push notification dialog.

[[UIApplication sharedApplication] registerForRemoteNotifications];
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];

Edit: I got a push notification to send to my phone with this code, so I'm not sure the categories parameter is necessary.

  • Yes this does work on iOS8 but how do I make it backwards compatible with iOS7? on iOS7 this will crash. Doing an iOS version check doesn't help because iOS7 doesn't reckognise the new symbols. – Wojtek Turowicz Jul 07 '14 at 09:23
  • 2
    `categories` is used to set up Notification Actions in iOS 8. You can see the WWDC 2014 video ["What's New in iOS Notifications"](https://developer.apple.com/videos/wwdc/2014/?include=713#713) for more details – matt--- Jul 11 '14 at 00:39
3

So it turns out that because AnyObject is the spiritual successor to id, you can call any message you want on AnyObject. That's the equivalent of sending a message to id. Ok, fair enough. But now we add in the concept that all methods are optional on AnyObject, and we have something we can work with.

Given the above, I was hopeful I could just cast UIApplication.sharedApplication() to AnyObject, then create a variable equal to the method signature, set that variable to the optional method, then test the variable. This didn't seem to work. My guess is that when compiled against the iOS 8.0 SDK, the compiler knows where it thinks that method should be, so it optimizes this all down to a memory lookup. Everything works fine until I try to test the variable, at which point I get a EXC_BAD_ACCESS.

However, in the same WWDC talk where I found the gem about all methods being optional, they use Optional Chaining to call an optional method - and this seems to work. The lame part is that you have to actually attempt to call the method in order to know if it exists, which in the case of registering for notifications is a problem because you're trying to figure out if this method exists before you go creating a UIUserNotificationSettings object. It seems like calling that method with nil though is okay, so the solution that seems to be working for me is:

var ao: AnyObject = UIApplication.sharedApplication()
if let x:Void = ao.registerUserNotificationSettings?(nil) {
    // It's iOS 8
    var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
    var settings = UIUserNotificationSettings(forTypes: types, categories: nil)
    UIApplication.sharedApplication().registerUserNotificationSettings(settings)
} else {
    // It's older
    var types = UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert
    UIApplication.sharedApplication().registerForRemoteNotificationTypes(types)
}

After much searching related to this, the key info came from this WWDC talk https://developer.apple.com/videos/wwdc/2014/#407 right in the middle at the section about "Optional Methods in Protocols"

In Xcode 6.1 beta the above code does not work anymore, the code below works:

   if UIApplication.sharedApplication().respondsToSelector("registerUserNotificationSettings:") {
        // It's iOS 8
        var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
       var settings = UIUserNotificationSettings(forTypes: types, categories: nil)
       UIApplication.sharedApplication().registerUserNotificationSettings(settings)
    } else {
        // It's older
        var types = UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert
        UIApplication.sharedApplication().registerForRemoteNotificationTypes(types)
    }
Community
  • 1
  • 1
Tom S.
  • 149
  • 3
3

If you like to add support to IOS7 IOS8 you can apply this code into your project .

-(void) Subscribe {
    NSLog(@"Registering for push notifications...");

    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    }
}

-(void)application:(UIApplication *)application 
    didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {

    if (notificationSettings.types) {
        NSLog(@"user allowed notifications");
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        NSLog(@"user did not allow notifications");
        UIAlertView *alert =[[UIAlertView alloc] 
            initWithTitle:@"Please turn on Notification"
            message:@"Go to Settings > Notifications > App.\n Switch on Sound, Badge & Alert"
            delegate:self
            cancelButtonTitle:@"Ok"
            otherButtonTitles: nil];
        [alert show];
        // show alert here
    }
}
Sagar R. Kothari
  • 23,587
  • 48
  • 158
  • 226
Nuno Sarmento
  • 331
  • 4
  • 13
2

After Xcode 6.1 Beta the code below works, slight edit on Tom S code that stopped working with the 6.1 beta (worked with previous beta):

   if UIApplication.sharedApplication().respondsToSelector("registerUserNotificationSettings:") {
        // It's iOS 8
        var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
       var settings = UIUserNotificationSettings(forTypes: types, categories: nil)
       UIApplication.sharedApplication().registerUserNotificationSettings(settings)
    } else {
        // It's older
        var types = UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert
        UIApplication.sharedApplication().registerForRemoteNotificationTypes(types)
    }
2

You can use this

if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) 
    {
        // for iOS 8
        [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];

        [application registerForRemoteNotifications];
    }
    else
    {
        // for iOS < 8
        [application registerForRemoteNotificationTypes:
         (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
    }

    // RESET THE BADGE COUNT 
    application.applicationIconBadgeNumber = 0;
Neenu
  • 6,740
  • 1
  • 26
  • 52
2

Swift 2.0

// Checking if app is running iOS 8
    if application.respondsToSelector("isRegisteredForRemoteNotifications") {

        print("registerApplicationForPushNotifications - iOS 8")

        application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil));
        application.registerForRemoteNotifications()

    } else {
        // Register for Push Notifications before iOS 8
        print("registerApplicationForPushNotifications - <iOS 8")
        application.registerForRemoteNotificationTypes([UIRemoteNotificationType.Alert, UIRemoteNotificationType.Badge, UIRemoteNotificationType.Sound])

    }
Anit Kumar
  • 7,239
  • 1
  • 16
  • 25
1

If all you need is the ios 8 code, this should do it.

 - (BOOL)application:(UIApplication *)application       didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
       [application registerUserNotificationSettings: [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound  | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge)  categories:nil]];

       [application registerForRemoteNotifications];
}

 return YES;
}
Tim
  • 11
  • 3
0

This is cleaner way I am doing and it just works great

if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_8_0)
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge|
     UIRemoteNotificationTypeAlert| UIRemoteNotificationTypeSound];
     else {
         [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]]; 
         [application registerForRemoteNotifications];
     }
0

for iOS 8 and above

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Hardik Thakkar
  • 13,424
  • 2
  • 80
  • 72