ios - mail - Detecta si la aplicación fue lanzada/abierta desde una notificación push
push mail iphone (22)
¿Es posible saber si la aplicación fue lanzada / abierta desde una notificación push?
Supongo que el evento de lanzamiento puede capturarse aquí:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (launchOptions != nil) {
// Launched from push notification
NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
}
}
Sin embargo, ¿cómo puedo detectar que se abrió desde una notificación de inserción cuando la aplicación estaba en segundo plano?
Cuando la aplicación está en segundo plano como shanegao puedes usar
- (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
}
}
Pero si desea iniciar la aplicación y cuando la aplicación está cerrada y desea depurar su aplicación, puede ir a Editar esquema y en el menú de la izquierda seleccionar Ejecutar y luego en el inicio seleccionar Esperar que se ejecute el ejecutable y luego la aplicación se inicia cuando haga clic en notificaciones push
Editar esquema> Ejecutar> Esperar a que se ejecute el ejecutable
Directamente de la documentación para
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo:nil
Si la aplicación se está ejecutando y recibe una notificación remota, la aplicación llama a este método para procesar la notificación.
Su implementación de este método debe usar la notificación para tomar un curso de acción apropiado.
Y un poco más tarde
Si la aplicación no se está ejecutando cuando llega una notificación de inserción, el método inicia la aplicación y proporciona la información adecuada en el diccionario de opciones de inicio.
La aplicación no llama a este método para manejar esa notificación push.
En cambio, su implementación de la
application:willFinishLaunchingWithOptions:
o
application:didFinishLaunchingWithOptions:
El método necesita obtener los datos de la carga útil de la notificación de inserción y responder de forma adecuada.
El problema con esta pregunta es que "abrir" la aplicación no está bien definido. Una aplicación se inicia en frío desde un estado que no se está ejecutando, o se reactiva desde un estado inactivo (por ejemplo, al volver a ella desde otra aplicación). Aquí está mi solución para distinguir todos estos posibles estados:
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;
}
Y MXDefaults
es solo una pequeña envoltura para NSUserDefaults
.
El problema que tuvimos fue actualizar correctamente la vista después de que se lanzó la aplicación. Aquí hay secuencias complicadas de métodos de ciclo de vida que se vuelven confusas.
Métodos del ciclo de vida
Nuestras pruebas para iOS 10 revelaron las siguientes secuencias de métodos del ciclo de vida para los distintos casos:
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
El problema
Ok, entonces ahora tenemos que:
- Determine si el usuario abre la aplicación desde un push
- Actualice la vista según el estado de inserción
- Borre el estado para que las subsiguientes aperturas no devuelvan al usuario a la misma posición.
El truco es que la actualización de la vista tiene que ocurrir cuando la aplicación realmente se activa, que es el mismo método de ciclo de vida en todos los casos.
Croquis de nuestra solución
Aquí están los componentes principales de nuestra solución:
- Almacene una variable de instancia
notificationUserInfo
en AppDelegate. - Establezca
notificationUserInfo = nil
enapplicationWillEnterForeground
ydidFinishLaunchingWithOptions
. - Establecer
notificationUserInfo = userInfo
endidReceiveRemoteNotification:inactive
- Desde
applicationDidBecomeActive
llame siempre a un método personalizadoopenViewFromNotification
y paseself.notificationUserInfo
. Siself.notificationUserInfo
es nil, devuelva pronto, de lo contrario abra la vista según el estado de notificación encontrado enself.notificationUserInfo
.
Explicación
Cuando se abre desde un push didFinishLaunchingWithOptions
o applicationWillEnterForeground
siempre se llama inmediatamente antes de que didReceiveRemoteNotification:inactive
, por lo que primero reiniciamos notificationUserInfo en estos métodos para que no haya un estado obsoleto. Entonces, si se llama a la función didReceiveRemoteNotification:inactive
, sabemos que estamos abriendo desde un push, por lo que establecemos self.notificationUserInfo
que luego es recogido por applicationDidBecomeActive
para reenviar al usuario a la vista correcta.
Hay un caso final que es si el usuario tiene la aplicación abierta dentro del conmutador de la aplicación (es decir, tocando dos veces el botón de inicio mientras la aplicación está en primer plano) y luego recibe una notificación de inserción. En este caso, solo se llama a didReceiveRemoteNotification:inactive
, y ni WillEnterForeground ni didFinishLaunching reciben llamadas, por lo que necesita algún estado especial para manejar ese caso.
Espero que esto ayude.
Empezaré con un gráfico de estado que creé para mi propio uso para visualizarlo con mayor precisión y para considerar todos los demás estados: https://docs.google.com/spreadsheets/d/e/2PACX-1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml?gid=0&single=true
Usando este cuadro, podemos ver lo que realmente se requiere para desarrollar un sistema robusto de manejo de notificaciones que funcione en casi todos los casos de uso posibles.
Solución completa ↓
- Almacenar la carga útil de notificación en didReceiveRemoteNotification
- Borrar notificaciones almacenadas en applicationWillEnterForeground y didFinishLaunchingWithOptions
- Para abordar los casos en los que se ha retirado el centro de control / notificación, puede utilizar un marcador willResignActiveCalled y establecerlo en falso inicialmente, configurarlo en verdadero en el método applicationWillResignActive ,
- En el método didReceiveRemoteNotification , guarde las notificaciones (userInfo) solo cuando willResignActiveCalled sea falso.
- Restablecer willResignActiveCalled en false en los métodos applicationDidEnterBackground y applicationDidBecomeActive .
Nota: Se sugiere una respuesta similar en los comentarios sobre la respuesta de Eric, sin embargo, la hoja de estado ayuda a encontrar todos los escenarios posibles como lo hice en mi aplicación.
Encuentre el código completo a continuación y comente a continuación si no se maneja ningún caso específico:
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 : aquí es donde puedes escribir todo tu código sucio para navegar por diferentes partes de la aplicación, manejar bases de datos (CoreData / Realm) y hacer todo lo demás que se debe hacer cuando se recibe una notificación.
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
}
}
}
En application:didReceiveRemoteNotification:
comprueba si has recibido la notificación cuando tu aplicación está en primer plano o en segundo plano.
Si se recibió en segundo plano, inicie la aplicación desde la notificación.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
NSLog(@"Notification received by running app");
} else {
NSLog(@"App opened from Notification");
}
}
Esta es una publicación muy usada ... pero todavía falta una solución real al problema (como se señala en los diversos comentarios).
La pregunta original es sobre detectar cuándo se inició / abrió la aplicación desde una notificación push, por ejemplo , un usuario toca la notificación. Ninguna de las respuestas cubre este caso.
La razón se puede ver en el flujo de llamadas cuando llega una notificación, application:didReceiveRemoteNotification...
se llama cuando se recibe la notificación Y nuevamente cuando el usuario toca la notificación. Debido a esto, no se puede decir con solo mirar UIApplicationState
si el usuario lo tocó.
Además, ya no es necesario manejar la situación de un ''arranque en frío'' de la aplicación en la application:didFinishLaunchingWithOptions...
como application:didReceiveRemoteNotification...
se llama nuevamente después del lanzamiento en iOS 9+ (tal vez 8 también).
Entonces, ¿cómo puede saber si el usuario ha iniciado la cadena de eventos? Mi solución es marcar el momento en que la aplicación comienza a salir de segundo plano o en frío y luego verificar el tiempo en la application:didReceiveRemoteNotification...
Si es menor a 0.1 s, entonces puede estar bastante seguro de que el toque activó el inicio.
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)
}
}
}
He probado esto para ambos casos (aplicación en segundo plano, aplicación no en ejecución) en iOS 9+ y funciona como un encanto. 0.1s también es bastante conservador, el valor real es ~ 0.002s así que 0.01 también está bien.
Para 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)")
}
}
Publicando esto para los usuarios de Xamarin.
La clave para detectar si la aplicación se lanzó mediante una notificación AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options)
es el AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options)
y el diccionario de opciones que se AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options)
.
El diccionario de opciones tendrá esta clave si es una notificación local: UIApplication.LaunchOptionsLocalNotificationKey
.
Si se trata de una notificación remota, será UIApplication.LaunchOptionsRemoteNotificationKey
.
Cuando la clave es LaunchOptionsLocalNotificationKey
, el objeto es de tipo UILocalNotification
. Luego puede ver la notificación y determinar qué notificación específica es.
Pro-tip: UILocalNotification
no tiene un identificador, del mismo modo que UNNotificationRequest
. Coloque una clave de diccionario en UserInfo que contenga un requestId para que cuando pruebe UILocalNotification
, tenga un requestId específico disponible para basar algo de lógica en.
Encontré que incluso en dispositivos con iOS 10+, cuando creaba notificaciones de ubicación usando AddNotificationRequest
y UNMutableNotificationContent
, cuando la aplicación no se ejecutaba (lo maté) y se iniciaba tocando la notificación en el centro de notificaciones, el diccionario todavía contiene el objeto UILocalNotificaiton
.
Esto significa que mi código que verifica el lanzamiento basado en notificaciones funcionará en los dispositivos iOS8 e 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;
}
Puedes usar:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
para manejar las notificaciones push remotas.
Verifique aquí la documentation
Sí, puedes detectar con este método en appDelegate :
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
/* your Code*/
}
Para la notificación local:
- (void)application:(UIApplication *)application
didReceiveLocalNotification:(UILocalNotification *)notification
{
/* your Code*/
}
Swift 2.0 para el estado "No se está ejecutando" (Notificación local y remota)
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
}
Todavía no lo he probado, pero ¿podrías enviarte una notificación? http://nshipster.com/nsnotification-and-nsnotificationcenter/
Ver este código:
- (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
}
}
igual que
-(void)application:(UIApplication *)application didReceiveLocalNotification (UILocalNotification *)notification
si alguien quiere la respuesta rápidamente 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
}
}
tarde pero quizás útil
Cuando la aplicación no se está ejecutando
- (BOOL) aplicación: aplicación (UIApplication *) didFinishLaunchingWithOptions: (NSDictionary *) launchOptions
se llama ..
donde necesitas verificar la notificación 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");
}
Cuando finaliza la aplicación, y el usuario toca la notificación de inserción
public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] != nil {
print("from push")
}
}
Cuando la aplicación está en segundo plano, y el usuario toca la notificación de inserción
Si el usuario abre su aplicación desde la alerta mostrada por el sistema, el sistema puede llamar a este método nuevamente cuando su aplicación esté a punto de ingresar al primer plano para que pueda actualizar su interfaz de usuario y mostrar información relacionada con la notificación.
public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .Inactive {
print("from push")
}
}
Dependiendo de su aplicación, también puede enviarle mensajes silenciosos con content-available
dentro de las aps
, así que tenga esto en cuenta :) Consulte https://.com/a/33778990/1418457
IN SWIFT:
Estoy ejecutando Notificaciones Push (con obtención de fondos). Cuando mi aplicación está en segundo plano y recibo una notificación de inserción, descubrí que didReceiveRemoteNotification en appDelegate se llamaría dos veces; una vez para cuando se recibe la notificación y otra cuando el usuario hace clic en la alerta de notificación.
Para detectar si se hizo clic en la alerta de notificación, simplemente verifique si applicationState raw value == 1 dentro de didReceiveRemoteNotification en appDelegate.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject]) {
// If not from alert click applicationState(1)
if (application.applicationState.rawValue != 1) {
// Run your code here
}
}
Espero que esto ayude.
Para rápido:
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
}
}
Swift 3.0
En AppDelegate, en la función ''didFinishLaunchingWithOptions'' maneja la notificación remota con cierto retraso y abre su Viewcontroller. Puede usar la demora para manejar la notificación después de cargar la aplicación con éxito.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as! [NSObject : AnyObject]? {
AppHelper.delay(0.8, closure: {
self.handelNotification(dic: remoteNotification as! [String : Any])
})
}
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
// 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")
}
}