registerForRemoteNotificationTypes: не поддерживается в iOS 8.0 и более поздних версиях


209

При попытке зарегистрироваться для push-уведомлений под iOS 8.x:

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

Я получаю следующую ошибку:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Любые идеи, каков новый способ сделать это? Это работает, когда я запускаю это приложение Swift на iOS 7.x.

РЕДАКТИРОВАТЬ

На iOS 7.x, когда я включаю условный код, который я получаю (либо условный SystemVersion, либо #if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Посмотрите документацию по UIApplication, я думаю, что вы должны использовать registerUserNotificationSettings и registerForRemoteNotifications.
Skyte

3
спасибо, я проверю это в понедельник
Войтек Турович

@Skyte: этот метод доступен только в iOS 8+
user102008,

Кто-нибудь знает, почему до сих пор работает с приложением, которое уже находится в магазине приложений, но нет, если я пытаюсь проверить его локально?
目 白 目

1
Зависит ли это от того, с какой версией xCode был создан бинарный файл? Простите за 2 комментария подряд, я опоздал на редактирование моего комментария выше.
目 白 目

Ответы:


145

Как вы описали, вам нужно будет использовать другой метод, основанный на разных версиях iOS. Если ваша команда использует Xcode 5 (который не знает ни о каких селекторах iOS 8) и Xcode 6, вам нужно использовать условную компиляцию следующим образом:

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

Если вы используете только Xcode 6, вы можете придерживаться только этого:

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

Причина в том, что способ получения разрешений на уведомления изменился в iOS 8. A UserNotification- это сообщение, отображаемое пользователю, как удаленно, так и локально. Вам нужно получить разрешение, чтобы показать один. Это описано в видео WWDC 2014 «Что нового в iOS-уведомлениях»


11
@ Matt - У вас есть ссылка на вопрос, почему Apple нарушила предыдущий API, чтобы получить разрешения на отправку push-уведомлений в iOS8? Я сделал то же самое в своем коде, но мне нужно поделиться одним официальным документом, чтобы объяснить это другим в моей компании.
Крис Субраманян

3
@KrisSubramanian Лучшая справка, которую я имею, это предварительная документация : «Приложения, которые используют видимые или звуковые оповещения в сочетании с локальным или push-уведомлением, должны регистрировать типы оповещений, которые они используют». Что касается «почему», у меня есть только моя интерпретация: удобство конечного пользователя, чтобы не беспокоить сообщения независимо от источника.
Мэтт ---

2
Вы не можете использовать __IPHONE_OS_VERSION_MAX_ALLOWEDдля проверки этого, потому что это проверка во время компиляции.
Роб Кенигер

5
Проверка времени компиляции - то, что вам нужно в случае Xcode 5.
matt ---

1
@woheras registerUserNotificationSettings:документирована здесь
матовый ---

334

Для 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;
}

Для iOS10

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


Как насчет вызова registerForRemoteNotifications из обратного вызова registerUserNotificationSettings, если вы действительно хотите убедиться, что вы не отправляете свое первое уведомление, прежде чем получите разрешения пользователя на показ предупреждений?
Мохамед Хафез,

5
Вместо того, чтобы проверить systemVersion, вы должны проверить[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Энди

1
[[UIApplication sharedApplication] registerForRemoteNotifications];не будет ни переходить, application:didRegisterForRemoteNotificationsWithDeviceToken:либо application:didFailToRegisterForRemoteNotificationsWithError:если пользователь отключил «Разрешить уведомления» в Настройках -> Уведомления -> <Мое приложение>.
Protocole

IMO Apple должна была полностью удалить функцию в iOS 8 вместо устаревшей или обеспечить обратную совместимость. В настоящее время во многих приложениях push-уведомления молча завершаются сбоем, и разработчики сейчас пытаются решить проблему.
Джош Липцин 2

7
ИМО они не должны были нарушать обратную совместимость. Посмотрите, насколько уродливым должен быть ваш код для поддержки обеих версий, а не одной строкой ранее. Прозрачная повторная реализация ваших старых API с точки зрения новых является надежной техникой и приводит к гораздо меньшему количеству раздраженных разработчиков. Отношение Apple означает, что поддержка приложений iOS - это боль, когда усилия, необходимые для поддержания того же уровня функциональности в течение всего лишь двух лет, нетривиальны.
robbie_c

23

Опираясь на ответ @ Prasath. Вот как вы делаете это в 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)
}

14

iOS 8 изменила регистрацию уведомлений не обратно совместимым способом. Хотя вам необходимо поддерживать iOS 7 и 8 (а приложения, созданные с использованием 8 SDK, не принимаются), вы можете проверить нужные селекторы и условно правильно вызвать их для работающей версии.

Вот категория на UIApplication, которая скроет эту логику за чистым интерфейсом для вас, который будет работать как в Xcode 5, так и в Xcode 6.

Заголовок:

//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

Реализация:

//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

5
Я просто не могу поверить, почему этого не делает Apple и разработчики должны делать такие вещи, когда Apple осуждает метод. Каждая новая версия iOS одинакова. Грустно переписывать код только потому, что Apple не поддерживает старые методы.
iVela

2
Я думаю, что с течением времени все становится лучше, а не просто добавление бинтов поверх старых струпьев, как в других операционных системах, о которых я мог подумать.
Поль Бруно,

Из моих тестов (которые заняли целый день), если я иду в Settingsи отключаю уведомления, isRegisteredForRemoteNotificationsвсе равно возвращаетсяYES
Iulian Onofrei

Спасибо за добавление правильного решения: еще один слой косвенности!
Беркус

6

Я думаю, что это лучший способ сохранить обратную совместимость, если мы будем использовать этот подход, он работает для моего случая, и надежда будет работать для вас. Также довольно легко понять.

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

Лучше использовать, if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])как показано здесь
Юлиан Онофрей

5

Для Swift-склонных:

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

3
В Swift 2.0, как я понимаю, вы должны предоставить Options в наборе [.Alert, .Badge, .Sound], потому что (.Alert | .Badge | .Sound) у меня не работает.
Апан

3

Я не мог понять, какую переменную NSSet для «категорий» следует установить, поэтому, если кто-то сможет меня заполнить, я с удовольствием отредактирую этот пост. Следующее, однако, вызывает диалоговое окно push-уведомлений.

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

Изменить: я получил push-уведомление для отправки на телефон с этим кодом, поэтому я не уверен, что параметр категорий необходим.


Да, это работает на iOS8, но как мне сделать его обратно совместимым с iOS7? на iOS7 это вылетит. Проверка версии iOS не помогает, потому что iOS7 не распознает новые символы.
Войтек Турович

2
categoriesиспользуется для настройки действий уведомлений в iOS 8. Вы можете посмотреть видео WWDC 2014 «Что нового в iOS-уведомлениях» для более подробной информации
matt ---

3

Таким образом, получается, что поскольку AnyObject является духовным преемником идентификатора, вы можете вызвать любое сообщение на AnyObject. Это эквивалентно отправке сообщения на id. Хорошо, достаточно справедливо. Но теперь мы добавляем в концепцию, что все методы в AnyObject являются необязательными , и у нас есть кое-что, с чем мы можем работать.

Учитывая вышесказанное, я надеялся, что смогу просто привести UIApplication.sharedApplication () к AnyObject, затем создать переменную, равную сигнатуре метода, установить эту переменную в необязательный метод, а затем протестировать переменную. Это не похоже на работу. Я предполагаю, что при компиляции с iOS 8.0 SDK компилятор знает, где, по его мнению, должен быть метод , поэтому он оптимизирует все это вплоть до поиска в памяти. Все работает нормально, пока я не попробую проверить переменную, после чего я получу EXC_BAD_ACCESS.

Тем не менее, в том же докладе WWDC, где я нашел жемчужину о том, что все методы являются необязательными, они используют Optional Chaining для вызова необязательного метода - и это, похоже, работает. Хромая часть заключается в том, что вам нужно на самом деле попытаться вызвать метод, чтобы узнать, существует ли он, что в случае регистрации для уведомлений является проблемой, поскольку вы пытаетесь выяснить, существует ли этот метод, прежде чем приступить к созданию UIUserNotificationSettings объект. Кажется, что вызов этого метода с nil, хотя все в порядке, поэтому решение, которое, кажется, работает для меня:

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

После долгих поисков, связанных с этим, ключевая информация пришла из этого выступления на WWDC https://developer.apple.com/videos/wwdc/2014/#407 прямо посередине в разделе «Необязательные методы в протоколах».

В бета-версии Xcode 6.1 вышеуказанный код больше не работает, код ниже работает:

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

3

Если вы хотите добавить поддержку IOS7 в IOS7, вы можете применить этот код в своем проекте.

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

2

После запуска Xcode 6.1 Beta приведенный ниже код немного редактирует код Tom S, который перестал работать с бета-версией 6.1 (работал с предыдущей бета-версией):

   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

Вы можете использовать это

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;

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])

    }

1

Если все, что вам нужно, это код ios 8, это должно сделать это.

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

       [application registerForRemoteNotifications];
}

 return YES;
}

0

Это более чистый способ, которым я занимаюсь, и он просто отлично работает

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

для iOS 8 и выше

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.