Powiadomienie push na iOS: jak wykryć, czy użytkownik kliknął powiadomienie, gdy aplikacja działa w tle?

116

Jest wiele wątków związanych z przepełnieniem stosów dotyczących tego tematu, ale nadal nie znalazłem dobrego rozwiązania.

Jeśli aplikacja nie jest w tle, można sprawdzić launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]na application:didFinishLaunchingWithOptions:wezwanie, aby zobaczyć czy jest otwarty od zgłoszenia.

Jeśli aplikacja działa w tle, wszystkie posty sugerują użycie application:didReceiveRemoteNotification:i sprawdzenie stanu aplikacji. Ale jak eksperymentowałem (i jak sugeruje nazwa tego API), ta metoda jest wywoływana po odebraniu powiadomienia, zamiast dotknięcia.

Problem polega więc na tym, że jeśli aplikacja jest uruchomiona, a następnie działa w tle, i wiesz, że otrzymano powiadomienie application:didReceiveNotification( application:didFinishLaunchWithOptions:nie zostanie uruchomione w tym momencie), skąd wiesz, czy użytkownik wznowił aplikację, dotykając powiadomienia lub po prostu dotykając ikonę aplikacji? Ponieważ jeśli użytkownik dotknął powiadomienia, oczekuje się, że otworzy stronę wspomnianą w tym powiadomieniu. W przeciwnym razie nie powinno.

Mógłbym użyć handleActionWithIdentifierdo powiadomień o niestandardowych akcjach, ale jest to wyzwalane tylko po dotknięciu przycisku niestandardowej akcji, a nie po dotknięciu przez użytkownika głównej treści powiadomienia.

Dzięki.

EDYTOWAĆ:

po przeczytaniu jednej odpowiedzi poniżej pomyślałem, że mogę wyjaśnić swoje pytanie:

Jak możemy rozróżnić te 2 scenariusze:

(A) 1. aplikacja przechodzi w tło; 2. otrzymane powiadomienie; 3. użytkownik dotyka powiadomienia; 4. aplikacja przechodzi na pierwszy plan

(B) 1. aplikacja przechodzi w tło; 2. otrzymane powiadomienie; 3. użytkownik ignoruje powiadomienie i później dotyka ikony aplikacji; 4. aplikacja przechodzi na pierwszy plan

Ponieważ application:didReceiveRemoteNotification:jest wyzwalane w obu przypadkach w kroku 2.

A może powinien application:didReceiveRemoteNotification:zostać uruchomiony w kroku 3 tylko dla (A), ale w jakiś sposób skonfigurowałem nieprawidłową aplikację, więc widzę ją w kroku 2?

Bao Lei
źródło
Użyj niestandardowej wartości słownika dla swojego ładunku i postępuj zgodnie z tym. Dość proste.
soulshined
@soulshined słownik w ładunku może wskazywać, czy użytkownik kliknął powiadomienie, prawda? np. twój znajomy A opublikował artykuł B, możesz powiedzieć {user: A, article: B} w ładunku, gdy aplikacja jest w tle i otrzymasz didReceiveRemoteNotification. Skąd wiesz, kiedy aplikacja zostanie wznowiona, czy powinieneś wyświetlić artykuł?
Bao Lei
2
@soulshined Przeczytałem dokumentację i nauczyłem się, co robi didReceiveRemoteNotification. Czy faktycznie przeczytałeś moje pytanie? Zgodnie z oficjalną dokumentacją firmy Apple didReceiveRemoteNotification „informuje delegata, że ​​uruchomiona aplikacja otrzymała zdalne powiadomienie”. Pytam, jak dobrze sprawdzić, czy użytkownik kliknął powiadomienie. Odnośnik SO, o którym wspomniałeś, dotyczy sytuacji, gdy aplikacja jest uruchamiana od nowa. Pytam o scenariusz, gdy aplikacja działa w tle.
Bao Lei
1
@soulshined OK, może nie powiedziałem tego wystarczająco jasno. Chodzi mi o to, że jeśli aplikacja zostanie całkowicie zamknięta, a nie w tle, tak zrobiłemFinishLaunching zostanie wywołane. Ale jeśli uruchomisz aplikację, a następnie uruchomisz aplikację w tle, a teraz pojawi się powiadomienie, a użytkownik dotknie powiadomienia, a teraz didFinishLaunching nie zostanie ponownie wywołany. Zamiast tego zostaną wywołane applicationWillEnterForeground i applicationDidBecomeActive. Po czym poznać, że aplikacja wchodzi na pierwszy plan, ponieważ użytkownik kliknął powiadomienie lub ikonę aplikacji?
Bao Lei

Odpowiedzi:

102

OK, w końcu się domyśliłem.

W ustawieniach docelowych ➝ Karta Możliwości ➝ Tryby w tle, jeśli zaznaczysz opcję „Powiadomienia zdalne”, application:didReceiveRemoteNotification:zostaną uruchomione, gdy tylko nadejdzie powiadomienie (o ile aplikacja działa w tle) i w takim przypadku nie ma sposobu, aby stwierdzić, czy użytkownik dotknie powiadomienia.

Jeśli odznaczysz to pole, application:didReceiveRemoteNotification:zostanie uruchomione dopiero po dotknięciu powiadomienia.

To trochę dziwne, że zaznaczenie tego pola zmieni sposób zachowania jednej z metod delegata aplikacji. Byłoby lepiej, gdyby to pole było zaznaczone, Apple używa dwóch różnych metod delegatów do odbierania powiadomień i stukania powiadomień. Myślę, że większość programistów zawsze chce wiedzieć, czy dotknęło powiadomienia, czy nie.

Mam nadzieję, że będzie to pomocne dla każdego, kto napotka ten problem. Apple również nie udokumentował tego jasno tutaj, więc zajęło mi trochę czasu, zanim się zorientowałem.

wprowadź opis obrazu tutaj

Bao Lei
źródło
6
Mam całkowicie wyłączone tryby tła, ale nadal otrzymuję powiadomienia o nadejściu powiadomienia z aplikacją działającą w tle.
Gottfried
5
Wspaniały! To mi pomogło. Szkoda jednak, że muszę wyłączyć zdalne powiadomienia. Chciałbym wstępnie pobrać dane, gdy nadejdzie powiadomienie push ORAZ wykryję dotknięcie użytkownika. Nie mogę znaleźć sposobu, aby to osiągnąć.
Peter Fennema
1
Ustawiłem tryb tła na tryb „ON” i włączam zdalne powiadomienia. Nadal nie można wykryć zdarzenia powiadomienia.
kalim sayyad
1
Mam ten sam problem Tryb tła jest ustawiony na „WŁĄCZONY” i włączone zdalne powiadamianie, ale nadal nie otrzymuję powiadomienia, gdy powiadomienie dotrze do aplikacji w trybie tła
Swapnil Dhotre
@Bao - myślę, że spowoduje to odrzucenie w Appstore, ponieważ ta opcja jest w zasadzie używana do pobierania treści związanych z powiadomieniem w tle. Jeśli więc nie pobierasz żadnych treści, Apple może odrzucić Twoją aplikację. developer.apple.com/library/ios/documentation/iPhone/Conceptual/…
niks
69

Szukałem tego samego co Ty i znalazłem rozwiązanie, które nie wymaga odhaczania zdalnego powiadomienia.

Aby sprawdzić, czy użytkownik kliknął, aplikacja jest w tle lub jest aktywna, wystarczy sprawdzić stan aplikacji w

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

    }

Aby uzyskać więcej informacji, sprawdź:

UIKit Framework Reference> UIApplication Class Reference> UIApplicationState

Dennizm
źródło
10
A co z aplikacją, która jest UIApplicationState nieaktywna w wyniku interakcji użytkownika z Centrum powiadomień (przesuń od góry) lub Centrum sterowania (przesuń od dołu), gdy aplikacja jest na ekranie. Najwyraźniej nie ma sposobu, aby stwierdzić, czy APNS został odebrany w stanie nieaktywnym, czy też użytkownik faktycznie go dotknął.
Kostia Kim
1
@KostiaKim Masz rację, ale to super ostry przypadek ... poza tym, ta odpowiedź jest najbardziej trafna pod względem oddzielania aplikacji na pierwszym planie ... stukanie użytkownika ... aplikacja w tle
Kochanie
Dzięki, to wszystko wyjaśnia. Pamiętaj, że aby metoda delegata została wywołana, gdy aplikacja jest w tle, powiadomienie push musi zawierać content-availableklucz, ale powiadomienie musi być ciche (tj. Nie zawierać dźwięku ani znaczka), jak podano w oficjalnych dokumentach .
Nickkk,
1
@KostiaKim Naprawdę udało mi się rozwiązać problem polegający na tym, że nie wiedziałem, czy centrum sterowania było otwarte lub czy użytkownik faktycznie dotknął powiadomienia, dodając wartość logiczną, która jest ustawiona na true w func applicationDidEnterBackground(_ application: UIApplication)i false w tym, func applicationDidBecomeActive(_ application: UIApplication)co pozwoliło mi pokazać w aplikacji powiadomienia, gdy aplikacja jest nieaktywna z powodu centrum sterowania lub listy powiadomień
DatForis
3
zdumiewa mnie, dlaczego Apple nie przedstawił innego zdarzenia lub po prostu nie dodał flagi do metadanych, aby wskazać, że użytkownik faktycznie wszedł w interakcję z powiadomieniem. Konieczność „wywnioskowania”, czy użytkownik wykonał jakąś czynność na podstawie informacji z otoczenia o stanie aplikacji, jest dość nieracjonalna w przypadku kluczowych informacji, które wpływają na oczekiwane przez użytkownika zachowanie aplikacji. Być może jedyną rzeczą do zrobienia jest utworzenie niestandardowej akcji, aby otworzyć aplikację z tymi informacjami?
Allan Nienhuis
20

Według iOS / XCode: skąd wiedzieć, że aplikacja została uruchomiona za pomocą kliknięcia powiadomienia lub ikony aplikacji Springboard? musisz sprawdzić stan aplikacji w didReceiveLocalNotification w następujący sposób:

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

Chociaż nie ma to dla mnie pełnego sensu, wydaje się, że działa.

Werner Kratochwil
źródło
1
W tym scenariuszu to nie zadziała. Próbowałem tego wcześniej. Po zaznaczeniu tego pola wyboru (szczegóły w mojej zaakceptowanej odpowiedzi), kiedy nadejdzie powiadomienie, zostanie wprowadzona linia z komentarzem „// użytkownik kliknął powiadomienie”, mimo że użytkownik nie dotknął powiadomienia (powiadomienie właśnie dotarło ).
Bao Lei
3
Nie zgadzam się. Mam zaznaczone "Zdalne powiadomienia" w moich możliwościach w tle i działa to w sposób opisany w mojej odpowiedzi. Mam również zaznaczoną opcję „Pobieranie w tle”. Może to powoduje zmianę?
Werner Kratochwil
czy mógłbyś dać +1 mojej odpowiedzi? ;-) Nadal potrzebuję kilku głosów, aby bardziej uczestniczyć w stackoverflow.
Werner Kratochwil
Tak, to jest rozwiązanie. tnx.
Afshin
Zwróć uwagę, że będzie to fałszywy alarm, jeśli powiadomienie zostanie wysłane, gdy użytkownik znajduje się na innym ekranie (na przykład, jeśli opuści pasek stanu, a następnie otrzyma powiadomienie z Twojej aplikacji).
Kevin Cooper
16

Jeśli ktoś chce tego w 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
    }

dla szybkiego 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
źródło
Na którym wyzwalaczu powinniśmy dodać ten kod, didReceiveRemoteNotification lub didFinishLaunchingWithOptions?
Dashrath
2
On didReceiveRemoteNotification
Hamid Shahsavari
@Hamid sh ,,,, Otrzymałem powiadomienie push we wszystkich stanach, tj. Kiedy aplikacja jest w trybie forground, background, close (zakończona) ..! ale moim problemem jest to, jak zwiększyć liczbę odznak, gdy aplikacja jest w stanie tła i zamyka (kończy) stan ???? Proszę, powiedz mi szczegółowo, jak to robię ....? liczba odznak mojej aplikacji rośnie tylko wtedy, gdy aplikacja jest w stanie podstawowym…? jeśli to możliwe, powiedz mi krótko .......!
Kiran jadhav
9

Ja też napotkałem ten problem - ale na iOS 11 z nowym UserNotificationsFramework.

Tutaj dla mnie jest tak:

  • Nowy start: application:didFinishLaunchingWithOptions:
  • Otrzymano ze stanu zakończenia: application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
  • Otrzymane na pierwszym planie: userNotificationCenter(_:willPresent:withCompletionHandler:)
  • Otrzymane w tle: userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
przed
źródło
W ten sposób można to zrobić w iOS 11 +
OhadM
To jest takie skomplikowane 🤦‍♂️
Zorayr
8

Jeśli masz zaznaczone "Tryby w tle"> "Zdalne powiadomienia" == TAK, stuknij w powiadomienie, które pojawi się za:

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

Pomogło mi. Miłej zabawy :)

Aleks Osipov
źródło
Widziałem, że Apple to dodał, ale nie mógł dowiedzieć się, jak w ten sposób uzyskać niestandardowy ładunek powiadomienia.
sudo
Ta funkcja jest wywoływana, gdy aplikacja jest w tle i staje się aktywna, a także gdy aplikacja uruchamia się ze stanu
uśpienia
@NikhilMuskur tylko wtedy, gdy włączone są "zdalne powiadomienia", tak?
Zorayr
@Zorayr tak, zgadza się
Nikhil Muskur
4

W moim przypadku wyłączenie trybu tła nie miało żadnego znaczenia. Jednak gdy aplikacja została zawieszona, a użytkownik dotknął powiadomienia, mogłem obsłużyć ten przypadek w tej metodzie wywołania zwrotnego:

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

}
DoruChidean
źródło
To oficjalny sposób, w jaki mówi Apple. Zgodnie z dokumentacją Apple, będzie ona wywoływana za każdym razem, gdy użytkownik wejdzie w interakcję z interfejsem powiadomień push. Jeśli aplikacja nie działa w tle, uruchomi aplikację w tle i wywoła tę metodę.
Ryan
3

W przypadku iOS 10 i nowszych umieść to w AppDelegate, aby dowiedzieć się, że powiadomienie jest dotknięte (działa nawet, gdy aplikacja jest zamknięta lub otwarta)

func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}
Yatin Arora
źródło
1
To jest poprawna odpowiedź na iOS 10+. Pełne wyjaśnienie znajduje się w innym wątku tutaj: stackoverflow.com/a/41783666/1761357 .
dead_can_dance
2

Istnieją dwie funkcje dla uchwytu otrzymanego w PushNotificationManagerklasie PushNotification :

class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate{

}

Jak przetestowałem pierwszy wyzwalacz, gdy tylko nadeszło powiadomienie

@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([])

}

I drugi po dotknięciu powiadomienia:

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

Przetestowałem to również z włączonymi i wyłączonymi stanami zdalnego powiadamiania (w trybach w tle)

Saeid
źródło
1

SWIFT 5.1

UIApplication.State u mnie nie zadziałało, ponieważ po odczytaniu odcisku palca (pojawia się modal) w mojej aplikacji, powiadomienie jest również rzucane w górnym pasku i użytkownik musi go kliknąć.

Stworzyłem

public static var isNotificationFromApp: Bool = false

in AppDelegatei ustawiam to truena początku, viewControllera potem w powiadomieniu storyboard/viewController właśnie to sprawdzam :)

Mam nadzieję, że może się przydać

Klepsydra
źródło
-7

Możesz skonfigurować ładunek powiadomienia wypychanego, aby wywoływał delegata aplikacji application:didReceiveRemoteNotification:fetchCompletionHandler: metodę gdy aplikacja działa w tle. Możesz tutaj ustawić flagę, aby przy następnym uruchomieniu aplikacji przez użytkownika można było wykonać operację.

Z dokumentacji firmy Apple należy skorzystać z tej metody, aby pobrać nową zawartość związaną z powiadomieniem push. Aby to zadziałało, musisz włączyć zdalne powiadamianie z trybów w tle, a ładunek powiadomienia push musi zawierać content-availableklucz z wartością ustawioną na 1. Więcej informacji można znaleźć w sekcji Używanie powiadomień push do inicjowania pobierania z dokumentu Apple tutaj .

Innym sposobem jest liczenie znaczków w ładunku powiadomień push. Dlatego następnym razem, gdy aplikacja zostanie uruchomiona, możesz sprawdzić liczbę odznak aplikacji. Jeśli jego wartość jest większa od zera, wykonaj operację i zeruj / wyczyść liczbę identyfikatorów z serwera.

Mam nadzieję, że to ci pomoże.

Ameet Dhas
źródło
@bhusan czy przeczytałeś szczegóły mojego pytania? Widzę, że didReceiveRemoteNotification jest wywoływane po nadejściu powiadomienia (zanim użytkownik go dotknie). Chciałem się dowiedzieć, czy użytkownik go stuknął.
Bao Lei
Nie odpowiedziałeś na pytanie OP. Nie chce tylko wiedzieć, że nastąpiło powiadomienie. Chce wiedzieć, czy użytkownik otworzył aplikację, dotykając tego zdalnego powiadomienia.
Joe C