Est-il possible de savoir si l'application a été lancée/ouverte à partir d'une notification Push?
Je suppose que l'événement de lancement peut être capturé ici:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (launchOptions != nil) {
// Launched from Push notification
NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
}
}
Cependant, comment puis-je détecter l'ouverture d'une notification Push lorsque l'application était en arrière-plan?
Voir ce code:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground )
{
//opened from a Push notification when the app was on background
}
}
pareil que
-(void)application:(UIApplication *)application didReceiveLocalNotification (UILocalNotification *)notification
en retard mais peut-être utile
Lorsque l'application ne fonctionne pas
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
est appelé ..
où vous devez vérifier la notification Push
NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (notification) {
NSLog(@"app recieved notification from remote%@",notification);
[self application:application didReceiveRemoteNotification:notification];
} else {
NSLog(@"app did not recieve notification");
}
Le problème que nous avons eu était de mettre à jour correctement la vue après le lancement de l'application. Il existe ici des séquences compliquées de méthodes de cycle de vie qui prêtent à confusion.
Méthodes du cycle de vie
Nos tests pour iOS 10 ont révélé les séquences suivantes de méthodes de cycle de vie pour les différents cas:
DELEGATE METHODS CALLED WHEN OPENING APP
Opening app when system killed or user killed
didFinishLaunchingWithOptions
applicationDidBecomeActive
Opening app when backgrounded
applicationWillEnterForeground
applicationDidBecomeActive
DELEGATE METHODS WHEN OPENING Push
Opening Push when system killed
[receiving Push causes didFinishLaunchingWithOptions (with options) and didReceiveRemoteNotification:background]
applicationWillEnterForeground
didReceiveRemoteNotification:inactive
applicationDidBecomeActive
Opening Push when user killed
didFinishLaunchingWithOptions (with options)
didReceiveRemoteNotification:inactive [only completionHandler version]
applicationDidBecomeActive
Opening Push when backgrounded
[receiving Push causes didReceiveRemoteNotification:background]
applicationWillEnterForeground
didReceiveRemoteNotification:inactive
applicationDidBecomeActive
Le problème
Ok, nous devons maintenant:
La difficulté réside dans le fait que la mise à jour de la vue doit avoir lieu lorsque l'application devient réellement active, ce qui correspond à la même méthode de cycle de vie dans tous les cas.
Esquisse de notre solution
Voici les principaux composants de notre solution:
notificationUserInfo
sur AppDelegate.notificationUserInfo = nil
dans applicationWillEnterForeground
et didFinishLaunchingWithOptions
.notificationUserInfo = userInfo
dans didReceiveRemoteNotification:inactive
applicationDidBecomeActive
, appelez toujours une méthode personnalisée openViewFromNotification
et transmettez self.notificationUserInfo
. Si self.notificationUserInfo
est nil, retournez tôt, sinon ouvrez la vue en fonction de l'état de notification trouvé dans self.notificationUserInfo
.Explication
Lorsque vous ouvrez une variable Push didFinishLaunchingWithOptions
ou applicationWillEnterForeground
est toujours appelée juste avant didReceiveRemoteNotification:inactive
, nous réinitialisons d’abord notificationUserInfo dans ces méthodes afin qu’il n’y ait pas d’état obsolète. Ensuite, si didReceiveRemoteNotification:inactive
est appelé, nous savons que nous ouvrons à partir d’un Push. Nous définissons donc self.notificationUserInfo
, qui est ensuite récupéré par applicationDidBecomeActive
pour transférer l’utilisateur à la vue de droite.
Il y a un dernier cas, c'est-à-dire si l'utilisateur a ouvert l'application dans le sélecteur d'applications (c'est-à-dire en appuyant deux fois sur le bouton d'accueil alors que l'application est au premier plan), puis reçoit une notification Push. Dans ce cas, seul didReceiveRemoteNotification:inactive
est appelé, et ni WillEnterForeground, ni didFinishLaunching ne sont appelés, vous avez donc besoin d'un état spécial pour gérer ce cas.
J'espère que cela t'aides.
Ceci est un message bien porté ... mais il manque toujours une solution} au problème (comme indiqué dans les différents commentaires).
La question initiale concerne la détection du moment où l'application était lancée.____./ouvert à partir d'une notification Push, p. un utilisateur appuie sur le notification. Aucune des réponses ne couvre réellement ce cas.
La raison peut être vue dans le flux d'appels quand une notification arrive, application:didReceiveRemoteNotification...
est appelé lorsque la notification est reçue ET à nouveau lorsque la notification est exploitée par l'utilisateur. Pour cette raison, vous ne pouvez pas savoir en regardant simplement UIApplicationState
si l'utilisateur a tapé dessus.
De plus, vous n'avez plus besoin de gérer la situation de «démarrage à froid» de l'application dans application:didFinishLaunchingWithOptions...
car application:didReceiveRemoteNotification...
est appelé à nouveau après le lancement dans iOS 9+ (peut-être aussi 8).
Alors, comment pouvez-vous savoir si l'utilisateur appuie sur le début de la chaîne d'événements? Ma solution consiste à marquer l'heure à laquelle l'application commence à sortir de l'arrière-plan ou du démarrage à froid, puis à vérifier l'heure dans application:didReceiveRemoteNotification...
. Si elle est inférieure à 0,1 s, vous pouvez être certain que le robinet a déclenché le démarrage.
_ {Swift 2.x} _
class AppDelegate: UIResponder, UIApplicationDelegate {
var wakeTime : NSDate = NSDate() // when did our application wake up most recently?
func applicationWillEnterForeground(application: UIApplication) {
// time stamp the entering of foreground so we can tell how we got here
wakeTime = NSDate()
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// ensure the userInfo dictionary has the data you expect
if let type = userInfo["type"] as? String where type == "status" {
// IF the wakeTime is less than 1/10 of a second, then we got here by tapping a notification
if application.applicationState != UIApplicationState.Background && NSDate().timeIntervalSinceDate(wakeTime) < 0.1 {
// User Tap on notification Started the App
}
else {
// DO stuff here if you ONLY want it to happen when the Push arrives
}
completionHandler(.NewData)
}
else {
completionHandler(.NoData)
}
}
}
_ {Swift 3} _
class AppDelegate: UIResponder, UIApplicationDelegate {
var wakeTime : Date = Date() // when did our application wake up most recently?
func applicationWillEnterForeground(_ application: UIApplication) {
// time stamp the entering of foreground so we can tell how we got here
wakeTime = Date()
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// ensure the userInfo dictionary has the data you expect
if let type = userInfo["type"] as? String, type == "status" {
// IF the wakeTime is less than 1/10 of a second, then we got here by tapping a notification
if application.applicationState != UIApplicationState.background && Date().timeIntervalSince(wakeTime) < 0.1 {
// User Tap on notification Started the App
}
else {
// DO stuff here if you ONLY want it to happen when the Push arrives
}
completionHandler(.newData)
}
else {
completionHandler(.noData)
}
}
}
J'ai testé cela pour les deux cas (application en arrière-plan, application non en cours d'exécution) sur iOS 9+ et cela fonctionne à merveille. 0.1s est assez conservateur aussi, la valeur réelle est ~ 0.002s, donc 0.01 convient également.
Swift 2.0 pour l'état 'Not running' (Notification locale et distante)
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Handle notification
if (launchOptions != nil) {
// For local Notification
if let localNotificationInfo = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification {
if let something = localNotificationInfo.userInfo!["yourKey"] as? String {
self.window!.rootViewController = UINavigationController(rootViewController: YourController(yourMember: something))
}
} else
// For remote Notification
if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as! [NSObject : AnyObject]? {
if let something = remoteNotification["yourKey"] as? String {
self.window!.rootViewController = UINavigationController(rootViewController: YourController(yourMember: something))
}
}
}
return true
}
Dans application:didReceiveRemoteNotification:
, vérifiez si vous avez reçu la notification lorsque votre application est au premier plan ou à l'arrière-plan.
S'il a été reçu en arrière-plan, lancez l'application à partir de la notification.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
NSLog(@"Notification received by running app");
} else {
NSLog(@"App opened from Notification");
}
}
Lorsque l'application est terminée et que l'utilisateur appuie sur la notification Push
public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] != nil {
print("from Push")
}
}
Lorsque l'application est en arrière-plan et que l'utilisateur appuie sur la notification Push
Si l'utilisateur ouvre votre application à partir de l'alerte affichée par le système, le système peut rappeler cette méthode lorsque votre application est sur le point d'entrer au premier plan afin que vous puissiez mettre à jour votre interface utilisateur et afficher les informations relatives à la notification.
public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .Inactive {
print("from Push")
}
}
En fonction de votre application, il peut également vous envoyer un Push silencieux avec content-available
dans aps
, soyez-en conscient également :) Voir https://stackoverflow.com/a/33778990/1418457
Pour Swift:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
PFPush.handlePush(userInfo)
if application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background {
//opened from a Push notification when the app was on background
}
}
Oui, vous pouvez détecter par cette méthode dans appDelegate :
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
/* your Code*/
}
Pour notification locale:
- (void)application:(UIApplication *)application
didReceiveLocalNotification:(UILocalNotification *)notification
{
/* your Code*/
}
si quelqu'un veut la réponse dans Swift 3
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
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
}
}
Poster ceci pour les utilisateurs de Xamarin.
La clé pour détecter si l'application a été lancée via une notification Push est la méthode AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options)
et le dictionnaire d'options transmis.
Le dictionnaire d'options contiendra cette clé s'il s'agit d'une notification locale: UIApplication.LaunchOptionsLocalNotificationKey
.
Si c'est une notification à distance, ce sera UIApplication.LaunchOptionsRemoteNotificationKey
.
Lorsque la clé est LaunchOptionsLocalNotificationKey
, l'objet est de type UILocalNotification
. Vous pouvez ensuite consulter la notification et déterminer de quelle notification il s'agit.
Conseil: UILocalNotification
n’a pas d’identifiant, de la même manière que UNNotificationRequest
. Placez une clé de dictionnaire dans UserInfo contenant un requestId afin que, lors du test de UILocalNotification
, vous disposiez d'un requestId spécifique sur lequel baser une certaine logique.
J'ai trouvé que même sur les appareils iOS 10+, lors de la création de notifications d'emplacement à l'aide de UNUserNotificationCenter
& AddNotificationRequest
de UNMutableNotificationContent
, que lorsque l'application n'est pas en cours d'exécution (je l'ai tuée), contient l'objet UILocalNotificaiton
.
Cela signifie que mon code vérifiant le lancement basé sur les notifications fonctionnera sur les appareils iOS8 et iOS 10+.
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
_logger.InfoFormat("FinishedLaunching");
if(options != null)
{
if (options.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey))
{
//was started by tapping a local notification when app wasn't previously running.
//works if using UNUserNotificationCenter.Current.AddNotificationRequest OR UIApplication.SharedApplication.PresentLocalNotificationNow);
var localNotification = options[UIApplication.LaunchOptionsLocalNotificationKey] as UILocalNotification;
//I would recommended a key such as this :
var requestId = localNotification.UserInfo["RequestId"].ToString();
}
}
return true;
}
Il n'y a qu'un seul moyen fiable, et il ne fonctionne que pour iOS 10+ :
Utilisation de la méthode UNUserNotificationCenter
Implement UNUserNotificationCenterDelegate
:
- (void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
//Here you can get your original Push if you need to
NSDictionary* pusDict = response.notification.request.content.userInfo;
if ([response.actionIdentifier isEqualToString: UNNotificationDefaultActionIdentifier]) {
//User tapped the notification
} else if ([response.actionIdentifier isEqualToString: UNNotificationDismissActionIdentifier]) {
//User dismissed the notification
} else if ([response.actionIdentifier isEqualToString: MYCustomActionId]) {
//User chose my custom defined action
}
...
}
Je vais commencer par un tableau d’états que j’ai créé pour mon propre usage afin de le visualiser plus précisément et de prendre en compte tous les autres états: https://docs.google.com/spreadsheets/d/e/2PACX- 1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7g31IFhD-L/pubhtml? Gid = 0 & single = true
En utilisant ce tableau, nous pouvons voir ce qui est réellement nécessaire pour développer un système de traitement des notifications robuste qui fonctionne dans presque tous les cas d'utilisation possibles.
Solution complète ↓
Remarque: Une réponse similaire est suggérée dans les commentaires sur la réponse d'Eric. Toutefois, la feuille de contrôle aide à trouver tous les scénarios possibles, comme je l'ai fait dans mon application.
Veuillez trouver le code complet ci-dessous et commenter ci-dessous si aucun cas spécifique n’est traité:
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
private var willResignActiveCalled = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
NotificationUtils.shared.notification = nil
return true
}
func applicationWillResignActive(_ application: UIApplication) {
willResignActiveCalled = true
}
func applicationDidEnterBackground(_ application: UIApplication) {
willResignActiveCalled = false
}
func applicationWillEnterForeground(_ application: UIApplication) {
NotificationUtils.shared.notification = nil
}
func applicationDidBecomeActive(_ application: UIApplication) {
willResignActiveCalled = false
NotificationUtils.shared.performActionOnNotification()
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if !willResignActiveCalled { // Check if app is in inactive by app switcher, control center, or notification center
NotificationUtils.shared.handleNotification(userInfo: userInfo)
}
}
}
NotificationUtils: C’est ici que vous pouvez écrire tout votre code pour naviguer dans les différentes parties de l’application, gérer les bases de données (CoreData/Realm) et effectuer tous les autres travaux à effectuer lors de la réception d’une notification.
class NotificationUtils {
static let shared = NotificationUtils()
private init() {}
var notification : [AnyHashable: Any]?
func handleNotification(userInfo : [AnyHashable: Any]){
if UIApplication.shared.applicationState == UIApplicationState.active {
self.notification = userInfo //Save Payload
//Show inApp Alert/Banner/Action etc
// perform immediate action on notification
}
else if UIApplication.shared.applicationState == UIApplicationState.inactive{
self.notification = userInfo
}
else if UIApplication.shared.applicationState == UIApplicationState.background{
//Process notification in background,
// Update badges, save some data received from notification payload in Databases (CoreData/Realm)
}
}
func performActionOnNotification(){
// Do all the stuffs like navigating to ViewControllers, updating Badges etc
defer {
notification = nil
}
}
}
Pour Swift
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]){
++notificationNumber
application.applicationIconBadgeNumber = notificationNumber;
if let aps = userInfo["aps"] as? NSDictionary {
var message = aps["alert"]
println("my messages : \(message)")
}
}
// shanegao's code in Swift 2.0
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject])
{
if ( application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background ){
print("opened from a Push notification when the app was on background")
}else{
print("opened from a Push notification when the app was on foreground")
}
}
Vous pouvez utiliser:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
pour gérer les notifications Push à distance.
Vérifiez ici la documentation
Lorsque l'application est en arrière-plan en tant que shanegao vous pouvez utiliser
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground )
{
//opened from a Push notification when the app was on background
}
}
Mais si vous voulez lancer l'application et lorsque l'application est fermée et que vous voulez déboguer votre application, vous pouvez aller à Edit Scheme et dans le menu de gauche, sélectionnez Run puis au lancement, sélectionnez Attendez l'exécutable à lancer et ensuite vous lancez l'application lorsque vous cliquez sur Notification push
Modifier le schéma> Exécuter> Attendre que le fichier exécutable soit lancé
Xcode 10 Swift 4.2
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let state : UIApplicationState = application.applicationState
if (state == .Inactive || state == .Background) {
// coming from background
} else {
// App is running in foreground
}
}
Le problème avec cette question est que "l'ouverture" de l'application n'est pas bien définie. Une application est lancée à froid à partir d'un état inactif ou réactivée à partir d'un état inactif (par exemple, en revenant à cette application depuis une autre application). Voici ma solution pour distinguer tous ces états possibles:
typedef NS_ENUM(NSInteger, MXAppState) {
MXAppStateActive = 0,
MXAppStateReactivated = 1,
MXAppStateLaunched = 2
};
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ... your custom launch stuff
[[MXDefaults instance] setDateOfLastLaunch:[NSDate date]];
// ... more custom launch stuff
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// Through a lot of trial and error (by showing alerts), I can confirm that on iOS 10
// this method is only called when the app has been launched from a Push notification
// or when the app is already in the Active state. When you receive a Push
// and then launch the app from the icon or apps view, this method is _not_ called.
// So with 99% confidence, it means this method is called in one of the 3 mutually exclusive cases
// 1) we are active in the foreground, no action was taken by the user
// 2) we were 'launched' from an inactive state (so we may already be in the main section) by a tap
// on a Push notification
// 3) we were truly launched from a not running state by a tap on a Push notification
// Beware that cases (2) and (3) may both show UIApplicationStateInactive and cant be easily distinguished.
// We check the last launch date to distinguish (2) and (3).
MXAppState appState = [self mxAppStateFromApplicationState:[application applicationState]];
//... your app's logic
}
- (MXAppState)mxAppStateFromApplicationState:(UIApplicationState)state {
if (state == UIApplicationStateActive) {
return MXAppStateActive;
} else {
NSDate* lastLaunchDate = [[MXDefaults instance] dateOfLastLaunch];
if (lastLaunchDate && [[NSDate date] timeIntervalSinceDate:lastLaunchDate] < 0.5f) {
return MXAppStateLaunched;
} else {
return MXAppStateReactivated;
}
}
return MXAppStateActive;
}
Et MXDefaults
est juste un petit wrapper pour NSUserDefaults
.
func application(_ application: UIApplication, didReceiveRemoteNotification data: [AnyHashable : Any]) {
print("Push notification received: \(data)")
if let info = data["aps"] as? Dictionary<String, AnyObject> {
let alertMsg = info["alert"] as! String
print(alertMsg)
switch application.applicationState {
case .active:
print("do stuff in case App is active")
case .background:
print("do stuff in case App is in background")
// navigateToChatDetailViewControler(pushdata: data)
case .inactive:
print("do stuff in case App is inactive")
// navigateToChatDetailViewControler(pushdata: data)
}
}
}
Je n'ai pas encore essayé, mais vous pourriez peut-être vous envoyer une notification? http://nshipster.com/nsnotification-and-nsnotificationcenter/
Tout droit de la documentation pour
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo:nil
Si l'application est en cours d'exécution et reçoit une notification à distance, l'application appelle cette méthode pour traiter la notification.
Votre implémentation de cette méthode doit utiliser la notification pour prendre les mesures appropriées.
Et un peu plus tard
Si l'application ne fonctionne pas lorsqu'une notification Push arrive, la méthode démarre l'application et fournit les informations appropriées dans le dictionnaire des options de lancement.
L'application n'appelle pas cette méthode pour gérer cette notification Push.
Au lieu de cela, votre mise en œuvre de la
application:willFinishLaunchingWithOptions:
ou
application:didFinishLaunchingWithOptions:
méthode doit obtenir les données utiles de notification Push et répondre de manière appropriée.