3

This works fine running in iOS8, but when I put it on an iOS7 device, it crashes giving me the error

-[UIApplication registerUserNotificationSettings:]: unrecognized selector sent to instance 0x14e56e20

I'm registering for notifications in my AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

#ifdef __IPHONE_8_0
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge
                                                                                         |UIRemoteNotificationTypeSound
                                                                                         |UIRemoteNotificationTypeAlert) categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
#else
    //register to receive notifications
    UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:myTypes];
#endif

    application.applicationIconBadgeNumber = 0;

    return YES;
}

When I set a breakpoint, it always runs the code in the __IPHONE_8_0 block. How else can I detect which block to run?

Cœur
  • 37,241
  • 25
  • 195
  • 267
djcj
  • 35
  • 5

2 Answers2

5

You shouldn't use the preprocessor like this. A better approach is to check if your object responds to the message you're sending it instead of checking the OS version. Change your method to

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)])
    {
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge
                                                                                             |UIRemoteNotificationTypeSound
                                                                                             |UIRemoteNotificationTypeAlert) categories:nil];
        [application registerUserNotificationSettings:settings];
    }
    else
    {
        //register to receive notifications
        UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
        [application registerForRemoteNotificationTypes:myTypes];
    }
    application.applicationIconBadgeNumber = 0;

    return YES;
}

A couple things to note here. I changed [UIApplication sharedApplication] to application because the delegate is already passing in your UIApplication instance, so just access it directly.

respondsToSelector: is a method from NSObject which will tell you if a receiver, application, will respond to a selector (or method). You just need to wrap the method like @selector(myMethod:) to check it. This is a good practice to get into for backwards compatibility purposes when using newer APIs. You would also want to do this kind of check when dealing with anonymous objects of type id.

As far as why the #ifdef __IPHONE_8_0 didn't work, check out this answer: https://stackoverflow.com/a/25301879/544094

Community
  • 1
  • 1
Chris
  • 7,270
  • 19
  • 66
  • 110
2

#ifdef __IPHONE_8_0 is a compile-time directive. Because your compile target will generally be the latest iOS version, this will always be the code that is compiled into your app. This is independent of the device that the app runs on as it is done before the app exists (so definitely before the app is run).

Instead, you should be using a run-time check. You could check the iOS version, or you could just check for the existence of the methods you're trying to use:

if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings)]) {
  // Do iOS8+ stuff.
} else {
  // Do iOS7- stuff.
}
Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51