Ir al contenido



Guía de implementación de notificaciones push

Esta guía de implementación opcional y avanzada cubre formas de aprovechar las extensiones de la aplicación de contenido de notificaciones push para sacar el máximo partido a tus mensajes push. Se incluyen tres casos de uso personalizados creados por nuestro equipo, fragmentos de código que los acompañan y orientaciones sobre el registro de análisis. ¡Visita el repositorio de demostraciones de Braze! Ten en cuenta que esta guía de implementación se centra en una implementación Swift, pero se proporcionan fragmentos de código Objective-C para los interesados.

Extensiones de contenido de notificaciones en la aplicación

Dos mensajes push mostrados uno al lado del otro. El mensaje de la derecha muestra cómo se ve un push con la interfaz predeterminada. El mensaje de la derecha muestra un push de tarjeta de fidelidad de café creado al implementar una interfaz push personalizada.

Las notificaciones push, aunque parecen estándar en las diferentes plataformas, ofrecen inmensas opciones de personalización más allá de lo que normalmente se implementa en la interfaz predeterminada. Cuando se expande una notificación push, las extensiones de contenido de notificaciones permiten una vista personalizada de la notificación push expandida.

Las notificaciones push se pueden expandir de tres formas diferentes:
- Una pulsación larga sobre el banner push
- Deslizando hacia abajo sobre el banner push
- Deslizando el banner y seleccionando “Ver”

Estas vistas personalizadas ofrecen formas inteligentes de interactuar con los clientes permitiéndote mostrar muchos tipos distintos de contenido, incluyendo notificaciones interactivas, notificaciones con datos de usuario e incluso mensajes push que pueden capturar información como números de teléfono y correos electrónicos. Aunque implementar push de esta forma puede ser poco familiar para algunos, una de nuestras características más conocidas en Braze, Push Stories, es un excelente ejemplo de cómo puede lucir una vista personalizada para una extensión de contenido de notificaciones en la aplicación.

Requisitos

Pantalla de Xcode para elegir una plantilla para tu nuevo destino con "Notification Content Extension" seleccionada en Application Extension.

  • Notificaciones push integradas correctamente en tu aplicación
  • iOS 10 o superior
  • Los siguientes archivos generados por Xcode según tu lenguaje de programación:

Swift
- NotificationViewController.swift
- MainInterface.storyboard

Objective-C
- NotificationViewController.h
- NotificationViewController.m
- MainInterface.storyboard

Configuración de categorías personalizadas

Para configurar una vista personalizada en el panel, debes activar los botones de notificación e introducir tu categoría personalizada. La categoría personalizada de iOS previamente registrada que proporciones se verifica contra el UNNotificationExtensionCategory en el .plist del destino de tu extensión de contenido de notificaciones. El valor proporcionado aquí debe coincidir con lo configurado en el panel de Braze.

Las opciones de botones de notificación encontradas en la configuración del creador de mensajes push. Un plist mostrando NSExtension con UNNotificationExtensionCategory establecido en "your_custom_category", UNNotificationExtensionDefaultContentHidden establecido en 1 y UNNotificationExtensionInitialContentSizeRatio establecido en 1.

Caso de uso y recorrido por la implementación

Se proporcionan tres tipos de extensión de aplicación de contenido para notificaciones push. Cada tipo incluye un recorrido conceptual, posibles casos de uso y una visión de cómo las variables de notificaciones push pueden verse y utilizarse en el panel de Braze:

Notificación push interactiva

Las notificaciones push pueden responder a las acciones del usuario dentro de una extensión de contenido. Para los usuarios que ejecutan iOS 12 o posterior, esto significa que puedes convertir tus mensajes push en notificaciones push totalmente interactivas. Esta interactividad ofrece muchas posibilidades para que tus usuarios interactúen con tus notificaciones. El siguiente ejemplo muestra una notificación push en la que los usuarios pueden jugar un juego de emparejar dentro de la notificación expandida.

Un diagrama de cómo podrían verse las fases de una notificación push interactiva. Las imágenes muestran a un usuario pulsando una notificación push que muestra un juego interactivo de emparejar.

Configuración del panel

Para configurar una vista personalizada en el panel, dentro de la configuración de los botones de notificación, introduce la categoría específica que deseas mostrar. A continuación, en el .plist de tu extensión de contenido de notificación, también debes configurar la categoría personalizada en el atributo UNNotificationExtensionCategory. El valor proporcionado aquí debe coincidir con lo configurado en el panel de Braze. Por último, para habilitar las interacciones del usuario en una notificación push, configura la clave UNNotificationExtensionInteractionEnabled como true.

La sección de botones de notificación en el panel de Braze con el campo de categoría de notificación de iOS configurado como "match_game".

Las opciones de botones de notificación que se encuentran en la configuración del creador de mensajes push.

Otros casos de uso

Las extensiones de contenido push son una opción interesante para introducir interactividad en tus promociones y aplicaciones. Algunos ejemplos incluyen un juego para que los usuarios jueguen, una rueda de girar y ganar para descuentos, o un botón de “me gusta” para guardar un anuncio o canción.

¿Listo para registrar análisis?

Visita la siguiente sección para comprender mejor cómo debería verse el flujo de datos.

Notificaciones push personalizadas

Dos iPhones mostrados uno al lado del otro. El primer iPhone muestra la vista contraída del mensaje push. El segundo iPhone muestra la versión expandida del mensaje push mostrando una captura de "progreso" de cuánto han avanzado en un curso, la siguiente sesión y cuándo es la fecha límite de la siguiente sesión.

Las notificaciones push pueden mostrar información específica del usuario dentro de una extensión de contenido. El ejemplo adjunto muestra una notificación push después de que un usuario ha completado una tarea específica (curso de Braze Learning) y ahora se le anima a expandir esta notificación para comprobar su progreso. La información proporcionada aquí es específica del usuario y puede enviarse cuando se completa una sesión o se realiza una acción específica del usuario aprovechando un disparador de API.

Configuración del panel

Para configurar una notificación push personalizada en el panel, debes registrar la categoría específica que deseas que se muestre y, a continuación, dentro de los pares clave-valor utilizando Liquid estándar, establecer los atributos de usuario apropiados que deseas que muestre el mensaje. Estas vistas pueden personalizarse en función de atributos específicos de un perfil de usuario concreto.

Cuatro conjuntos de pares clave-valor, donde "next_session_name" y "next_session_complete_date" están configurados como una propiedad de disparador de API usando Liquid, y "completed_session count" y "total_session_count" están configurados como un atributo personalizado de usuario usando Liquid.

Manejo de pares clave-valor

El siguiente método, didReceive, se llama cuando la extensión de contenido ha recibido una notificación y se encuentra dentro del NotificationViewController. Los pares clave-valor proporcionados en el panel se representan en el código mediante el uso de un diccionario userInfo.

Análisis de pares clave-valor de las notificaciones push

func didReceive(_ notification: UNNotification) {
  let userInfo = notification.request.content.userInfo

  guard let value = userInfo["YOUR-KEY-VALUE-PAIR"] as? String,
        let otherValue = userInfo["YOUR-OTHER-KEY-VALUE-PAIR"] as? String,
  else { fatalError("Key-Value Pairs are incorrect.")}

  ...
}
- (void)didReceiveNotification:(nonnull UNNotification *)notification {
  NSDictionary *userInfo = notification.request.content.userInfo;

  if (userInfo[@"YOUR-KEY-VALUE-PAIR"] && userInfo[@"YOUR-OTHER-KEY-VALUE-PAIR"]) {

  ...

  } else {
    [NSException raise:NSGenericException format:@"Key-Value Pairs are incorrect"];
  }
}

Otros casos de uso

Las ideas para extensiones de contenido push basadas en progreso y centradas en el usuario son infinitas, algunos ejemplos incluyen añadir la opción de compartir tu progreso en diferentes plataformas, expresar logros desbloqueados, tarjetas de puntos o incluso listas de verificación de incorporación.

¿Listo para registrar análisis?

Visita la siguiente sección para comprender mejor cómo debería verse el flujo de datos.

Notificación push de captura de información

Las notificaciones push pueden capturar información del usuario dentro de una extensión de contenido, permitiéndote llevar al límite lo que es posible con una notificación push. Al examinar el siguiente flujo mostrado, la vista es capaz de responder a cambios de estado. Esos componentes de cambio de estado están representados en cada imagen.

  1. El usuario recibe una notificación push.
  2. Se abre la notificación push y solicita información al usuario.
  3. Se proporciona la información y, si es válida, se muestra el botón de registro.
  4. Se muestra la vista de confirmación y la notificación push se descarta.

Ten en cuenta que la información solicitada aquí puede ser una amplia variedad de cosas, como la captura del número de SMS, no tiene que ser específica del correo electrónico.

Configuración del panel

Para configurar una notificación push con capacidad de captura de información en el panel, debes registrar y configurar tu categoría personalizada, y proporcionar los pares clave-valor necesarios. Como se ve en el ejemplo, también puedes incluir una imagen en tu notificación push. Para hacer esto, debes integrar las notificaciones enriquecidas, configurar el estilo de notificación en tu campaña como notificación enriquecida e incluir una imagen de push enriquecida.

Un mensaje push con tres conjuntos de pares clave-valor. 1. "Braze_id" configurado como una llamada Liquid para obtener el ID de Braze. 2. "cert_title" configurado como "Braze Marketer Certification". 3. "Cert_description" configurado como "Certified Braze marketers drive...".

Manejo de acciones de botones

Cada botón de acción se identifica de forma única. El código comprueba si tu identificador de respuesta es igual al actionIdentifier y, de ser así, sabe que el usuario hizo clic en el botón de acción.

Manejo de respuestas de botones de acción de notificaciones push

func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
  if response.actionIdentifier == "YOUR-REGISTER-IDENTIFIER" {
    // do something
  } else {
    // do something else
  }
}
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion {
  if ([response.actionIdentifier isEqualToString:@"YOUR-REGISTER-IDENTIFIER"]) {
    completion(UNNotificationContentExtensionResponseOptionDismiss);
  } else {
    completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
  }
}
Descartar notificaciones push

Las notificaciones push se pueden descartar automáticamente al presionar un botón de acción. Existen tres opciones predefinidas de descarte de notificaciones push que se recomiendan:

  1. completion(.dismiss) - Descarta la notificación
  2. completion(.doNotDismiss) - La notificación permanece abierta
  3. completion(.dismissAndForward) - La notificación push se descarta y el usuario es redirigido a la aplicación.

Otros casos de uso

Solicitar información del usuario a través de notificaciones push es una oportunidad interesante que muchas empresas no aprovechan. En estos mensajes push, puedes no solo solicitar información básica como nombre, correo electrónico o número, sino también animar a los usuarios a completar un perfil de usuario si está incompleto, o incluso a enviar comentarios.

¿Listo para registrar análisis?

Visita la siguiente sección para comprender mejor cómo debería verse el flujo de datos.

Registro de análisis

El registro de análisis solo se puede realizar en tiempo real con la ayuda del servidor del cliente que envía solicitudes a nuestro endpoint /users/track. Para registrar análisis, envía el valor braze_id en el campo de pares clave-valor (como se muestra en la siguiente captura de pantalla) para identificar qué perfil de usuario actualizar.

Un mensaje push con tres conjuntos de pares clave-valor. 1. "Braze_id" establecido como una llamada Liquid para obtener el ID de Braze. 2. "cert_title" establecido como "Braze Marketer Certification". 3. "Cert_description" establecido como "Certified Braze marketers drive...".

Registro manual

El registro manual requerirá que primero configures los grupos de aplicaciones dentro de Xcode, y luego crees, guardes y recuperes los análisis. Esto requerirá algo de trabajo personalizado del desarrollador de tu parte. Los siguientes fragmentos de código que se muestran te ayudarán a abordar esto.

También es importante tener en cuenta que los análisis no se envían a Braze hasta que la aplicación móvil se inicia posteriormente. Esto significa que, dependiendo de tu configuración de descarte, a menudo existe un período de tiempo indeterminado entre el momento en que se descarta una notificación push y el momento en que se inicia la aplicación móvil y se recuperan los análisis. Aunque este período de espera puede no afectar a todos los casos de uso, los usuarios deben considerar el impacto y, si es necesario, ajustar su recorrido de usuario para incluir la apertura de la aplicación y así abordar esta cuestión.

Un gráfico que describe cómo se procesan los análisis en Braze. 1. Se crean los datos de análisis. 2. Se guardan los datos de análisis. 3. Se descarta la notificación push. 4. Período de tiempo indeterminado entre el descarte de la notificación push y el inicio de la aplicación móvil. 5. Se inicia la aplicación móvil. 6. Se reciben los datos de análisis. 7. Se envían los datos de análisis a Braze.

Paso 1: Configurar grupos de aplicaciones dentro de Xcode

Añade la capacidad App Groups. Si no tienes ningún grupo de aplicaciones en tu aplicación, ve a la capacidad del target principal de la aplicación, activa App Groups y haz clic en “+”. Usa el bundle ID de tu aplicación para crear el grupo de aplicaciones. Por ejemplo, si el bundle ID de tu aplicación es com.company.appname, puedes nombrar tu grupo de aplicaciones group.com.company.appname.xyz. Asegúrate de que App Groups esté activado tanto para el target principal de tu aplicación como para el target de la extensión de contenido.

El cuadro de diálogo "Añadir un nuevo contenedor" en Xcode para configurar un grupo de aplicaciones, con un campo de texto prellenado con "group.".

Paso 2: Integrar fragmentos de código

Los siguientes fragmentos de código son una referencia útil sobre cómo guardar y enviar eventos personalizados, atributos personalizados y atributos de usuario. Esta guía hablará en términos de UserDefaults, pero la representación del código estará en forma de un archivo auxiliar RemoteStorage. También existen archivos auxiliares adicionales UserAttributes y EventName Dictionary que se utilizan al enviar y guardar atributos de usuario. Todos los archivos auxiliares se pueden encontrar al final de esta guía.

Guardar eventos personalizados

Para guardar eventos personalizados debes crear los análisis como un nuevo diseño. Esto se hace creando un diccionario, llenándolo con metadatos y guardando los datos a través de un archivo auxiliar.

  1. Inicializa un diccionario con metadatos del evento
  2. Inicializa userDefaults para recuperar y almacenar los datos del evento
  3. Si existe un array existente, añade los nuevos datos al array existente y guarda
  4. Si no existe un array, guarda el nuevo array en userDefaults
func saveCustomEvent(with properties: [String: Any]? = nil) {
  // 1
  let customEventDictionary = Dictionary(eventName: "YOUR-EVENT-NAME", properties: properties)

  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)

  // 3
  if var pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] {
    pendingEvents.append(contentsOf: [customEventDictionary])
    remoteStorage.store(pendingEvents, forKey: .pendingCustomEvents)
  } else {
  // 4
    remoteStorage.store([customEventDictionary], forKey: .pendingCustomEvents)
  }
}
- (void)saveCustomEvent:(NSDictionary<NSString *, id> *)properties {
  // 1
  NSDictionary<NSString *, id> *customEventDictionary = [[NSDictionary alloc] initWithEventName:@"YOUR-EVENT-NAME" properties:properties];

  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingEvents = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomEvents] mutableCopy];

  // 3
  if (pendingEvents) {
    [pendingEvents addObject:customEventDictionary];
    [remoteStorage store:pendingEvents forKey:RemoteStorageKeyPendingCustomAttributes];
  } else {
  // 4
    [remoteStorage store:@[ customEventDictionary ] forKey:RemoteStorageKeyPendingCustomAttributes];
  }
}
Enviar eventos personalizados a Braze

Después de que el SDK se inicializa es el mejor momento para registrar cualquier análisis guardado desde una extensión de contenido de notificación de la aplicación. Esto se puede hacer recorriendo todos los eventos pendientes, verificando la clave “Event Name”, estableciendo los valores apropiados en Braze, y luego limpiando el almacenamiento para la próxima vez que se necesite esta función.

  1. Recorre el array de eventos pendientes
  2. Recorre cada par clave-valor en el diccionario pendingEvents
  3. Verifica explícitamente la clave “Event Name” para establecer el valor de acuerdo
  4. Cada otro par clave-valor se añadirá al diccionario properties
  5. Registra el evento personalizado individual
  6. Elimina todos los eventos pendientes del almacenamiento
func logPendingCustomEventsIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] else { return }

  // 1
  for event in pendingEvents {
    var eventName: String?
    var properties: [AnyHashable: Any] = [:]

  // 2
    for (key, value) in event {
      if key == PushNotificationKey.eventName.rawValue {
  // 3
        if let eventNameValue = value as? String {
          eventName = eventNameValue
        } else {
          print("Invalid type for event_name key")
        }
      } else {
  // 4
        properties[key] = value
      }
    }
  // 5
    if let eventName = eventName {
      logCustomEvent(eventName, withProperties: properties)
    }
  }

  // 6
  remoteStorage.removeObject(forKey: .pendingCustomEvents)
}
- (void)logPendingEventsIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingEvents = [remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomEvents];

  // 1
  for (NSDictionary<NSString *, id> *event in pendingEvents) {
    NSString *eventName = nil;
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];

  // 2
    for (NSString* key in event) {
      if ([key isEqualToString:@"event_name"]) {
  // 3
        if ([[event objectForKey:key] isKindOfClass:[NSString class]]) {
          eventName = [event objectForKey:key];
        } else {
          NSLog(@"Invalid type for event_name key");
        }
      } else {
  // 4
        properties[key] = event[key];
      }
    }
  // 5
    if (eventName != nil) {
      [[Appboy sharednstance] logCustomEvent:eventName withProperties:properties];
    }
  }

  // 6
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingCustomEvents];
}
Guardar atributos personalizados

Para guardar atributos personalizados debes crear los análisis como un nuevo diseño. Esto se hace creando un diccionario, llenándolo con metadatos y guardando los datos a través del uso de un archivo auxiliar.

  1. Inicializa un diccionario con metadatos de atributos
  2. Inicializa userDefaults para recuperar y almacenar los datos de atributos
  3. Si existe un array existente, añade los nuevos datos al array existente y guarda
  4. Si no existe un array, guarda el nuevo array en userDefaults
func saveCustomAttribute() {
  // 1
  let customAttributeDictionary: [String: Any] = ["YOUR-CUSTOM-ATTRIBUTE-KEY": "YOUR-CUSTOM-ATTRIBUTE-VALUE"]

  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)

  // 3
  if var pendingAttributes = remoteStorage.retrieve(forKey: .pendingCustomAttributes) as? [[String: Any]] {
    pendingAttributes.append(contentsOf: [customAttributeDictionary])
    remoteStorage.store(pendingAttributes, forKey: .pendingCustomAttributes)
  } else {
  // 4
    remoteStorage.store([customAttributeDictionary], forKey: .pendingCustomAttributes)
  }
}
- (void)saveCustomAttribute {
  // 1
  NSDictionary<NSString *, id> *customAttributeDictionary = @{ @"YOUR-CUSTOM-ATTRIBUTE-KEY": @"YOUR-CUSTOM-ATTRIBUTE-VALUE" };

  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingAttributes = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomAttributes] mutableCopy];

  // 3
  if (pendingAttributes) {
    [pendingAttributes addObject:customAttributeDictionary];
    [remoteStorage store:pendingAttributes forKey:RemoteStorageKeyPendingCustomAttributes];
  } else {
  // 4
    [remoteStorage store:@[ customAttributeDictionary ] forKey:RemoteStorageKeyPendingCustomAttributes];
  }
}
Enviar atributos personalizados a Braze

Después de que el SDK se inicializa es el mejor momento para registrar cualquier análisis guardado desde una extensión de contenido de notificación de la aplicación. Esto se puede hacer recorriendo los atributos pendientes, estableciendo el atributo personalizado apropiado en Braze, y luego limpiando el almacenamiento para la próxima vez que se necesite esta función.

  1. Recorre el array de atributos pendientes
  2. Recorre cada par clave-valor en el diccionario pendingAttributes
  3. Registra el atributo personalizado individual con la clave y el valor correspondientes
  4. Elimina todos los atributos pendientes del almacenamiento
func logPendingCustomAttributesIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingAttributes = remoteStorage.retrieve(forKey: .pendingCustomAttributes) as? [[String: Any]] else { return }

  // 1
  pendingAttributes.forEach { setCustomAttributesWith(keysAndValues: $0) }

  // 4
  remoteStorage.removeObject(forKey: .pendingCustomAttributes)
}

func setCustomAttributesWith(keysAndValues: [String: Any]) {
  // 2
  for (key, value) in keysAndValues {
  // 3
    if let value = value as? [String] {
      setCustomAttributeArrayWithKey(key, andValue: value)
    } else {
      setCustomAttributeWithKey(key, andValue: value)
    }
  }
}
- (void)logPendingCustomAttributesIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingAttributes = [remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomAttributes];

  // 1
  for (NSDictionary<NSString*, id> *attribute in pendingAttributes) {
    [self setCustomAttributeWith:attribute];
  }

  // 4
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingCustomAttributes];
}

- (void)setCustomAttributeWith:(NSDictionary<NSString *, id> *)keysAndValues {
  // 2
  for (NSString *key in keysAndValues) {
  // 3
    [self setCustomAttributeWith:key andValue:[keysAndValues objectForKey:key]];
  }
}
Guardar atributos de usuario

Al guardar atributos de usuario, se recomienda crear un objeto personalizado para descifrar qué tipo de atributo se está actualizando (email, first_name, phone_number, etc.). El objeto debe ser compatible con ser almacenado/recuperado desde UserDefaults. Consulta el archivo auxiliar UserAttribute para ver un ejemplo de cómo lograr esto.

  1. Inicializa un objeto UserAttribute codificado con el tipo correspondiente
  2. Inicializa userDefaults para recuperar y almacenar los datos del evento
  3. Si existe un array existente, añade los nuevos datos al array existente y guarda
  4. Si no existe un array, guarda el nuevo array en userDefaults
func saveUserAttribute() {
  // 1
  guard let data = try? PropertyListEncoder().encode(UserAttribute.userAttributeType("USER-ATTRIBUTE-VALUE")) else { return }

  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)

  // 3
  if var pendingAttributes = remoteStorage.retrieve(forKey: .pendingUserAttributes) as? [Data] {
    pendingAttributes.append(contentsOf: [data])
    remoteStorage.store(pendingAttributes, forKey: .pendingUserAttributes)
  } else {
  // 4
    remoteStorage.store([data], forKey: .pendingUserAttributes)
  }
}
- (void)saveUserAttribute {
  // 1
  UserAttribute *userAttribute = [[UserAttribute alloc] initWithUserField:@"USER-ATTRIBUTE-VALUE" attributeType:UserAttributeTypeEmail];

  NSError *error;
  NSData *data = [NSKeyedArchiver archivedDataWithRootObject:userAttribute requiringSecureCoding:YES error:&error];

  if (error != nil) {
    // log error
  }
  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingAttributes = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingUserAttributes] mutableCopy];

  // 3
  if (pendingAttributes) {
    [pendingAttributes addObject:data];
    [remoteStorage store:pendingAttributes forKey:RemoteStorageKeyPendingUserAttributes];
  } else {
  // 4
    [remoteStorage store:@[data] forKey:RemoteStorageKeyPendingUserAttributes];
  }
}
Enviar atributos de usuario a Braze

Después de que el SDK se inicializa es el mejor momento para registrar cualquier análisis guardado desde una extensión de contenido de notificación de la aplicación. Esto se puede hacer recorriendo los atributos pendientes, estableciendo el atributo personalizado apropiado en Braze, y luego limpiando el almacenamiento para la próxima vez que se necesite esta función.

  1. Recorre el array de datos pendingAttributes
  2. Inicializa un objeto UserAttribute codificado a partir de los datos de atributos
  3. Establece el campo de usuario específico basado en el tipo de atributo de usuario (correo electrónico)
  4. Elimina todos los atributos de usuario pendientes del almacenamiento
func logPendingUserAttributesIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingAttributes = remoteStorage.retrieve(forKey: .pendingUserAttributes) as? [Data] else { return }

  // 1
  for attributeData in pendingAttributes {
  // 2
    guard let userAttribute = try? PropertyListDecoder().decode(UserAttribute.self, from: attributeData) else { continue }

  // 3
    switch userAttribute {
    case .email(let email):
      user?.email = email
    }
  }
  // 4
  remoteStorage.removeObject(forKey: .pendingUserAttributes)
}
- (void)logPendingUserAttributesIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingAttributes = [remoteStorage retrieveForKey:RemoteStorageKeyPendingUserAttributes];

  // 1
  for (NSData *attributeData in pendingAttributes) {
    NSError *error;

  // 2
    UserAttribute *userAttribute = [NSKeyedUnarchiver unarchivedObjectOfClass:[UserAttribute class] fromData:attributeData error:&error];

    if (error != nil) {
      // log error
    }

  // 3
    if (userAttribute) {
      switch (userAttribute.attributeType) {
        case UserAttributeTypeEmail:
          [self user].email = userAttribute.userField;
          break;
      }
    }
  }
  // 4
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingUserAttributes];
}
Archivos auxiliares
Archivo auxiliar RemoteStorage
enum RemoteStorageKey: String, CaseIterable {

  // MARK: - Notification Content Extension Analytics
  case pendingCustomEvents = "pending_custom_events"
  case pendingCustomAttributes = "pending_custom_attributes"
  case pendingUserAttributes = "pending_user_attributes"
}

enum RemoteStorageType {
  case standard
  case suite
}

class RemoteStorage: NSObject {
  private var storageType: RemoteStorageType = .standard
  private lazy var defaults: UserDefaults = {
    switch storageType {
    case .standard:
      return .standard
    case .suite:
      return UserDefaults(suiteName: "YOUR-DOMAIN-IDENTIFIER")!
    }
  }()

  init(storageType: RemoteStorageType = .standard) {
    self.storageType = storageType
  }

  func store(_ value: Any, forKey key: RemoteStorageKey) {
    defaults.set(value, forKey: key.rawValue)
  }

  func retrieve(forKey key: RemoteStorageKey) -> Any? {
    return defaults.object(forKey: key.rawValue)
  }

  func removeObject(forKey key: RemoteStorageKey) {
    defaults.removeObject(forKey: key.rawValue)
  }

  func resetStorageKeys() {
    for key in RemoteStorageKey.allCases {
      defaults.removeObject(forKey: key.rawValue)
    }
  }
}
@interface RemoteStorage ()

@property (nonatomic) StorageType storageType;
@property (nonatomic, strong) NSUserDefaults *defaults;

@end

@implementation RemoteStorage

- (id)initWithStorageType:(StorageType)storageType {
  if (self = [super init]) {
    self.storageType = storageType;
  }
  return self;
}

- (void)store:(id)value forKey:(RemoteStorageKey)key {
  [[self defaults] setValue:value forKey:[self rawValueForKey:key]];
}

- (id)retrieveForKey:(RemoteStorageKey)key {
  return [[self defaults] objectForKey:[self rawValueForKey:key]];
}

- (void)removeObjectForKey:(RemoteStorageKey)key {
  [[self defaults] removeObjectForKey:[self rawValueForKey:key]];
}

- (void)resetStorageKeys {
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingCustomEvents]];
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingCustomAttributes]];
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingUserAttributes]];
}

- (NSUserDefaults *)defaults {
  if (!self.defaults) {
    switch (self.storageType) {
      case StorageTypeStandard:
        return [NSUserDefaults standardUserDefaults];
        break;
      case StorageTypeSuite:
        return [[NSUserDefaults alloc] initWithSuiteName:@"YOUR-DOMAIN-IDENTIFIER"];
    }
  } else {
    return self.defaults;
  }
}

- (NSString*)rawValueForKey:(RemoteStorageKey)remoteStorageKey {
    switch(remoteStorageKey) {
    case RemoteStorageKeyPendingCustomEvents:
      return @"pending_custom_events";
    case RemoteStorageKeyPendingCustomAttributes:
      return @"pending_custom_attributes";
    case RemoteStorageKeyPendingUserAttributes:
      return @"pending_user_attributes";
    default:
      [NSException raise:NSGenericException format:@"Unexpected FormatType."];
  }
}
Archivo auxiliar UserAttribute
enum UserAttribute: Hashable {
  case email(String?)
}

// MARK: - Codable
extension UserAttribute: Codable {
  private enum CodingKeys: String, CodingKey {
    case email
  }

  func encode(to encoder: Encoder) throws {
    var values = encoder.container(keyedBy: CodingKeys.self)

    switch self {
    case .email(let email):
      try values.encode(email, forKey: .email)
    }
  }

  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    let email = try values.decode(String.self, forKey: .email)
    self = .email(email)
  }
}
@implementation UserAttribute

- (id)initWithUserField:(NSString *)userField attributeType:(UserAttributeType)attributeType {
  if (self = [super init]) {
    self.userField = userField;
    self.attributeType = attributeType;
  }
  return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
  [encoder encodeObject:self.userField forKey:@"userField"];
  [encoder encodeInteger:self.attributeType forKey:@"attributeType"];
}

- (id)initWithCoder:(NSCoder *)decoder {
  if (self = [super init]) {
    self.userField = [decoder decodeObjectForKey:@"userField"];

    NSInteger attributeRawValue = [decoder decodeIntegerForKey:@"attributeType"];
    self.attributeType = (UserAttributeType) attributeRawValue;
  }
  return self;
}

@end
Archivo auxiliar EventName Dictionary
extension Dictionary where Key == String, Value == Any {
  init(eventName: String, properties: [String: Any]? = nil) {
    self.init()
    self[PushNotificationKey.eventName.rawValue] = eventName

    if let properties = properties {
      for (key, value) in properties {
        self[key] = value
      }
    }
  }
}
@implementation NSDictionary (Helper)

- (id)initWithEventName:(NSString *)eventName properties:(NSDictionary *)properties {
  self = [self init];
  if (self) {
    dict[@"event_name"] = eventName;

    for(id key in properties) {
      dict[key] = properties[key];
    }
  }
  return self;
}

@end


New Stuff!