ios ios推送通知 - 檢測應用是否從推送通知啟動/打開




ios本地推播 ios前景推播 (19)

是否有可能知道應用程序是否從推送通知啟動/打開?

我想這個發布會可以在這裡抓到:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    if (launchOptions != nil) {
         // Launched from push notification
         NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

    }
}

但是,當應用程序在後台時,如何才能檢測到它是從推送通知中打開的?


Answers

直接從文檔中

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

如果應用程序正在運行並收到遠程通知,則應用程序會調用此方法來處理通知。

此方法的實施應使用通知來採取適當的行動。

稍後一點

如果在推送通知到達時應用程序未運行,該方法將啟動應用程序並在啟動選項字典中提供適當的信息。

該應用程序不會調用此方法來處理推送通知。

相反,你的實現

application:willFinishLaunchingWithOptions:

要么

application:didFinishLaunchingWithOptions:

方法需要獲取推送通知有效載荷數據並進行適當的響應。


這是一個很老的帖子......但它仍然缺少解決問題的實際辦法 (正如各種評論所指出的那樣)。

原始問題是關於通過推送通知來檢測應用何時啟動 / 打開例如用戶點擊通知。 沒有一個答案實際上涵蓋了這種情況。

原因可以在通知到達時在呼叫流程中看到, application:didReceiveRemoteNotification...

當通知被接收時被調用,並且當用戶點擊通知時再次被調用。 正因為如此,用戶只需查看UIApplicationState就可以知道用戶點擊了它。

此外,您不再需要處理應用程序在application:didFinishLaunchingWithOptions...中的“冷啟動”情況application:didFinishLaunchingWithOptions...作為application:didReceiveRemoteNotification...在iOS 9+(也可能是8)中啟動後再次調用。

那麼,你怎麼知道用戶點擊是否開始了事件鏈? 我的解決方案是標記應用程序開始走出背景或冷啟動的時間,然後在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)
    }
  }
}

斯威夫特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)
      }
    }
}

我已經在iOS 9+上對這兩種情況(應用程序在後台,應用程序未運行)進行了測試,並且它像魅力一樣起作用。 0.1s也相當保守,實際值為〜0.002s,所以0.01也很好。


For Swift Users:

If you want to launch a different page on opening from push or something like that, you need to check it in didFinishLaunchingWithOptions like:

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

為了迅速:

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

    }

}

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

我希望這有幫助。


當應用程序在後台作為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
    }
}

但是如果你想啟動應用程序,當應用程序關閉,你想調試你的應用程序,你可以去編輯方案,並在左側菜單中選擇運行 ,然後在啟動選擇等待可執行文件啟動 ,然後你應用程序啟動,當你點擊推送通知

編輯方案>運行>等待可執行文件啟動


我將首先創建一個狀態圖,供我自己使用,以更準確地形象化並考慮所有其他狀態: https://docs.google.com/spreadsheets/d/e/2PACX-1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml?gid=0&single=true : https://docs.google.com/spreadsheets/d/e/2PACX-1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml?gid=0&single=true

使用這張圖表,我們可以看到實際需要什麼來開發一個強大的通知處理系統,該系統幾乎適用於所有可能的用例。

完整的解決方案↓

  • 通知有效內容存儲在didReceiveRemoteNotification中
  • applicationWillEnterForegrounddidFinishLaunchingWithOptions中 清除存儲的通知
  • 要處理控制中心/通知中心拉動的情況,可以使用willResignActiveCalled標誌並將其初始設置為false ,在applicationWillResignActive方法中將其設置為true
  • didReceiveRemoteNotification方法中,僅當willResignActiveCalled為false時才保存通知(userInfo)。
  • applicationDidEnterBackgroundapplicationDidBecomeActive方法中將willResignActiveCalled重置為false

注意:在Eric的回答的評論中提出了類似的答案,但是,狀態表有助於查找所有可能的場景,就像我在我的應用程序中所做的那樣。

如果沒有處理任何特定案例,請在下面找到完整的代碼並在下面評論:

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

我們遇到的問題是在應用程序啟動後正確更新視圖。 生命週期方法有復雜的序列,這會讓人困惑。

生命週期方法

我們針對iOS 10的測試揭示了各種情況下的以下生命週期方法序列:

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. 清除狀態,以便後續的打開不會將用戶返回到相同的位置。

棘手的問題是,當應用程序實際上變為活動時,更新視圖必鬚髮生,這在所有情況下都是相同的生命週期方法。

我們解決方案的草圖

以下是我們解決方案的主要組成部分:

  1. 在AppDelegate上存儲notificationUserInfo實例變量。
  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拾取以將用戶轉發到正確的視圖。

最後一種情況是,如果用戶在應用程序切換器中打開了應用程序(即在應用程序處於前台時雙擊主頁按鈕),然後收到推送通知。 在這種情況下,只調用didReceiveRemoteNotification didReceiveRemoteNotification:inactive ,既不會調用WillEnterForeground也不會調用didFinishLaunching,所以您需要一些特殊的狀態來處理這種情況。

希望這可以幫助。


這個問題的問題是“打開”應用程序的定義不明確。 應用程序要么從非運行狀態冷啟動,要么從非活動狀態重新激活(例如從另一個應用程序切換回它)。 這是我的解決方案來區分所有這些可能的狀態:

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的一個小包裝。


請參閱此代碼:

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

當應用終止時,用戶點擊推送通知

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 ,所以請注意這一點:)請參閱https://.com/a/33778990/1418457


晚但可能有用

當應用程序未運行時

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

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

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

是的,你可以通過appDelegate中的這個方法來檢測:

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

對於本地通知:

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

您可以使用:

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

處理遠程推送通知。

在這裡查看documentation


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

    }
}

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

您可以配置推送通知的有效內容以調用應用程序委託的application:didReceiveRemoteNotification:fetchCompletionHandler:當應用程序處於後台時的方法。 您可以在此處設置一些標誌,以便下次用戶啟動應用程序時,您可以執行操作。

從蘋果的文檔中,您應該使用此方法下載與推送通知關聯的新內容。 此外,為了使其工作,您必須從後台模式啟用遠程通知,並且您的推送通知有效content-available必須包含content-available鍵,並將其值設置為1.更多信息,請參閱使用推送通知從蘋果doc啟動下載部分。

另一種方法是在推送通知有效負載中使用徽章計數。 因此,下次啟動應用程序時,您可以檢查應用程序徽章數。 如果其大於零,則還要執行操作並從服務器清除零/清除徽章計數。

希望這可以幫助你。





ios push-notification ios6 apple-push-notifications uiapplicationdelegate