Ir para o conteúdo



Guia de implementação de notificações por push

Este guia de implementação opcional e avançado aborda maneiras de aproveitar as extensões de app de conteúdo de notificação por push para obter o máximo de suas mensagens push. Incluídos estão três casos de uso personalizados criados por nossa equipe, trechos de código de acompanhamento e orientações sobre o registro de análise de dados. Consulte o Repositório de Demonstrações da Braze! Note que este guia de implementação está centrado em uma implementação Swift, mas são fornecidos trechos em Objective-C para os interessados.

Extensões de conteúdo de notificação para apps

Duas notificações por push exibidas lado a lado. A mensagem à esquerda mostra como um push se parece com a interface padrão. A mensagem à direita mostra um push de cartão fidelidade de café criado com a implementação de uma interface personalizada.

As notificações por push, embora aparentemente padronizadas entre diferentes plataformas, oferecem imensas opções de personalização além do que é normalmente implementado na interface padrão. Quando uma notificação por push é expandida, as extensões de conteúdo de notificação permitem uma visualização personalizada da notificação por push expandida.

As notificações por push podem ser expandidas de três maneiras diferentes:
- Pressionando e segurando o banner do push
- Deslizando para baixo no banner do push
- Deslizando o banner e selecionando “Visualizar”

Essas visualizações personalizadas oferecem maneiras inteligentes de engajar clientes, permitindo exibir diversos tipos de conteúdo, incluindo notificações interativas, notificações preenchidas com dados do usuário e até mensagens push que podem capturar informações como números de telefone e e-mail. Embora implementar push dessa forma possa ser novidade para alguns, um dos nossos recursos mais conhecidos na Braze, Push Stories, é um ótimo exemplo de como uma visualização personalizada para uma extensão de conteúdo de notificação pode se parecer!

Requisitos

Tela do Xcode para escolher um modelo para o novo target, com "Notification Content Extension" selecionado em Application Extension.

  • Notificações por push integradas com sucesso no seu app
  • iOS 10 ou superior
  • Os seguintes arquivos gerados pelo Xcode com base na sua linguagem de programação:

Swift
- NotificationViewController.swift
- MainInterface.storyboard

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

Configuração de categoria personalizada

Para configurar uma visualização personalizada no dashboard, é necessário ativar os botões de notificação e inserir sua categoria personalizada. A categoria iOS personalizada pré-registrada que você fornece é então verificada em relação à UNNotificationExtensionCategory no .plist do seu Notification Content Extension Target. O valor fornecido aqui deve corresponder ao que está definido no dashboard da Braze.

As opções de botão de notificação encontradas nas configurações do criador de mensagem push. Um plist mostrando NSExtension com UNNotificationExtensionCategory definido como "your_custom_category", UNNotificationExtensionDefaultContentHidden definido como 1 e UNNotificationExtensionInitialContentSizeRatio definido como 1.

Caso de uso e tutorial de implementação

Existem três tipos de extensões de conteúdo para notificações por push fornecidos. Cada tipo inclui um tutorial do conceito, possíveis casos de uso e uma visão de como as variáveis de notificação por push podem ser exibidas e usadas no dashboard da Braze:

Notificação por push interativa

As notificações por push podem responder a ações do usuário dentro de uma extensão de conteúdo. Para usuários com iOS 12 ou posterior, isso significa que você pode transformar suas mensagens push em notificações por push totalmente interativas! Essa interatividade oferece diversas possibilidades para engajar seus usuários com suas notificações. O exemplo a seguir mostra uma push na qual os usuários podem jogar um jogo de combinação dentro da notificação expandida.

Um diagrama mostrando como podem ser as fases de uma notificação por push interativa. As imagens mostram um usuário pressionando uma notificação por push que exibe um jogo interativo de combinação.

Configuração no dashboard

Para configurar uma visualização personalizada no dashboard, nas configurações do botão de notificação, insira a categoria específica que deseja exibir. Em seguida, no .plist da sua Notification Content Extension, você também deve definir a categoria personalizada no atributo UNNotificationExtensionCategory. O valor fornecido aqui deve corresponder ao que está definido no dashboard da Braze. Por fim, para ativar as interações do usuário em uma notificação por push, defina a chave UNNotificationExtensionInteractionEnabled como true.

A seção Botões de Notificação no dashboard da Braze com o campo Categoria de Notificação iOS definido como "match_game".

As opções de botão de notificação encontradas nas configurações do criador de mensagens push.

Outros casos de uso

As extensões de conteúdo push são uma opção empolgante para introduzir interatividade em suas promoções e aplicativos. Alguns exemplos incluem um jogo para os usuários jogarem, uma roleta para descontos ou um botão “curtir” para salvar um anúncio ou música.

Pronto para registrar a análise de dados?

Acesse a seção a seguir para entender melhor como o fluxo de dados deve funcionar.

Notificações por push personalizadas

Dois iPhones exibidos lado a lado. O primeiro iPhone mostra a visualização não expandida da mensagem push. O segundo iPhone mostra a versão expandida da mensagem push exibindo um progresso de quanto o usuário avançou em um curso, a próxima sessão e a data de vencimento da próxima sessão.

As notificações por push podem exibir informações específicas do usuário dentro de uma extensão de conteúdo. O exemplo ao lado mostra uma notificação por push após um usuário ter concluído uma tarefa específica (curso do Braze Learning) e agora é incentivado a expandir essa notificação para verificar seu progresso. As informações fornecidas aqui são específicas do usuário e podem ser disparadas quando uma sessão é concluída ou uma ação específica do usuário é realizada, aproveitando um disparo por API.

Configuração no dashboard

Para configurar uma push personalizada no dashboard, você deve registrar a categoria específica que deseja exibir e, em seguida, nos pares chave-valor usando Liquid padrão, definir os atributos de usuário apropriados que deseja que a mensagem mostre. Essas visualizações podem ser personalizadas com base em atributos específicos de um perfil de usuário específico.

Quatro conjuntos de pares chave-valor, onde "next_session_name" e "next_session_complete_date" são definidos como uma propriedade de disparo de API usando Liquid, e "completed_session count" e "total_session_count" são definidos como atributo personalizado do usuário usando Liquid.

Manipulando pares chave-valor

O método a seguir, didReceive, é chamado quando a extensão de conteúdo recebe uma notificação e pode ser encontrado no NotificationViewController. Os pares chave-valor fornecidos no dashboard são representados no código por meio do uso de um dicionário userInfo.

Analisando pares chave-valor de notificações por 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"];
  }
}

Outros casos de uso

As ideias para extensões de conteúdo push baseadas em progresso e focadas no usuário são infinitas. Alguns exemplos incluem adicionar a opção de compartilhar seu progresso em diferentes plataformas, expressar conquistas desbloqueadas, cartões fidelidade ou até listas de verificação de integração.

Pronto para registrar a análise de dados?

Acesse a seção a seguir para entender melhor como o fluxo de dados deve funcionar.

Notificação por push de captura de informações

As notificações por push podem capturar informações do usuário dentro de uma extensão de conteúdo, permitindo expandir os limites do que é possível com uma push. Examinando o fluxo a seguir, a visualização é capaz de responder a mudanças de estado. Esses componentes de mudança de estado estão representados em cada imagem.

  1. O usuário recebe uma notificação por push.
  2. A push é aberta e solicita informações ao usuário.
  3. As informações são fornecidas e, se forem válidas, o botão de registro é exibido.
  4. A visualização de confirmação é exibida e a push é descartada.

Observe que as informações solicitadas aqui podem abranger diversas finalidades, como a captura de número de SMS, e não precisam ser específicas de e-mail.

Configuração no dashboard

Para configurar uma push com capacidade de captura de informações no dashboard, você deve registrar e definir sua categoria personalizada e fornecer os pares chave-valor necessários. Como visto no exemplo, você também pode incluir uma imagem na sua push. Para isso, você deve integrar notificações Rich, definir o estilo de notificação na sua campanha como Notificação Rich e incluir uma imagem de push Rich.

Uma mensagem push com três conjuntos de pares chave-valor. 1. "Braze_id" definido como uma chamada Liquid para obter o Braze ID. 2. "cert_title" definido como "Braze Marketer Certification". 3. "Cert_description" definido como "Certified Braze marketers drive...".

Manipulando ações de botões

Cada botão de ação é identificado de forma única. O código verifica se o identificador de resposta é igual ao actionIdentifier e, se for, sabe que o usuário clicou no botão de ação.

Manipulando respostas de botões de ação de notificações por 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);
  }
}
Descartando pushes

As notificações por push podem ser descartadas automaticamente ao pressionar um botão de ação. Existem três opções pré-criadas de descarte de push recomendadas:

  1. completion(.dismiss) - Descarta a notificação
  2. completion(.doNotDismiss) - A notificação permanece aberta
  3. completion(.dismissAndForward) - A push é descartada e o usuário é redirecionado para o aplicativo.

Outros casos de uso

Solicitar a entrada do usuário por meio de notificações por push é uma oportunidade empolgante da qual muitas empresas não tiram proveito. Nessas mensagens push, você pode não apenas solicitar informações básicas como nome, e-mail ou número, mas também pedir aos usuários que completem um perfil de usuário inacabado ou até que enviem feedback.

Pronto para registrar a análise de dados?

Acesse a seção a seguir para entender melhor como o fluxo de dados deve funcionar.

Registro de análise de dados

O registro de análise de dados só pode ser feito em tempo real com a ajuda do servidor do cliente acessando nosso endpoint /users/track. Para registrar análise de dados, envie o valor braze_id no campo de pares chave-valor (conforme mostrado na captura de tela a seguir) para identificar qual perfil de usuário deve ser atualizado.

Uma mensagem push com três conjuntos de pares chave-valor. 1. "Braze_id" definido como uma chamada Liquid para obter o Braze ID. 2. "cert_title" definido como "Braze Marketer Certification". 3. "Cert_description" definido como "Certified Braze marketers drive...".

Registro manual

O registro manual exigirá que você primeiro configure os grupos de apps (App Groups) no Xcode e, em seguida, crie, salve e recupere os dados de análise. Isso requer algum trabalho de desenvolvimento personalizado da sua parte. Os trechos de código a seguir ajudarão a resolver isso.

Também é importante observar que os dados de análise não são enviados para a Braze até que o app móvel seja iniciado novamente. Isso significa que, dependendo das suas configurações de descarte, frequentemente existe um período indeterminado de tempo entre o descarte de uma notificação por push e o lançamento do app móvel e a recuperação dos dados de análise. Embora esse intervalo de tempo possa não afetar todos os casos de uso, os usuários devem considerar o impacto e, se necessário, ajustar a jornada do usuário para incluir a abertura do app e lidar com essa questão.

Um gráfico descrevendo como os dados de análise são processados na Braze. 1. Os dados de análise são criados. 2. Os dados de análise são salvos. 3. A notificação por push é descartada. 4. Período indeterminado de tempo entre o descarte da notificação por push e o lançamento do app móvel. 5. O app móvel é iniciado. 6. Os dados de análise são recebidos. 7. Os dados de análise são enviados para a Braze.

Etapa 1: Configurar grupos de apps no Xcode

Adicione a funcionalidade App Groups. Se você ainda não tem nenhum grupo de apps no seu app, acesse as funcionalidades do target principal do app, ative App Groups e clique em “+”. Use o bundle ID do seu app para criar o grupo de apps. Por exemplo, se o bundle ID do seu app é com.company.appname, você pode nomear seu grupo de apps como group.com.company.appname.xyz. Certifique-se de que App Groups esteja ativado tanto para o target principal do app quanto para o target da extensão de conteúdo.

A caixa de diálogo "Adicionar um novo container" no Xcode para configurar um grupo de apps, com um campo de texto pré-preenchido com "group.".

Etapa 2: Integrar trechos de código

Os trechos de código a seguir são uma referência útil sobre como salvar e enviar eventos personalizados, atributos personalizados e atributos de usuário. Este guia usará termos do UserDefaults, mas a representação em código será na forma de um arquivo auxiliar RemoteStorage. Também existem arquivos auxiliares adicionais UserAttributes e EventName Dictionary que são usados ao enviar e salvar atributos de usuário. Todos os arquivos auxiliares podem ser encontrados ao final deste guia.

Salvando eventos personalizados

Para salvar eventos personalizados, você deve criar os dados de análise como um novo design. Isso é feito criando um dicionário, preenchendo-o com metadados e salvando os dados por meio de um arquivo auxiliar.

  1. Inicialize um dicionário com metadados do evento
  2. Inicialize userDefaults para recuperar e armazenar os dados do evento
  3. Se já existir um array, adicione os novos dados ao array existente e salve
  4. Se não existir um array, salve o novo array em 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];
  }
}
Enviando eventos personalizados para a Braze

Após a inicialização do SDK, o melhor momento para registrar quaisquer dados de análise salvos de uma extensão de conteúdo de notificação. Isso pode ser feito percorrendo os eventos pendentes, verificando a chave “Event Name”, definindo os valores apropriados na Braze e, em seguida, limpando o armazenamento para a próxima vez que essa função for necessária.

  1. Percorra o array de eventos pendentes
  2. Percorra cada par chave-valor no dicionário pendingEvents
  3. Verifique explicitamente a chave “Event Name” para definir o valor adequadamente
  4. Todos os outros pares chave-valor serão adicionados ao dicionário properties
  5. Registre o evento personalizado individual
  6. Remova todos os eventos pendentes do armazenamento
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];
}
Salvando atributos personalizados

Para salvar atributos personalizados, você deve criar os dados de análise como um novo design. Isso é feito criando um dicionário, preenchendo-o com metadados e salvando os dados por meio de um arquivo auxiliar.

  1. Inicialize um dicionário com metadados do atributo
  2. Inicialize userDefaults para recuperar e armazenar os dados do atributo
  3. Se já existir um array, adicione os novos dados ao array existente e salve
  4. Se não existir um array, salve o novo array em 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];
  }
}
Enviando atributos personalizados para a Braze

Após a inicialização do SDK, o melhor momento para registrar quaisquer dados de análise salvos de uma extensão de conteúdo de notificação. Isso pode ser feito percorrendo os atributos pendentes, definindo o atributo personalizado apropriado na Braze e, em seguida, limpando o armazenamento para a próxima vez que essa função for necessária.

  1. Percorra o array de atributos pendentes
  2. Percorra cada par chave-valor no dicionário pendingAttributes
  3. Registre o atributo personalizado individual com a chave e o valor correspondentes
  4. Remova todos os atributos pendentes do armazenamento
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]];
  }
}
Salvando atributos de usuário

Ao salvar atributos de usuário, é recomendável criar um objeto personalizado para identificar qual tipo de atributo está sendo atualizado (email, first_name, phone_number, etc.). O objeto deve ser compatível com o armazenamento/recuperação em UserDefaults. Consulte o arquivo auxiliar UserAttribute para um exemplo de como fazer isso.

  1. Inicialize um objeto UserAttribute codificado com o tipo correspondente
  2. Inicialize userDefaults para recuperar e armazenar os dados do evento
  3. Se já existir um array, adicione os novos dados ao array existente e salve
  4. Se não existir um array, salve o novo array em 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];
  }
}
Enviando atributos de usuário para a Braze

Após a inicialização do SDK, o melhor momento para registrar quaisquer dados de análise salvos de uma extensão de conteúdo de notificação. Isso pode ser feito percorrendo os atributos pendentes, definindo o atributo personalizado apropriado na Braze e, em seguida, limpando o armazenamento para a próxima vez que essa função for necessária.

  1. Percorra o array de dados pendingAttributes
  2. Inicialize um objeto UserAttribute codificado a partir dos dados do atributo
  3. Defina o campo específico do usuário com base no tipo de atributo de usuário (e-mail)
  4. Remova todos os atributos de usuário pendentes do armazenamento
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];
}
Arquivos auxiliares
Arquivo 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."];
  }
}
Arquivo 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
Arquivo 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!