Skip to content



Guía de implantación de la tarjeta de contenido

Esta guía de implementación opcional y avanzada abarca consideraciones sobre códigos de tarjetas de contenido, tres casos de uso personalizados creados por nuestro equipo, fragmentos de código que los acompañan y orientaciones sobre el registro de impresiones, clics y descartes. ¡Visita nuestro repositorio de demostraciones Braze aquí! 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.

Consideraciones sobre códigos

Tarjetas de contenido como objetos personalizados

Al igual que un cohete que añade un propulsor, tus propios objetos personalizados pueden ampliarse para funcionar como tarjetas de contenido. Las superficies API limitadas como esta proporcionan flexibilidad para trabajar con diferentes backends de datos indistintamente. Esto puede hacerse ajustándose al protocolo ContentCardable e implementando el inicializador (como se ve en los siguientes fragmentos de código) y, mediante el uso de la estructura ContentCardData, te permite acceder a los datos de ABKContentCard. La carga útil ABKContentCard se utilizará para inicializar la estructura ContentCardData y el propio objeto personalizado, todo ello a partir de un tipo Dictionary mediante el inicializador que incluye el protocolo.

El inicializador también incluye una enumeración ContentCardClassType. Esta enumeración se utiliza para decidir qué objeto inicializar. Mediante el uso de pares clave-valor dentro del panel Braze, puedes establecer una clave class_type explícita que se utilizará para determinar qué objeto inicializar. Estos pares clave-valor de las tarjetas de contenido aparecen en la variable extras en ABKContentCard. Otro componente básico del inicializador es el parámetro del diccionario metaData. El metaData incluye todo lo que hay en el ABKContentCard analizado en una serie de claves y valores. Una vez analizadas las tarjetas relevantes y convertidas en tus objetos personalizados, la aplicación está lista para empezar a trabajar con ellas como si se hubieran instanciado desde JSON o cualquier otra fuente.

Una vez que tengas una sólida comprensión de estas consideraciones sobre códigos, consulta nuestros casos de uso para empezar a implementar tus objetos personalizados.

Protocolo ContentCardable
Un objeto ContentCardData que representa los datos de ABKContentCard junto con una enumeración ContentCardClassType. Un inicializador utilizado para instanciar objetos personalizados con metadatos ABKContentCard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protocol ContentCardable {
  var contentCardData: ContentCardData? { get }
  init?(metaData: [ContentCardKey: Any], classType contentCardClassType: ContentCardClassType)
}
 
extension ContentCardable {
  var isContentCard: Bool {
    return contentCardData != nil
  }
   
  func logContentCardClicked() {
    BrazeManager.shared.logContentCardClicked(idString: contentCardData?.contentCardId)
  }
   
  func logContentCardDismissed() {
    BrazeManager.shared.logContentCardDismissed(idString: contentCardData?.contentCardId)
  }
   
  func logContentCardImpression() {
    BrazeManager.shared.logContentCardImpression(idString: contentCardData?.contentCardId)
  }
}

Estructura de datos de la tarjeta de contenido
ContentCardData representa los valores analizados de un ABKContentCard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ContentCardData: Hashable {
  let contentCardId: String
  let contentCardClassType: ContentCardClassType
  let createdAt: Double
  let isDismissable: Bool
  ...
  // other Content Card properties such as expiresAt, pinned, etc.
}
 
extension ContentCardData: Equatable {
  static func ==(lhs: ContentCardData, rhs: ContentCardData) -> Bool {
    return lhs.contentCardId == rhs.contentCardId
  }
}

Protocolo ContentCardable
Un objeto ContentCardData que representa los datos ABKContentCard junto con una enumeración ContentCardClassType, un inicializador utilizado para instanciar objetos personalizados con metadatos ABKContentCard.

1
2
3
4
5
6
7
8
9
10
11
12
@protocol ContentCardable <NSObject>
 
@property (nonatomic, strong) ContentCardData *contentCardData;
- (instancetype __nullable)initWithMetaData:(NSDictionary *)metaData
                                  classType:(enum ContentCardClassType)classType;
 
- (BOOL)isContentCard;
- (void)logContentCardImpression;
- (void)logContentCardClicked;
- (void)logContentCardDismissed;
 
@end

Estructura de datos de la tarjeta de contenido
ContentCardData representa los valores analizados de un ABKContentCard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface ContentCardData : NSObject
 
+ (ContentCardClassType)contentCardClassTypeForString:(NSString *)rawValue;
 
- (instancetype)initWithIdString:(NSString *)idString
                       classType:(ContentCardClassType)classType
                       createdAt:(double)createdAt isDismissible:(BOOL)isDismissible;
 
@property (nonatomic, readonly) NSString *contentCardId;
@property (nonatomic) ContentCardClassType classType;
@property (nonatomic, readonly) double *createdAt;
@property (nonatomic, readonly) BOOL isDismissible;
...
// other Content Card properties such as expiresAt, pinned, etc.    
 
@end

Inicializador de objetos personalizado
Los metadatos de un ABKContentCard se utilizan para rellenar las variables de tu objeto. Los pares clave-valor configurados en el panel de Braze se representan en el diccionario “extras”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension CustomObject: ContentCardable {
  init?(metaData: [ContentCardKey: Any], classType contentCardClassType: ContentCardClassType) {
    guard let idString = metaData[.idString] as? String,
      let createdAt = metaData[.created] as? Double,
      let isDismissable = metaData[.dismissable] as? Bool,
      let extras = metaData[.extras] as? [AnyHashable: Any],
      else { return nil }
 
    let contentCardData = ContentCardData(contentCardId: idString, contentCardClassType: contentCardClassType, createdAt: createdAt, isDismissable: isDismissable)
    let customObjectProperty = extras["YOUR-CUSTOM-OBJECT-PROPERTY"] as? String
           
    self.init(contentCardData: contentCardData, property: customObjectProperty)
  }
}

Tipos identificadores
La enumeración ContentCardClassType representa el valor class_type en el panel Braze. Este valor también se utiliza como identificador de filtro para mostrar tarjetas de contenido en distintos lugares.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum ContentCardClassType: Hashable {
  case yourValue
  case yourOtherValue
  ...
  case none
 
  init(rawType: String?) {
    switch rawType?.lowercased() {
    case "your_value": // these values much match the value set in the Braze dashboard
      self = .yourValue
    case "your_other_value": // these values much match the value set in the Braze dashboard
      self = .yourOtherValue
    ...
    default:
      self = .none
    }
  }
}

Inicializador de objetos personalizado
Los metadatos de un ABKContentCard se utilizan para rellenar las variables de tu objeto. Los pares clave-valor configurados en el panel de Braze se representan en el diccionario “extras”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (id _Nullable)initWithMetaData:(nonnull NSDictionary *)metaData classType:(enum ContentCardClassType)classType {
  self = [super init];
  if (self) {
    if ([metaData objectForKey:ContentCardKeyIdString] && [metaData objectForKey:ContentCardKeyCreated] && [metaData objectForKey:ContentCardKeyDismissible] && [metaData objectForKey:ContentCardKeyExtras]) {
      NSDictionary  *extras = metaData[ContentCardKeyExtras];
      NSString *idString = metaData[ContentCardKeyIdString];
      double createdAt = [metaData[ContentCardKeyCreated] doubleValue];
      BOOL isDismissible = metaData[ContentCardKeyDismissible];
 
      if ([extras objectForKey: @"YOUR-CUSTOM-PROPERTY")
        _customObjectProperty = extras[@"YOUR-CUSTOM-OBJECT-PROPERTY"];
 
      self.contentCardData = [[ContentCardData alloc] initWithIdString:idString classType:classType createdAt:createdAt isDismissible:isDismissible];
 
      return self;
    }
  }
  return nil;
}

Tipos identificadores
La enumeración ContentCardClassType representa el valor class_type en el panel Braze. Este valor también se utiliza como identificador de filtro para mostrar tarjetas de contenido en distintos lugares.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef NS_ENUM(NSInteger, ContentCardClassType) {
  ContentCardClassTypeNone = 0,
  ContentCardClassTypeYourValue,
  ContentCardClassTypeYourOtherValue,
  ...
};
 
+ (NSArray *)contentCardClassTypeArray {
  return @[ @"", @"your_value", @"your_other_value" ];
}
 
+ (ContentCardClassType)contentCardClassTypeForString:(NSString*)rawValue {
  if ([[self contentCardClassTypeArray] indexOfObject:rawValue] == NSNotFound) {
    return ContentCardClassTypeNone;
  } else {
    NSInteger value = [[self contentCardClassTypeArray] indexOfObject:rawValue];
    return (ContentCardClassType) value;
  }
}

Solicitud de tarjetas de contenido
Mientras el observador siga retenido en memoria, se puede esperar la devolución de llamada de notificación del SDK de Braze.

1
2
3
4
func loadContentCards() {
  BrazeManager.shared.addObserverForContentCards(observer: self, selector: #selector(contentCardsUpdated))
  BrazeManager.shared.requestContentCardsRefresh()
}

Manejo de la devolución de llamada SDK de las tarjetas de contenido
Reenvía la devolución de llamada de notificación al archivo de ayuda para analizar los datos de carga útil de tu(s) objeto(s) personalizado(s).

1
2
3
4
5
@objc func contentCardsUpdated(_ notification: Notification) {
  guard let contentCards = BrazeManager.shared.handleContentCardsUpdated(notification, for: [.yourValue]) as? [CustomObject],!contentCards.isEmpty else { return }
 
 // do something with your array of custom objects
}

Trabajar con tarjetas de contenido
El class_type se pasa como filtro para devolver sólo las tarjetas de contenido que tengan un class_type coincidente.

1
2
3
4
5
func handleContentCardsUpdated(_ notification: Notification, for classTypes: [ContentCardClassType]) -> [ContentCardable] {
  guard let updateIsSuccessful = notification.userInfo?[ABKContentCardsProcessedIsSuccessfulKey] as? Bool, updateIsSuccessful, let cards = contentCards else { return [] }
             
  return convertContentCards(cards, for: classTypes)
}

Solicitud de tarjetas de contenido
Mientras el observador siga retenido en memoria, se puede esperar la devolución de llamada de notificación del SDK de Braze.

1
2
3
4
- (void)loadContentCards {
  [[BrazeManager shared] addObserverForContentCards:self selector:@selector(contentCardsUpdated:)];
  [[BrazeManager shared] requestContentCardsRefresh];
}

Manejo de la devolución de llamada SDK de las tarjetas de contenido
Reenvía la devolución de llamada de notificación al archivo de ayuda para analizar los datos de carga útil de tu(s) objeto(s) personalizado(s).

1
2
3
4
5
6
- (void)contentCardsUpdated:(NSNotification *)notification {
  NSArray *classTypes = @[@(ContentCardClassTypeYourValue)];
  NSArray *contentCards = [[BrazeManager shared] handleContentCardsUpdated:notification forClassTypes:classTypes];
 
  // do something with your array of custom objects
}

Trabajar con tarjetas de contenido
El class_type se pasa como filtro para devolver sólo las tarjetas de contenido que tengan un class_type coincidente.

1
2
3
4
5
6
7
8
- (NSArray *)handleContentCardsUpdated:(NSNotification *)notification forClassType:(ContentCardClassType)classType {  
  BOOL updateIsSuccessful = [notification.userInfo[ABKContentCardsProcessedIsSuccessfulKey] boolValue];
  if (updateIsSuccessful) {
    return [self convertContentCards:self.contentCards forClassType:classType];
  } else {
    return @[];
  }
}

Trabajar con datos de carga útil
Recorre la matriz de tarjetas de contenido y sólo analiza las tarjetas que coincidan con class_type. La carga útil de una ABKContentCard se analiza en Dictionary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func convertContentCards(_ cards: [ABKContentCard], for classTypes: [ContentCardClassType]) -> [ContentCardable] {
  var contentCardables: [ContentCardable] = []
    
  for card in cards {
    let classTypeString = card.extras?[ContentCardKey.classType.rawValue] as? String
    let classType = ContentCardClassType(rawType: classTypeString)
    guard classTypes.contains(classType) else { continue }
       
    var metaData: [ContentCardKey: Any] = [:]
    switch card {
    case let banner as ABKBannerContentCard:
      metaData[.image] = banner.image
    case let captioned as ABKCaptionedImageContentCard:
      metaData[.title] = captioned.title
      metaData[.cardDescription] = captioned.cardDescription
      metaData[.image] = captioned.image
    case let classic as ABKClassicContentCard:
      metaData[.title] = classic.title
      metaData[.cardDescription] = classic.cardDescription
    default:
      break
    }
 
    metaData[.idString] = card.idString
    metaData[.created] = card.created
    metaData[.dismissible] = card.dismissible
    metaData[.urlString] = card.urlString
    metaData[.extras] = card.extras
    ...
    // other Content Card properties such as expiresAt, pinned, etc.
      
    if let contentCardable = contentCardable(with: metaData, for: classType) {
      contentCardables.append(contentCardable)
    }
  }
  return contentCardables
}

Inicializar tus objetos personalizados a partir de los datos de la carga útil de la tarjeta de contenido
La dirección class_type se utiliza para determinar cuál de tus objetos personalizados se inicializará a partir de los datos de carga útil.

1
2
3
4
5
6
7
8
9
10
11
func contentCardable(with metaData: [ContentCardKey: Any], for classType: ContentCardClassType) -> ContentCardable? {
  switch classType {
  case .yourValue:
    return CustomObject(metaData: metaData, classType: classType)
  case .yourOtherValue:
    return OtherCustomObject(metaData: metaData, classType: classType)
  ...
  default:
    return nil
  }
}

Trabajar con datos de carga útil
Recorre la matriz de tarjetas de contenido y sólo analiza las tarjetas que coincidan con class_type. La carga útil de una ABKContentCard se analiza en Dictionary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- (NSArray *)convertContentCards:(NSArray<ABKContentCard*> *)cards forClassType:(ContentCardClassType)classType {
  NSMutableArray *contentCardables = [[NSMutableArray alloc] init];      for (ABKContentCard *card in cards) {
    NSString *classTypeString = [card.extras objectForKey:ContentCardKeyClassType];
    ContentCardClassType cardClassType = [ContentCardData contentCardClassTypeForString: classTypeString];
    if (cardClassType != classType) { continue; }
     
    NSMutableDictionary *metaData = [[NSMutableDictionary alloc] init];
    if ([card isKindOfClass:[ABKBannerContentCard class]]) {
      ABKBannerContentCard *banner = (ABKBannerContentCard *)card;
      metaData[ContentCardKeyImage] = banner.image;
    } else if ([card isKindOfClass:[ABKCaptionedImageContentCard class]]) {
      ABKCaptionedImageContentCard *captioned = (ABKCaptionedImageContentCard *)card;
      metaData[ContentCardKeyTitle] = captioned.title;
      metaData[ContentCardKeyCardDescription] = captioned.cardDescription;
      metaData[ContentCardKeyImage] = captioned.image;
    } else if ([card isKindOfClass:[ABKClassicContentCard class]]) {
      ABKClassicContentCard *classic = (ABKClassicContentCard *)card;
      metaData[ContentCardKeyCardDescription] = classic.title;
      metaData[ContentCardKeyImage] = classic.image;
    }
     
    metaData[ContentCardKeyIdString] = card.idString;
    metaData[ContentCardKeyCreated] = [NSNumber numberWithDouble:card.created];
    metaData[ContentCardKeyDismissible] = [NSNumber numberWithBool:card.dismissible];
    metaData[ContentCardKeyUrlString] = card.urlString;
    metaData[ContentCardKeyExtras] = card.extras;
    ...
    // other Content Card properties such as expiresAt, pinned, etc.   
 
    id<ContentCardable> contentCardable = [self contentCardableWithMetaData:metaData forClassType:classType];
    if (contentCardable) {
      [contentCardables addObject:contentCardable];
    }
  }
 
  return contentCardables;
}

Inicializar tus objetos personalizados a partir de los datos de la carga útil de la tarjeta de contenido
La dirección class_type se utiliza para determinar cuál de tus objetos personalizados se inicializará a partir de los datos de carga útil.

1
2
3
4
5
6
7
8
9
10
11
- (id<ContentCardable>)contentCardableWithMetaData:(NSDictionary *)metaData forClassType:(ContentCardClassType)classType {
  switch (classType) {
    case ContentCardClassTypeYourValue:
      return [[CustomObject alloc] initWithMetaData:metaData classType:classType];
    case ContentCardClassTypeYourOtherValue:
      return nil;
    ...
    default:
      return nil;
  }
}

Casos prácticos

A continuación te presentamos tres casos de uso. Cada caso de uso ofrece una explicación detallada, fragmentos de código relevantes y una visión de cómo pueden verse y utilizarse las variables de la tarjeta de contenido en el panel Braze:

Tarjetas de contenido como contenido complementario

Puedes integrar fácilmente las tarjetas de contenido en una fuente existente, permitiendo que los datos de varias fuentes se carguen simultáneamente. Esto crea una experiencia cohesiva y armoniosa con las tarjetas de contenido Braze y el contenido de la fuente existente.

El ejemplo de la derecha muestra un UICollectionView con una lista híbrida de elementos que se rellenan mediante datos locales y tarjetas de contenido impulsadas por Braze. Con esto, las tarjetas de contenido pueden ser indistinguibles de los contenidos existentes.

Configuración del panel de control

Esta tarjeta de contenido se entrega mediante una campaña desencadenada por API con pares clave-valor desencadenados por API. Esto es ideal para campañas en las que los valores de la tarjeta dependen de factores externos para determinar qué contenido mostrar al usuario. Ten en cuenta que class_type debe conocerse en el momento de la configuración.

Los pares clave-valor para el caso de uso de las tarjetas de contenido suplementario. En este ejemplo, diferentes aspectos de la tarjeta, como  "tile_id","tile_deeplink", y  ,"tile_title" se configuran utilizando Liquid.

¿Listo para el análisis de registros?

Visita la sección siguiente para comprender mejor cómo debe ser el flujo de datos.

Tarjetas de contenido en un centro de mensajes


Las tarjetas de contenido pueden utilizarse en un formato de centro de mensajes en el que cada mensaje es su propia tarjeta. Cada mensaje del centro de mensajes se rellena mediante una carga útil de tarjeta de contenido, y cada tarjeta contiene pares clave-valor adicionales que potencian la UI/UX al hacer clic. En el siguiente ejemplo, un mensaje te dirige a una vista personalizada arbitraria, mientras que otro se abre en una vista web que muestra HTML personalizado.

Configuración del panel de control

Para los siguientes tipos de mensaje, el par clave-valor class_type debe añadirse a la configuración de tu panel. Los valores asignados aquí son arbitrarios, pero deben poder distinguirse entre tipos de clases. Estos pares clave-valor son los identificadores clave en los que se fija la aplicación para decidir a dónde ir cuando el usuario hace clic en un mensaje de buzón de entrada abreviado.

Los pares clave-valor para este caso de uso incluyen:

  • message_header configurado como Full Page
  • class_type configurado como message_full_page

Los pares clave-valor para este caso de uso incluyen:

  • message_header configurado como HTML
  • class_type configurado como message_webview
  • message_title

Este mensaje también busca un par clave-valor HTML, pero si trabajas con un dominio Web, también es válido un par clave-valor URL.

Más explicaciones

La lógica del centro de mensajes se rige por la dirección contentCardClassType que proporcionan los pares clave-valor de Braze. Con el método addContentCardToView puedes filtrar e identificar estos tipos de clases.

Utilizando class_type para el comportamiento al hacer clic
Cuando se hace clic en un mensaje, ContentCardClassType gestiona cómo debe rellenarse la siguiente pantalla.

1
2
3
4
5
6
7
8
9
10
func addContentCardToView(with message: Message) {
    switch message.contentCardData?.contentCardClassType {
      case .message(.fullPage):
        loadContentCardFullPageView(with: message as! FullPageMessage)
      case .message(.webView):
        loadContentCardWebView(with: message as! WebViewMessage)
      default:
        break
    }
}

Utilizando class_type para el comportamiento al hacer clic
Cuando se hace clic en un mensaje, ContentCardClassType gestiona cómo debe rellenarse la siguiente pantalla.

1
2
3
4
5
6
7
8
9
10
11
12
- (void)addContentCardToView:(Message *)message {
  switch (message.contentCardData.classType) {
    case ContentCardClassTypeMessageFullPage:
      [self loadContentCardFullPageView:(FullPageMessage *)message];
      break;
    case ContentCardClassTypeMessageWebview:
      [self loadContentCardWebView:(WebViewMessage *)message];
      break;
    default:
      break;
  }
}
¿Listo para el análisis de registros?

Visita la sección siguiente para comprender mejor cómo debe ser el flujo de datos.

En la esquina inferior izquierda de la pantalla aparece una tarjeta de contenido interactiva que muestra una promoción del 50%. Tras hacer clic, se aplicará una promoción al carrito.

Tarjetas interactivas de contenido


Las tarjetas de contenido pueden aprovecharse para crear experiencias dinámicas e interactivas para tus usuarios. En el ejemplo de la derecha, tenemos una ventana emergente de una tarjeta de contenido que aparece en el momento de la compra y que ofrece a los usuarios promociones de última hora.

Las tarjetas bien colocadas como ésta son una forma estupenda de dar a los usuarios un “empujoncito” hacia acciones específicas del usuario.


Configuración del panel de control

La configuración del panel para las tarjetas de contenido interactivas es sencilla. Los pares clave-valor para este caso de uso incluyen un discount_percentage configurado como el importe de descuento deseado y class_type configurado como coupon_code. Estos pares clave-valor son la forma en que se filtran las tarjetas de contenido específicas de cada tipo y se muestran en la pantalla de pago.

¿Listo para el análisis de registros?

Visita la sección siguiente para comprender mejor cómo debe ser el flujo de datos.

Personalización del modo oscuro

Por predeterminado, las vistas de la tarjeta de contenido responderán automáticamente a los cambios del modo oscuro en el dispositivo con un conjunto de colores temáticos.

Este comportamiento puede anularse como se detalla en nuestra guía de estilo personalizado.

Registro de impresiones, clics y rechazos

Tras ampliar tus objetos personalizados para que funcionen como tarjetas de contenido, el registro de métricas valiosas como impresiones, clics y rechazos es rápido. Esto puede hacerse utilizando un protocolo ContentCardable que haga referencia y proporcione datos a un archivo de ayuda para que sea registrado por el SDK de Braze.

Componentes de aplicación

Análisis de registros
Los métodos de registro pueden llamarse directamente desde objetos que cumplan el protocolo ContentCardable.

1
2
3
customObject.logContentCardImpression()
customObject.logContentCardClicked()
customObject.logContentCardDismissed()

Recuperar el ABKContentCard
El idString pasado desde tu objeto personalizado se utiliza para identificar la tarjeta de contenido asociada a los análisis de registro.

1
2
3
4
5
6
7
8
9
10
11
extension BrazeManager {
  func logContentCardImpression(idString: String?) {
    guard let contentCard = getContentCard(forString: idString) else { return }
 
    contentCard.logContentCardImpression()
  }
   
  private func getContentCard(forString idString: String?) -> ABKContentCard? {
    return contentCards?.first(where: { $0.idString == idString })
  }
}

Análisis de registros
Los métodos de registro pueden llamarse directamente desde objetos que cumplan el protocolo ContentCardable.

1
2
3
[customObject logContentCardImpression];
[customObject logContentCardClicked];
[customObject logContentCardDismissed];

Recuperar el ABKContentCard
El idString pasado desde tu objeto personalizado se utiliza para identificar la tarjeta de contenido asociada a los análisis de registro.

1
2
3
4
5
6
7
8
9
10
11
- (void)logContentCardImpression:(NSString *)idString {
  ABKContentCard *contentCard = [self getContentCard:idString];
  [contentCard logContentCardImpression];
}
 
- (ABKContentCard *)getContentCard:(NSString *)idString {
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self.idString == %@", idString];
  NSArray *filteredArray = [self.contentCards filteredArrayUsingPredicate:predicate];
 
  return filteredArray.firstObject;
}

Archivos de ayuda

ContentCardKey helper file
1
2
3
4
5
6
7
8
enum ContentCardKey: String {
  case idString
  case created
  case classType = "class_type"
  case dismissible
  case extras
  ...
}
1
2
3
4
5
6
static NSString *const ContentCardKeyIdString = @"idString";
static NSString *const ContentCardKeyCreated = @"created";
static NSString *const ContentCardKeyClassType = @"class_type";
static NSString *const ContentCardKeyDismissible = @"dismissible";
static NSString *const ContentCardKeyExtras = @"extras";
...
New Stuff!