检测应用程序是否从推送通知启动/打开

是否有可能知道应用程序是否从推送通知启动/打开?

我猜这个启动事件可以在这里抓到:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if (launchOptions != nil) { // Launched from push notification NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; } } 

但是,当应用程序在后台时,如何才能检测到它是从推送通知中打开的?

请参阅此代码:

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

与…一样

 -(void)application:(UIApplication *)application didReceiveLocalNotification (UILocalNotification *)notification 

晚但可能有用

当应用程序不运行

  • (BOOL)应用程序:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

叫做 ..

你需要检查推送通知

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

这是一个老旧的post,但它仍然缺less一个实际的解决scheme (正如在各种评论中指出的那样)。

原始问题是关于通过推送通知来检测应用何时启动 / 打开例如用户点击通知。 没有一个答案实际上涵盖了这种情况。

原因可以在通知stream程中看到通知到达时, application:didReceiveRemoteNotification...

当通知被接收时被调用,并且当用户点击通知时被再次调用。 正因为如此,用户只需查看UIApplicationState就可以知道用户点击了它。

此外,您不再需要处理应用application:didFinishLaunchingWithOptions...的“冷启动”情况application:didFinishLaunchingWithOptions...作为application:didReceiveRemoteNotification...在iOS 9+(可能也是8)启动后再次被调用。

那么,你怎么知道用户是否开始了事件链? 我的解决scheme是标记应用程序开始从背景或冷启动的时间,然后检查application:didReceiveRemoteNotification...中的时间application:didReceiveRemoteNotification... 如果小于0.1s,那么你可以很确定龙头触发了启动。

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

我已经testing了这两种情况(应用程序在背景中,应用程序不运行)在iOS 9 +,它的作品就像一个魅力。 0.1s也相当保守,实际值为〜0.002s,所以0.01也很好。

Swift 2.0对于“未运行”状态(本地和远程通知)

 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 

}

application:didReceiveRemoteNotification:检查您的应用程序在前台或后台时是否收到通知。

如果在后台收到,则从通知中启动应用程序。

 -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { NSLog(@"Notification received by running app"); } else { NSLog(@"App opened from Notification"); } } 

我们遇到的问题是在应用程序启动后正确更新视图。 这里有复杂的生命周期方法序列令人困惑。

生命周期方法

我们针对iOS 10的testing揭示了以下针对各种情况的生命周期方法序列:

 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 

问题

好的,现在我们需要:

  1. 确定用户是否从推送中打开应用程序
  2. 根据推送状态更新视图
  3. 清除状态,以便后续打开不会将用户返回到相同的位置。

棘手的是,当应用程序实际上变为活动状态时,更新视图必须发生,在所有情况下这都是相同的生命周期方法。

我们解决scheme的草图

以下是我们解决scheme的主要组成部分:

  1. 在AppDelegate上存储一个notificationUserInfo实例variables。
  2. applicationWillEnterForegrounddidFinishLaunchingWithOptions设置notificationUserInfo = nil
  3. didReceiveRemoteNotification:inactive设置notificationUserInfo = userInfo didReceiveRemoteNotification:inactive
  4. applicationDidBecomeActive始终调用自定义方法openViewFromNotification并传递self.notificationUserInfo 。 如果self.notificationUserInfo为零,则提前返回,否则根据self.notificationUserInfo的通知状态打开视图。

说明

当从推式打开时, didFinishLaunchingWithOptionsapplicationWillEnterForeground总是在didReceiveRemoteNotification:inactive之前立即调用didReceiveRemoteNotification:inactive ,所以我们首先在这些方法中重置notificationUserInfo,所以没有陈旧的状态。 然后,如果didReceiveRemoteNotification:inactive被调用,我们知道我们是从一个推动打开,所以我们设置self.notificationUserInfo ,然后由applicationDidBecomeActive拾起将用户转发到正确的视图。

最后一种情况是,如果用户在应用程序切换器中打开了应用程序(即在应用程序处于前台时双击主页button),然后接收到推送通知。 在这种情况下,只有didReceiveRemoteNotification:inactive被调用,WillEnterForeground和didFinishLaunching都不会被调用,所以你需要一些特殊的状态来处理这种情况。

希望这可以帮助。

为了迅速:

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

当应用程序被终止,用户点击推送通知

 public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { if launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] != nil { print("from push") } } 

当应用程序在后台,用户点击推送通知

如果用户从系统显示的警报中打开您的应用程序,系统可能会在您的应用程序即将进入前台时再次调用此方法,以便您可以更新用户界面并显示与通知相关的信息。

 public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { if application.applicationState == .Inactive { print("from push") } } 

根据您的应用程序,它也可以发送给您无声的推动与content-available内部的aps ,所以要注意这一点:)以及https://stackoverflow.com/a/33778990/1418457

是的,你可以通过这个方法在appDelegate中检测:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { /* your Code*/ } 

对于本地通知:

 - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { /* your Code*/ } 

如果有人想快速回答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 } } 

直接从文件

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo:nil 

如果应用程序正在运行并收到远程通知,则应用程序会调用此方法来处理通知。

此方法的实施应使用通知来采取适当的行动。

稍后一点

如果在推送通知到达时应用程序没有运行,该方法将启动应用程序并在启动选项字典中提供适当的信息。

该应用程序不会调用此方法来处理推送通知。

相反,你的执行

 application:willFinishLaunchingWithOptions: 

要么

 application:didFinishLaunchingWithOptions: 

方法需要获取推送通知有效载荷数据并作出适当的响应。

您可以使用:

 -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 

处理远程推送通知。

在这里检查文档

我还没有尝试过,但也许你可以发送自己的通知? http://nshipster.com/nsnotification-and-nsnotificationcenter/

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

这个问题的问题是“打开”应用程序没有明确定义。 应用程序要么从非运行状态冷启动,要么从非活动状态重新激活(例如,从另一个应用程序切换回它)。 这是我的解决scheme来区分所有这些可能的状态:

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

MXDefaults只是MXDefaults的一个小包装。

当应用程序在后台shanegao可以使用

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

但是,如果你想启动应用程序,当应用程序closures,你想debugging你的应用程序,你可以去编辑计划,并在左侧菜单中select运行 ,然后在启动select等待可执行文件启动 ,然后你的应用程序启动,当你点击推送通知

编辑scheme>运行>等待可执行文件启动

对于Swift用户:

如果你想从push或类似的东西上打开不同的页面,你需要在didFinishLaunchingWithOptions检查它,例如:

 let directVc: directVC! = directVC(nibName:"directVC", bundle: nil) let pushVc: pushVC! = pushVC(nibName:"pushVC", bundle: nil) if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary { self.navigationController = UINavigationController(rootViewController: pushVc!) } else { self.navigationController = UINavigationController(rootViewController: directVc!) } self.window!.rootViewController = self.navigationController 

在SWIFT:

我正在运行推送通知(带有后台提取)。 当我的应用程序在后台,我收到推送通知时,我发现appDelegate中的didReceiveRemoteNotification将被调用两次; 一次用于收到通知时,另一次用户点击通知提醒。

要检测是否单击通知警报,只需检查appDelegate中的applicationState原始值== 1内的didReceiveRemoteNotification。

 func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject]) { // If not from alert click applicationState(1) if (application.applicationState.rawValue != 1) { // Run your code here } } 

我希望这有帮助。

为了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)") } } 

我将从我自己创build的状态图开始,更准确地将其形象化,并考虑所有其他状态: https : //docs.google.com/spreadsheets/d/1pIWIUcBxlS8ipCrXufxSmaCPZjnPZ66IQb1D7TsskSQ/edit?usp=sharing

使用这个图表,我们可以看到实际需要什么来开发一个强大的通知处理系统,在几乎所有可能的用例中都可以使用。

完整的解决scheme↓

  • 通知有效内容存储在didReceiveRemoteNotification中
  • applicationWillEnterForegrounddidFinishLaunchingWithOptions中 清除存储的通知
  • 要处理控制中心/通知中心拉的情况,可以使用标志willResignActiveCalled并将其初始设置为false ,在applicationWillResignActive方法中将其设置为true
  • didReceiveRemoteNotification方法中,仅当willResignActiveCalled为false时才保存通知(userInfo)。
  • applicationDidEnterBackgroundapplicationDidBecomeActive方法中重置willResignActiveCalled为false

注意:在Eric的回答中提出了类似的回答,但是,状态表有助于find所有可能的情况,就像我在应用程序中所做的那样。

如果没有处理任何特定情况,请在下面find完整的代码并在下面评论:

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 :这是您可以编写所有脏代码来导航到应用程序的不同部分,处理数据库(CoreData / Realm)以及在收到通知时执行所有其他需要完成的任务的地方。

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