web-dev-qa-db-ja.com

アプリがプッシュ通知から起動/開かれたかどうかを検出する

アプリがプッシュ通知から起動/起動されたかどうかを知ることは可能ですか?

起動イベントはここでキャッチできます。

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

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

    }
}

ただし、アプリがバックグラウンドにあるときにプッシュ通知から開かれたことを検出するにはどうすればよいですか?

152
joao

このコードを参照してください:

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

遅くても便利かもしれない

アプリが実行されていないとき

- (BOOL)application:(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");
}
124
M.Othman

問題は、アプリの起動後にビューを正しく更新することでした。混乱を招くライフサイクルメソッドの複雑なシーケンスがここにあります。

ライフサイクルメソッド

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. notificationUserInfoインスタンス変数をAppDelegateに保存します。
  2. applicationWillEnterForegrounddidFinishLaunchingWithOptionsの両方にnotificationUserInfo = nilを設定します。
  3. notificationUserInfo = userInfodidReceiveRemoteNotification:inactiveを設定します
  4. applicationDidBecomeActiveからは常にカスタムメソッドopenViewFromNotificationを呼び出し、self.notificationUserInfoを渡します。 self.notificationUserInfoがnilの場合は早く戻り、そうでない場合はself.notificationUserInfoで見つかった通知状態に基づいてビューを開きます。

説明

プッシュから開く場合、didFinishLaunchingWithOptionsまたはapplicationWillEnterForegroundは常にdidReceiveRemoteNotification:inactiveの直前に呼び出されるため、これらのメソッドで最初にnotificationUserInfoをリセットして、古い状態がないようにします。次に、didReceiveRemoteNotification:inactiveが呼び出されると、プッシュから開いていることがわかるので、self.notificationUserInfoを設定し、applicationDidBecomeActiveによってピックアップされて、ユーザーを正しいビューに転送します。

最後の1つのケースとして、ユーザーがアプリスイッチャー内でアプリを開いた場合(つまり、アプリがフォアグラウンドにあるときにホームボタンをダブルタップした場合)、プッシュ通知を受信します。この場合はdidReceiveRemoteNotification:inactiveのみが呼び出され、WillEnterForegroundもdidFinishLaunchingも呼び出されないため、そのケースを処理するには特別な状態が必要です。

お役に立てれば。

27
Eric Conner

これは使い古された投稿です...しかし、実際の解決策がまだ問題に欠けています(さまざまなコメントで指摘されているように)。

元の質問は、アプリがプッシュ通知から起動/開いたであったことを検出することです。egユーザーが通知をタップします。実際、このケースをカバーする回答はありません。

理由は、通知が到着したときの呼び出しフロー、application:didReceiveRemoteNotification...で確認できます。

通知が受信されたときに呼び出されますAND通知がユーザーによってタップされたときに再び。このため、ユーザーがUIApplicationStateをタップしたかどうかだけではわかりません。

さらに、application:didFinishLaunchingWithOptions...がiOS 9+(おそらく8)での起動後に再度呼び出されるため、application:didReceiveRemoteNotification...でアプリの「コールドスタート」の状況を処理する必要がなくなりました。

では、ユーザータップが一連のイベントを開始したかどうかをどのように確認できますか?私の解決策は、アプリがバックグラウンドまたはコールドスタートから抜け出す時間をマークし、application:didReceiveRemoteNotification...でその時間をチェックすることです。 0.1秒未満の場合、タップが起動をトリガーしたことを確認できます。

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

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.1秒もかなり控えめで、実際の値は〜0.002秒なので、0.01でも問題ありません。

23
MobileVet

「実行されていない」状態の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

}

18

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

アプリが終了し、ユーザーがプッシュ通知をタップしたとき

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

アプリによっては、aps内のcontent-availableを使用してサイレントプッシュを送信することもできるため、これにも注意してください:) https://stackoverflow.com/a/33778990/1418457

15
onmyway133

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

    }

}
12
LondonGuy

はい、このメソッドでappDelegateで検出できます:

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

ローカル通知の場合:

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

これをXamarinユーザーに投稿します。

アプリがプッシュ通知を介して起動されたかどうかを検出する鍵は、AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options)メソッドと渡されるオプション辞書です。

オプション通知は、ローカル通知の場合、UIApplication.LaunchOptionsLocalNotificationKeyというキーを持ちます。

リモート通知の場合、UIApplication.LaunchOptionsRemoteNotificationKeyになります。

キーがLaunchOptionsLocalNotificationKeyの場合、オブジェクトはUILocalNotification型です。その後、通知を見て、どの特定の通知であるかを判断できます。

プロのヒント:UILocalNotificationには識別子がありません。UNNotificationRequestには識別子があります。 requestIdを含むUserInfoにディクショナリキーを配置して、UILocalNotificationをテストするときに、特定のロジックをベースにする特定のrequestIdを使用できるようにします。

IOS 10以降のデバイスでも、UNUserNotificationCenterAddNotificationRequestUNMutableNotificationContentを使用して位置通知を作成するとき、アプリが実行されていないとき(強制終了)、通知センターで通知をタップすることで起動されること、辞書がまだあることがわかりましたUILocalNotificaitonオブジェクトが含まれます。

これは、通知ベースの起動を確認するコードがiOS8および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;
}
3
Wes

誰かが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
    }
}
2

私が自分で使用するために作成した状態図から始めて、より正確に視覚化し、他のすべての状態を検討します: https://docs.google.com/spreadsheets/d/e/ 2PACX-1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml?gid = 0&single = true

このチャートを使用すると、考えられるほぼすべてのユースケースで機能する堅牢な通知処理システムを開発するために実際に必要なものを確認できます。

完全なソリューション↓

  • notificationペイロードをdidReceiveRemoteNotificationに保存します
  • Clearに保存された通知applicationWillEnterForegroundおよびdidFinishLaunchingWithOptions
  • コントロールセンター/通知センターがプルされた場合に対処するには、フラグwillResignActiveCalledを使用し、最初にfalseに設定し、これをtrueに設定しますapplicationWillResignActiveメソッド、
  • didReceiveRemoteNotificationメソッドでは、willResignActiveCalledがfalseの場合にのみ通知(userInfo)を保存します。
  • リセットwillResignActiveCalled to false in applicationDidEnterBackgroundおよびapplicationDidBecomeActiveメソッド。

注:同様の回答が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
    }
  }
}
2
chetan anand

唯一の信頼できる方法があり、それはiOS 10 +に対してのみ機能します:

UNUserNotificationCenterを使用して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
    }
    ...
}
2
Luten

のドキュメントから直接

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

アプリが実行中で、リモート通知を受信した場合、アプリはこのメソッドを呼び出して通知を処理します。

このメソッドの実装では、通知を使用して適切な一連のアクションを実行する必要があります。

そして少し後で

プッシュ通知の到着時にアプリが実行されていない場合、メソッドはアプリを起動し、起動オプション辞書に適切な情報を提供します。

アプリは、そのプッシュ通知を処理するためにこのメソッドを呼び出しません。

代わりに、

application:willFinishLaunchingWithOptions:

または

application:didFinishLaunchingWithOptions:

メソッドは、プッシュ通知ペイロードデータを取得し、適切に応答する必要があります。

1
Pfitz

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

    }
}
0
idris yıldız
     // 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")
            }
    }
0
Sean Dev

次を使用できます。

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

リモートプッシュ通知を処理します。

こちらをご覧ください documentation

0
sunkehappy

アプリが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
    }
}

ただし、アプリケーションを起動し、アプリケーションを閉じてアプリケーションをデバッグする場合は、スキームの編集に移動し、左側のメニューで実行を選択してから起動します実行される実行可能ファイルの待機を選択し、プッシュ通知をクリックするとアプリケーションが起動します

スキームの編集>実行>実行可能ファイルの起動を待つ

0
salmancs43

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

この質問の問題は、アプリを「開く」ことが明確に定義されていないことです。アプリは、非実行状態からコールドローンチされるか、非アクティブ状態から再アクティブ化されます(たとえば、別のアプリから元の状態に切り替えるなど)。これらの可能な状態をすべて区別するための私のソリューションは次のとおりです。

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

そして、MXDefaultsNSUserDefaultsの単なる小さなラッパーです。

0
skensell
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)
        }
    }
}
0
hardik bar

まだ試していませんが、通知を自分に送信できますか? http://nshipster.com/nsnotification-and-nsnotificationcenter/

0
Berendschot