registerForRemoteNotificationTypes: não é suportado no iOS 8.0 e posterior


209

Ao tentar se registrar para receber notificações push no iOS 8.x:

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

Estou tendo o erro a seguir:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Alguma idéia de qual é a nova maneira de fazer isso? Funciona quando executo este aplicativo Swift no iOS 7.x.

EDITAR

No iOS 7.x, quando incluo o código condicional que recebo (SystemVersion condicional ou #if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Veja a documentação do UIApplication, acho que você deve usar registerUserNotificationSettings e registerForRemoteNotifications.
Skyte

3
obrigado, vou verificar isso na segunda-feira
Wojtek Turowicz

@Skyte: Esse método está disponível apenas no iOS 8
superior

alguém sabe por que ainda trabalha com um aplicativo que já está na loja de aplicativos, mas não se eu tentar testá-lo localmente?
最白目

1
Depende de qual versão do xCode o binário foi construído? Perdoado por 2 comentários seguidos, eu estava muito atrasado para editar meu comentário acima.
最白目

Respostas:


145

Como você descreveu, será necessário usar um método diferente com base em diferentes versões do iOS. Se sua equipe estiver usando o Xcode 5 (que não conhece nenhum seletor do iOS 8) e o Xcode 6, será necessário usar a compilação condicional da seguinte maneira:

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

Se você estiver usando apenas o Xcode 6, poderá usar apenas isso:

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

O motivo é que o modo como você obtém permissões de notificação mudou no iOS 8. A UserNotificationé uma mensagem mostrada ao usuário, remota ou local. Você precisa obter permissão para mostrar um. Isso é descrito no vídeo da WWDC 2014 "O que há de novo nas notificações do iOS"


11
@ Matt - Você tem uma referência de por que a apple quebrou a API anterior para obter permissões para enviar push no iOS8? Fiz a mesma coisa no meu código, mas preciso compartilhar um documento oficial para explicar isso a outras pessoas na minha empresa.
Kris Subramanian

3
@KrisSubramanian A melhor referência que tenho é a documentação de pré-lançamento : "Os aplicativos que usam alertas visíveis ou audíveis em conjunto com uma notificação local ou por push devem registrar os tipos de alertas que empregam". Quanto ao "porquê", tenho apenas minha interpretação: a conveniência do usuário final de não ser incomodada por mensagens, independentemente da origem.
Matt ---

2
Você não pode __IPHONE_OS_VERSION_MAX_ALLOWEDverificar isso porque é uma verificação em tempo de compilação.
Rob Keniger 12/09

5
Uma verificação em tempo de compilação é o que você precisa no caso do Xcode 5.
matt ---

1
@woheras registerUserNotificationSettings:está documentado aqui
matt ---

334

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

Para iOS10

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


Que tal ligar para registerForRemoteNotifications do retorno de chamada de registerUserNotificationSettings, se você realmente deseja garantir que não envie sua primeira notificação antes de obter permissões de usuário para mostrar alertas?
Mohamed Hafez

5
Em vez de verificar o systemVersion, você deve verificar #[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Andy

1
[[UIApplication sharedApplication] registerForRemoteNotifications];não acessará application:didRegisterForRemoteNotificationsWithDeviceToken:ou application:didFailToRegisterForRemoteNotificationsWithError:se um usuário desativou "Permitir notificações" em Configurações -> Notificações -> <Meu aplicativo>.
Protocole

A IMO Apple deveria ter removido totalmente a função no iOS 8 em vez de descontinuar ou fornecer compatibilidade com versões anteriores. Do jeito que está agora, as notificações por push estão falhando silenciosamente em muitos aplicativos e os desenvolvedores estão se esforçando para corrigir o problema.
Josh Liptzin

7
IMO eles não deveriam ter quebrado a compatibilidade com versões anteriores. Veja como o seu código deve ser feio para suportar as duas versões, em oposição a uma linha anterior. A reimplementação transparente de suas APIs antigas em termos de novas é uma técnica sólida e resulta em muito menos desenvolvedores irritados. A atitude da Apple significa que é difícil suportar aplicativos para iOS, onde o esforço necessário para manter o mesmo nível de funcionalidade por menos de 2 anos não é trivial.
robbie_c

23

Com base na resposta de @ Prasath. É assim que você faz no 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

O iOS 8 alterou o registro de notificações de uma maneira não compatível com versões anteriores. Embora você precise oferecer suporte ao iOS 7 e 8 (e embora os aplicativos criados com o SDK 8 não sejam aceitos), você pode verificar os seletores necessários e chamá-los condicionalmente corretamente para a versão em execução.

Aqui está uma categoria no UIApplication que oculta essa lógica por trás de uma interface limpa para você, que funcionará no Xcode 5 e no Xcode 6.

Cabeçalho:

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

Implementação:

//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
Eu simplesmente não consigo acreditar por que isso não torna as coisas que a Apple e os desenvolvedores precisam fazer assim sempre que a Apple descontinua um método. Toda nova versão do iOS é a mesma. É triste reescrever códigos apenas porque a Apple reprova os métodos mais antigos.
iVela 22/09

2
Eu acho que é para que as coisas melhorem com o tempo, em vez de apenas adicionar bandaids em cima de crostas antigas, como outros sistemas operacionais em que eu poderia pensar.
Paul Bruneau

De meus testes (que levou um dia inteiro), se eu ir para os Settingse desativar as notificações, isRegisteredForRemoteNotificationsainda retornosYES
Iulian Onofrei

Polegares para cima para adicionar uma solução adequada: outra camada de indireção!
Berkus

6

Eu acho que essa é a melhor maneira de manter a compatibilidade com versões anteriores, se seguirmos essa abordagem, ela está funcionando para o meu caso e espero que funcione para você. Também é muito fácil de entender.

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

Melhor uso, if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])como mostrado aqui
Iulian Onofrei 13/02/2015

5

Para os de inclinação rápida:

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
No Swift 2.0, como eu entendo, você deve fornecer Opções no conjunto [.Alert, .Badge, .Sound] porque (.Alert | .Badge | .Sound) não funcionou para mim.
Apan

3

Eu não conseguia descobrir o que a variável NSSet "categorias" deve ser definida, por isso, se alguém puder me informar, terei prazer em editar esta postagem. A seguir, porém, é exibida a caixa de diálogo de notificação por push.

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

Editar: recebi uma notificação por push para enviar ao meu telefone com esse código, então não tenho certeza se o parâmetro de categorias é necessário.


Sim, isso funciona no iOS8, mas como torná-lo compatível com o iOS7? no iOS7, isso irá travar. A verificação da versão do iOS não ajuda porque o iOS7 não reconhece os novos símbolos.
Wojtek Turowicz 07/07/2014

2
categoriesé usado para configurar ações de notificação no iOS 8. Você pode ver o vídeo da WWDC 2014 "O que há de novo nas notificações do iOS" para obter mais detalhes
matt ---

3

Portanto, como o AnyObject é o sucessor espiritual do id, você pode chamar qualquer mensagem que desejar no AnyObject. É o equivalente a enviar uma mensagem para id. Ok, é justo. Mas agora adicionamos o conceito de que todos os métodos são opcionais em AnyObject e temos algo com o qual podemos trabalhar.

Dado o exposto, esperava-me que pudesse converter UIApplication.sharedApplication () para AnyObject, criar uma variável igual à assinatura do método, definir essa variável para o método opcional e testar a variável. Isso não pareceu funcionar. Meu palpite é que, quando compilado no iOS 8.0 SDK, o compilador sabe onde ele acha que esse método deve estar, otimizando tudo isso em busca de memória. Tudo funciona bem até eu tentar testar a variável; nesse momento, recebo um EXC_BAD_ACCESS.

No entanto, na mesma palestra da WWDC em que achei a jóia sobre todos os métodos opcionais, eles usam o Encadeamento opcional para chamar um método opcional - e isso parece funcionar. A parte ruim é que você precisa realmente chamar o método para saber se ele existe, o que, no caso de se registrar para receber notificações, é um problema, porque você está tentando descobrir se esse método existe antes de criar um Objeto UIUserNotificationSettings. Parece que chamar esse método com nada é bom, então a solução que parece estar funcionando para mim é:

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

Após muita pesquisa relacionada a isso, as principais informações vieram desta conversa da WWDC https://developer.apple.com/videos/wwdc/2014/#407 bem no meio, na seção sobre "Métodos opcionais em protocolos"

No Xcode 6.1 beta, o código acima não funciona mais, o código abaixo funciona:

   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

Se você deseja adicionar suporte ao IOS7 IOS8, pode aplicar esse código ao seu projeto.

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

Após o Xcode 6.1 Beta, o código abaixo funciona, uma leve edição no código Tom S que parou de funcionar com o 6.1 beta (trabalhou com o beta anterior):

   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

Você pode usar isso

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

Se tudo o que você precisa é do código ios 8, isso deve ser feito.

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

       [application registerForRemoteNotifications];
}

 return YES;
}

0

Esta é a maneira mais limpa de fazer e funciona muito bem

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

para iOS 8 e superior

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.