it-swarm.com.ru

Определить, было ли приложение запущено/открыто из Push-уведомления

Можно ли узнать, было ли приложение запущено/открыто из Push-уведомления?

Я предполагаю, что событие запуска может быть поймано здесь:

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

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

    }
}

Тем не менее, как я могу обнаружить, что он был открыт из Push-уведомления, когда приложение было в фоновом режиме?

145
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
175
shanegao

поздно, но может быть полезно

Когда приложение не запущено 

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

называется ..

где вам нужно проверить 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");
}
119
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. Определите, открывает ли пользователь приложение из Push
  2. Обновите представление на основе состояния Push
  3. Очистите состояние, чтобы последующие открытия не возвращали пользователя в ту же позицию.

Сложность в том, что обновление представления должно происходить, когда приложение фактически становится активным, что является одним и тем же методом жизненного цикла во всех случаях.

Эскиз нашего решения

Вот основные компоненты нашего решения:

  1. Сохраните переменную экземпляра notificationUserInfo в AppDelegate.
  2. Установите notificationUserInfo = nil в обоих applicationWillEnterForeground и didFinishLaunchingWithOptions.
  3. Установить notificationUserInfo = userInfo в didReceiveRemoteNotification:inactive
  4. Из applicationDidBecomeActive всегда вызывайте пользовательский метод openViewFromNotification и передайте self.notificationUserInfo. Если self.notificationUserInfo равен nil, вернитесь раньше, иначе откройте представление на основе состояния уведомления, найденного в self.notificationUserInfo.

Объяснение

При открытии из Push didFinishLaunchingWithOptions или applicationWillEnterForeground всегда вызывается непосредственно перед didReceiveRemoteNotification:inactive, поэтому мы сначала сбрасываем NotionUserInfo в этих методах, чтобы не было устаревшего состояния. Затем, если didReceiveRemoteNotification:inactive вызывается, мы знаем, что открываем из Push, поэтому мы устанавливаем self.notificationUserInfo, который затем выбирается applicationDidBecomeActive, чтобы перенаправить пользователя в нужное представление.

Существует один последний случай, когда пользователь открывает приложение в переключателе приложений (то есть, дважды нажав кнопку «Домой», когда приложение находится на переднем плане), а затем получает push-уведомление. В этом случае вызывается только didReceiveRemoteNotification:inactive, и не вызывается ни WillEnterForeground, ни didFinishLaunching, поэтому вам нужно какое-то специальное состояние для обработки этого случая.

Надеюсь это поможет.

25
Eric Conner

Это хорошо изношенный пост ... но в нем все еще отсутствует реальное решение проблемы (как указано в различных комментариях).

Оригинальный вопрос касается определения, когда приложение было запущено / открыто из Push-уведомления, например. пользователь нажимает на уведомление. Ни один из ответов на самом деле не охватывает этот случай.

Причину можно увидеть в потоке вызовов при получении уведомления, application:didReceiveRemoteNotification...

вызывается при получении уведомленияИснова, когда уведомление прослушивается пользователем. Из-за этого вы не можете сказать, просто посмотрев на UIApplicationState, когда пользователь коснулся его.

Кроме того, вам больше не нужно обрабатывать ситуацию «холодного запуска» приложения в application:didFinishLaunchingWithOptions..., так как application:didReceiveRemoteNotification... вызывается снова после запуска в iOS 9+ (возможно, и в 8).

Итак, как вы можете определить, что пользователь нажал на цепочку событий? Мое решение состоит в том, чтобы отметить время, когда приложение начинает выходить из фона или холодного запуска, а затем проверить это время в 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 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,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
Vladimir Vozniak

В 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

Когда приложение закрывается, и пользователь нажимает на Push-уведомление

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

Когда приложение работает в фоновом режиме, а пользователь нажимает на Push-уведомление

Если пользователь открывает ваше приложение из системного предупреждения, система может вызвать этот метод еще раз когда ваше приложение собирается выйти на передний план, чтобы вы могли обновить свой пользовательский интерфейс и отобразить информацию, относящуюся к уведомлению.

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

В зависимости от вашего приложения, оно также может отправлять вам тихий Push с content-available внутри aps, так что имейте это в виду :) Смотрите https://stackoverflow.com/a/33778990/1418457

15
onmyway133

Для Свифта:

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

если кто-то хочет ответ в 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
Hamid Shahsavari

Размещать это для пользователей Xamarin.

Ключом к определению того, было ли приложение запущено с помощью push-уведомления, является метод AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options) и переданный в него словарь параметров.

Словарь параметров будет иметь этот ключ, если это локальное уведомление: UIApplication.LaunchOptionsLocalNotificationKey.

Если это удаленное уведомление, это будет UIApplication.LaunchOptionsRemoteNotificationKey.

Когда ключ LaunchOptionsLocalNotificationKey, объект имеет тип UILocalNotification. Затем вы можете посмотреть на уведомление и определить, какое именно это уведомление.

Pro-tip: UILocalNotification не имеет идентификатора, как и UNNotificationRequest. Поместите ключ словаря в UserInfo, содержащий requestId, чтобы при тестировании UILocalNotification у вас был конкретный requestId, на котором можно основывать логику.

Я обнаружил, что даже на устройствах iOS 10+, которые при создании уведомлений о местоположении с использованием UNUserNotificationCenter's AddNotificationRequest & UNMutableNotificationContent, что, когда приложение не запущено (я его убил), запускается нажатием на уведомление в центре уведомлений, что словарь по-прежнему содержит объект 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;
}
2
Wes

Существует только один надежный способ, и он работает только для 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

Я начну с таблицы состояний, которую я создал для собственного использования, чтобы более точно ее визуализировать и рассмотреть все другие состояния: https://docs.google.com/spreadsheets/d/e/2PACX- 1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml? Gid = 0 & single = true

Используя эту таблицу, мы можем увидеть, что на самом деле требуется для разработки надежной системы обработки уведомлений, которая работает практически во всех возможных случаях использования.

Полное решение ↓ 

  • Сохранять полезную нагрузку messages в didReceiveRemoteNotification
  • Очистить сохраненные уведомления в applicationWillEnterForeground и didFinishLaunchingWithOptions
  • Чтобы справиться со случаями, когда управляющий центр/центр уведомлений извлекается, вы можете использовать флаг willResignActiveCalled и изначально установить для него значение false, Установите для этого параметра значение true в методе applicationWillResignActive
  • В методе didReceiveRemoteNotification сохраняйте уведомления (userInfo) только в том случае, если willResignActiveCalled имеет значение false.
  • Сброс willResignActiveCalled в false в методе applicationDidEnterBackground и applicationDidBecomeActive.

Примечание. Аналогичный ответ предлагается в комментариях к ответу Эрика, однако лист состояния помогает найти все возможные сценарии, как я делал в своем приложении.

Пожалуйста, найдите полный код ниже и комментарий ниже, если какой-либо конкретный случай не обрабатывается:

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
    }
  }
}
1
chetan anand

Для 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

обрабатывать удаленные Push-уведомления.

Проверьте здесь документация

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

Но если вы хотите запустить приложение и когда приложение закрыто и вы хотите отладить ваше приложение, вы можете перейти к Редактировать схему и в левом меню выбрать Выполнить , а затем в запуске выбрать Дождаться исполняемого файла быть запущенным а затем вы запускаете приложение, когда вы нажимаете на Push-уведомление

Изменить схему> Выполнить> Дождаться запуска исполняемого файла

0
salmancs43

Проблема с этим вопросом заключается в том, что «открытие» приложения не является четко определенным. Приложение либо запускается в холодном режиме из неработающего состояния, либо оно активируется из неактивного состояния (например, из-за переключения обратно в другое приложение). Вот мое решение различать все эти возможные состояния:

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 это просто небольшая оболочка для NSUserDefaults.

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

Прямо из документации по 

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

Если приложение работает и получает удаленное уведомление, приложение вызывает этот метод для обработки уведомления. 

Ваша реализация этого метода должна использовать уведомление, чтобы принять надлежащий курс действий.

И чуть позже

Если приложение не запускается при получении Push-уведомления, метод запускает приложение и предоставляет соответствующую информацию в словаре параметров запуска. 

Приложение не вызывает этот метод для обработки этого Push-уведомления. 

Вместо этого ваша реализация

application:willFinishLaunchingWithOptions:

или же

application:didFinishLaunchingWithOptions:

метод должен получить данные полезных данных push-уведомлений и ответить соответствующим образом.

0
Pfitz