Notificação push do iOS: como detectar se o usuário tocou na notificação quando o aplicativo está em segundo plano?

116

Existem muitos threads de stackoverflow relacionados a esse tópico, mas ainda não encontrei uma boa solução.

Se o aplicativo não está no fundo, eu posso verificar launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]na application:didFinishLaunchingWithOptions:chamada para ver se ele está aberto a partir de uma notificação.

Se o aplicativo estiver em segundo plano, todas as postagens sugerem o uso application:didReceiveRemoteNotification:e a verificação do estado do aplicativo. Mas, como experimentei (e também como o nome desta API sugere), esse método é chamado quando a notificação é recebida, em vez de tocada.

Portanto, o problema é, se o aplicativo for iniciado e depois colocado em segundo plano, e você souber que uma notificação foi recebida de application:didReceiveNotification(application:didFinishLaunchWithOptions: não será acionada neste ponto), como saber se o usuário retomou o aplicativo tocando na notificação ou apenas tocando no ícone do aplicativo? Porque se o usuário tocou na notificação, a expectativa é abrir a página mencionada naquela notificação. Caso contrário, não deveria.

Eu poderia usar handleActionWithIdentifierpara notificações de ação personalizada, mas isso só é acionado quando um botão de ação personalizada é pressionado, não quando o usuário toca no corpo principal da notificação.

Obrigado.

EDITAR:

depois de ler uma das respostas abaixo, pensei que desta forma posso esclarecer minha dúvida:

Como podemos diferenciar esses 2 cenários:

(A) 1.app vai para segundo plano; 2. notificação recebida; 3. toque do usuário na notificação; 4. aplicativo entra em primeiro plano

(B) 1.app vai para segundo plano; 2. notificação recebida; 3. o usuário ignora a notificação e toca no ícone do aplicativo posteriormente; 4. aplicativo entra em primeiro plano

Uma vez que application:didReceiveRemoteNotification:é acionado em ambos os casos na etapa 2.

Ou deve application:didReceiveRemoteNotification:ser acionado na etapa 3 apenas para (A), mas de alguma forma configurei meu aplicativo de forma errada, então estou vendo na etapa 2?

Bao Lei
fonte
Use um valor de dicionário personalizado para sua carga útil e aja de acordo. Bem simples.
soulshined
@soulshined um dicionário na carga útil pode representar se o usuário tocou na notificação, certo? por exemplo, seu amigo A postou um artigo B, você pode dizer {usuário: A, artigo: B} na carga útil, enquanto o aplicativo está em segundo plano e você obtém didReceiveRemoteNotification. Como saber quando o aplicativo será reiniciado, se você deve exibir o artigo?
Bao Lei de
2
@soulshined Eu li a documentação e me eduquei sobre o que didReceiveRemoteNotification faz. Você realmente leu minha pergunta? De acordo com a documentação oficial da Apple, didReceiveRemoteNotification "informa ao delegado que o aplicativo em execução recebeu uma notificação remota". Estou perguntando qual é uma boa maneira de saber se o usuário tocou em uma notificação. O link do SO ao qual você se referiu é para quando o aplicativo está sendo iniciado de um novo começo, estou perguntando o cenário quando o aplicativo está em segundo plano.
Bao Lei
1
@soulshined OK, talvez eu não tenha afirmado com clareza suficiente. Quero dizer, se o aplicativo for encerrado completamente, não em segundo plano, sim, didFinishLaunching será chamado. Mas se você iniciar seu aplicativo e, em seguida, colocá-lo em segundo plano e, agora, uma notificação entrar e o usuário tocar na notificação, e agora o didFinishLaunching não será chamado novamente. Em vez disso, applicationWillEnterForeground e applicationDidBecomeActive serão chamados. Como você pode saber se o aplicativo está entrando em primeiro plano porque o usuário tocou na notificação ou no ícone do aplicativo?
Bao Lei de

Respostas:

102

OK, eu finalmente descobri.

Nas configurações de destino ➝ guia Capacidades ➝ Modos de fundo, se você marcar "Notificações remotas", application:didReceiveRemoteNotification:será acionado assim que a notificação chegar (contanto que o aplicativo esteja em segundo plano) e, nesse caso, não há como saber se o usuário tocará na notificação.

Se você desmarcar essa caixa, application:didReceiveRemoteNotification:será acionado apenas quando você tocar na notificação.

É um pouco estranho que marcar esta caixa altere o comportamento de um dos métodos de delegação do aplicativo. Seria melhor se essa caixa estiver marcada, a Apple usa dois métodos de delegação diferentes para receber notificações e tocar em notificações. Acho que a maioria dos desenvolvedores sempre quer saber se uma notificação foi tocada ou não.

Esperançosamente, isso será útil para qualquer pessoa que tenha esse problema. A Apple também não documentou claramente aqui, então demorei um pouco para descobrir.

insira a descrição da imagem aqui

Bao Lei
fonte
6
Eu tenho os Modos de fundo definidos como "DESLIGADOS" completamente, mas ainda sou notificado quando uma notificação chega com o aplicativo em modo de fundo.
Gottfried
5
Ótimo! Isso me ajudou. No entanto, é uma pena que eu precise desligar as notificações remotas. Eu gostaria de pré-buscar dados quando uma notificação push chegar E detectar o toque do usuário. Não consigo encontrar uma maneira de conseguir isso.
Peter Fennema
1
Defino o modo de fundo para o modo "ON" e habilito as notificações remotas. Ainda não foi possível detectar o evento de notificação.
kalim sayyad
1
Eu tenho o mesmo problema O modo de fundo está definido como "LIGADO" e habilitado para notificação remota, mas ainda não sou notificado quando a notificação chega ao aplicativo no modo de fundo
Swapnil Dhotre
@Bao - Acho que vai causar rejeição na Appstore, já que essa opção é basicamente usada para baixar conteúdo relacionado à notificação em segundo plano. Portanto, se você não estiver baixando nenhum conteúdo, a Apple pode rejeitar seu aplicativo. developer.apple.com/library/ios/documentation/iPhone/Conceptual/…
niks
69

Estou procurando a mesma coisa que você e realmente encontrei uma solução que não requer notificação remota para ser marcada.

Para verificar se o usuário tocou ou se o aplicativo está em segundo plano ou ativo, basta verificar o estado do aplicativo em

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{

    if(application.applicationState == UIApplicationStateActive) {

        //app is currently active, can update badges count here

    }else if(application.applicationState == UIApplicationStateBackground){

        //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here

    }else if(application.applicationState == UIApplicationStateInactive){

        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here

    }

Para mais informações, verifique:

Referência da estrutura UIKit> Referência de classe UIApplication> UIApplicationState

Denismo
fonte
10
Que tal o aplicativo ser UIApplicationStateInactive como resultado do envolvimento do usuário com a Central de notificações (deslizar de cima para baixo) ou o centro de controle (deslizar de baixo para cima) enquanto o aplicativo está na tela. Aparentemente, não há como saber se o APNS foi recebido no estado Inativo ou se o usuário realmente o tocou.
Kostia Kim
1
@KostiaKim Você está certo, mas esse é um caso super-extremo ... fora isso, esta resposta é a mais válida em termos de separação entre o aplicativo em primeiro plano ... o usuário tocando ... o aplicativo em segundo plano
Honey
Obrigado, isso meio que desmistifica tudo. Lembre-se de que, para que o método delegado seja chamado quando o aplicativo estiver em segundo plano, a notificação push deve incluir a content-availablechave, mas a notificação deve ser silenciosa (ou seja, não incluir um som ou emblema) conforme declarado nos documentos oficiais .
Nickkk
1
@KostiaKim Na verdade, consegui corrigir o problema de não saber se o centro de controle estava aberto ou se o usuário realmente tocou na notificação adicionando um booleano definido como verdadeiro no func applicationDidEnterBackground(_ application: UIApplication)e falso no. func applicationDidBecomeActive(_ application: UIApplication)Isso me permitiu mostrar o no aplicativo notificações quando o aplicativo está inativo devido ao centro de controle ou à lista de notificações
DatForis
3
fico confuso por que a Apple não apresentou um evento diferente ou simplesmente adicionou um sinalizador nos metadados para indicar que o usuário realmente interagiu com a notificação. Ter que 'deduzir' se o usuário executou uma ação com base nas informações do ambiente sobre o estado do aplicativo não é razoável para um bit de informação importante que afeta a expectativa do usuário quanto ao comportamento do aplicativo. Talvez a única coisa a fazer seja criar uma ação personalizada para abrir o aplicativo com essas informações?
Allan Nienhuis
20

De acordo com iOS / XCode: como saber se o aplicativo foi lançado com um clique na notificação ou no ícone do aplicativo trampolim? você tem que verificar o estado do aplicativo em didReceiveLocalNotification assim:

if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive)
{
    // user has tapped notification
}
else
{
    // user opened app from app icon
}

Embora não faça totalmente sentido para mim, parece funcionar.

Werner Kratochwil
fonte
1
Não funcionaria neste cenário. Eu tentei isso antes. Quando essa caixa de seleção foi marcada (veja minha resposta aceita para detalhes), quando a notificação chegar, sua linha com o comentário "// usuário tocou na notificação" será inserida, mesmo que o usuário não tenha tocado na notificação (a notificação acabou de chegar )
Bao Lei
3
Discordo. Eu marquei "Notificações remotas" em minhas capacidades de segundo plano e funciona da maneira descrita na minha resposta. Também marquei a opção "Busca de plano de fundo". Talvez isso cause a mudança?
Werner Kratochwil
você poderia marcar minha resposta com +1? ;-) Ainda preciso de alguns votos para participar mais do stackoverflow. Muito obrigado
Werner Kratochwil
Sim, esta é a solução. tnx.
Afshin,
Observe que isso será um falso positivo se a notificação for enviada enquanto o usuário estiver em uma tela diferente (por exemplo, se ele puxar para baixo a barra de status e, em seguida, receber uma notificação de seu aplicativo).
Kevin Cooper
16

Se alguém quiser no Swift 3.0

switch application.applicationState {
    case .active:
        //app is currently active, can update badges count here
        break
    case .inactive:
        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
        break
    case .background:
        //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
        break
    default:
        break
    }

para swift 4

switch UIApplication.shared.applicationState {
case .active:
    //app is currently active, can update badges count here
    break
case .inactive:
    //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
    break
case .background:
    //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
    break
default:
    break
}
Hamid Shahsavari
fonte
Em qual gatilho devemos adicionar este código, didReceiveRemoteNotification ou didFinishLaunchingWithOptions?
Dashrath
2
Em didReceiveRemoteNotification
Hamid Shahsavari
@Hamid sh ,,,, Recebi notificação push em todos os estados, ou seja, quando o aplicativo está em forground, background, close (encerrado) ..! mas meu problema é como incrementar a contagem de crachás quando o aplicativo está no estado de segundo plano e no estado de fechamento (encerramento) ?? Por favor, diga-me em detalhes como eu faço isso ....? a contagem de meus emblemas de aplicativo só aumenta quando o aplicativo está no estado inicial .....? se possível, diga-me brevemente .......!
Kiran jadhav
8

Se você selecionou "Modos de fundo"> "Notificações remotas" == SIM, toque no evento de notificação chegará em:

-(void)userNotificationCenter:(UNUserNotificationCenter *)center **didReceiveNotificationResponse**:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler.

Isso me ajudou. Por favor aproveite :)

Aleks Osipov
fonte
Eu vi que a Apple adicionou isso, mas não consegui descobrir como obter a carga útil personalizada da notificação dessa forma.
sudo
Esta função é chamada quando o aplicativo está em um estado de segundo plano e se torna ativo e também quando o aplicativo é iniciado do estado morto
Nikhil Muskur
@NikhilMuskur apenas se as "notificações remotas" estiverem habilitadas, certo?
Zorayr
@Zorayr yup isso mesmo
Nikhil Muskur
8

Também tive esse problema - mas no iOS 11 com o novo UserNotificationsFramework.

Aqui para mim é assim:

  • Novo lançamento: application:didFinishLaunchingWithOptions:
  • Recebido de um estado finalizado: application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
  • Recebido em primeiro plano: userNotificationCenter(_:willPresent:withCompletionHandler:)
  • Recebido em segundo plano: userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
pré
fonte
Esta é a maneira de fazer isso no iOS
11+
Isso é tão complicado 🤦‍♂️
Zorayr
4

No meu caso, o modo de fundo OFF não fez nenhuma diferença. No entanto, quando o aplicativo foi suspenso e o usuário tocou na notificação, pude lidar com o caso neste método de retorno de chamada:

func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {

}
DoruChidean
fonte
Esta é a forma oficial que a Apple diz. De acordo com o documento da Apple, ele será chamado sempre que o usuário interagir com a IU de notificação por push. Se o aplicativo não estiver em segundo plano, ele iniciará o aplicativo no modo de segundo plano e chamará esse método.
Ryan
3

Para iOS 10 e superior, coloque-o em AppDelegate, para saber se a notificação foi tocada (funciona mesmo se o aplicativo estiver fechado ou aberto)

func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}
Yatin Arora
fonte
1
Esta é a resposta correta para iOS 10+. Explicação completa fornecida em outro tópico aqui: stackoverflow.com/a/41783666/1761357 .
dead_can_dance
2

Existem dois Funcs para lidar com PushNotification recebido dentro da PushNotificationManagerclasse:

class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate{

}

Como testei o primeiro no gatilho assim que a notificação chegou

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

    completionHandler(UNNotificationPresentationOptions.alert)

    //OnReceive Notification
    let userInfo = notification.request.content.userInfo
    for key in userInfo.keys {
         Constants.setPrint("\(key): \(userInfo[key])")
    }

    completionHandler([])

}

E o segundo quando tocado na notificação:

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    //OnTap Notification
    let userInfo = response.notification.request.content.userInfo
    for key in userInfo.keys {
        Constants.setPrint("\(key): \(userInfo[key])")
    }

    completionHandler()
}

Eu também testei com os estados LIGADO e DESLIGADO de Notificação Remota (em Modos de Fundo)

Saeid
fonte
1

SWIFT 5.1

UIApplication.State não funcionou para mim, porque depois de ler a impressão digital (modal é mostrado) em meu aplicativo, a notificação também é lançada na barra superior e o usuário deve clicar nela.

Eu criei

public static var isNotificationFromApp: Bool = false

em AppDelegatee eu configurei truena minha inicialização viewControllere, em seguida, na minha notificação storyboard/ viewControllereu acabei de verificar isso :)

Espero que possa ser útil

Ampulheta
fonte
-7

Você pode configurar a carga útil da sua notificação push para chamar o delegado do aplicativo application:didReceiveRemoteNotification:fetchCompletionHandler: método aplicativo quando o aplicativo está em segundo plano. Você pode definir algum sinalizador aqui para que, quando o usuário iniciar seu aplicativo na próxima vez, você possa executar sua operação.

A partir da documentação da apple, você deve usar esses métodos para baixar novo conteúdo associado à notificação push. Além disso, para que isso funcione, você deve habilitar a notificação remota nos modos de segundo plano e sua carga útil de notificação push deve conter a content-availablechave com seu valor definido como 1. Para obter mais informações, consulte Usando notificações push para iniciar um download seção do doc da apple aqui .

Outra maneira é fazer com que o emblema conte na carga útil da notificação push. Portanto, na próxima vez que seu aplicativo for iniciado, você poderá verificar a contagem de crachás do aplicativo. Se for maior que zero, execute sua operação e zerar / limpar a contagem do crachá do servidor também.

Espero que isso ajude você.

Ameet Dhas
fonte
@bhusan, você leu os detalhes da minha pergunta? Estou vendo didReceiveRemoteNotification sendo chamado quando a notificação chega (antes que o usuário toque nela). Eu queria saber se o usuário tocou nele.
Bao Lei
Você não respondeu à pergunta de OP. Ele não quer apenas saber se houve uma notificação. Ele quer saber se o usuário abriu o aplicativo tocando nessa notificação remota.
Joe C de