--- title: "Configure app cards" slug: "ios-configure-app-cards" description: "Integrate iOS App Cards (InsiderAppCards), retrieve campaigns, manage read/unread/delete status, and handle deeplinks and categories." updated: 2026-08-10T12:42:12Z published: 2026-08-10T12:42:12Z canonical: "academy.insiderone.com/ios-configure-app-cards" --- > ## Documentation Index > Fetch the complete documentation index at: https://academy.insiderone.com/llms.txt > Use this file to discover all available pages before exploring further. # Configure app cards The [App Cards](https://academy.insiderone.com/docs/app-cards) module provides a complete API for accessing and managing the Insider platform's app cards campaigns. It allows you to fetch marketing messages and notifications, track read/unread status, handle interactive buttons within messages, and observe app cards events in real time. Messages can contain rich content, including text, images, and interactive buttons with deeplink actions. > [!NOTE] > The InsiderAppCards class cannot be instantiated directly. Access it through the main Insider SDK via appCards / Insider.appCards(). ## Prerequisites - Insider iOS SDK (InsiderMobile), integrated and initialized in your app - Minimum iOS deployment target: **11.0** **Access** Obtain the InsiderAppCards instance from the main SDK: **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; ``` **Swift** ```swift let appCards = Insider.appCards() ``` ## API Reference ### Core Types #### InsiderAppCardCampaignsResponseModel Root container for app cards data. | Name | Type | Description | Required | | --- | --- | --- | --- | | appCards | [InsiderAppCardModel] | Array of all app cards in the campaigns | Yes | #### InsiderAppCardModel Represents a single app card. | Name | Type | Description | Required | | --- | --- | --- | --- | | appCardId | String | Yes | Unique identifier for the app card | | type | InsiderAppCardType | Yes | Type of the app card (.message or .image) | | isRead | Bool | Yes | Whether the app card has been read | | content | InsiderAppCardContentModel? | No | Text content (title and subtitle) | | images | [InsiderAppCardImageModel]? | No | Array of images associated with the app card | | buttons | [InsiderAppCardButtonModel]? | No | Array of action buttons | | deeplink | InsiderAppCardDeeplinkActionModel? | No | Deeplink action for app card tap | | categories | NSArray | No | The categories assigned to the card’s campaign. Empty array when the card has none. | **App card types** | Constant | Description | | --- | --- | | InsiderAppCardTypeMessage | Text-based message | | InsiderAppCardTypeImage | Image-based message | #### InsiderAppCardContentModel Textual content of an app card. | Name | Type | Description | Required | | --- | --- | --- | --- | | title | String | Main title text | Yes | | subtitle | String | Subtitle or description text | Yes | #### InsiderAppCardImageModel Image associated with an app card. | Name | Type | Description | Required | | --- | --- | --- | --- | | url | URL | URL of the image | Yes | | title | String? | Optional title for the image | No | | subtitle | String? | Optional subtitle or caption | No | #### InsiderAppCardButtonModel An interactive button within an app card. | Name | Type | Description | Required | | --- | --- | --- | --- | | buttonId | String | Unique identifier for the button | Yes | | appCardId | String | Unique identifier of the parent app card | Yes | | text | String | Display text shown on the button | Yes | | action | InsiderAppCardActionModel? | Action executed when the button is tapped | No | #### InsiderAppCardActionModel (Protocol) Protocol defining the interface for app card actions. | Name | Type | Description | Required | | --- | --- | --- | --- | | type | InsiderAppCardActionType | Type of the action | Yes | **Action types** | Constant | Description | | --- | --- | | InsiderAppCardActionTypeDeeplink | Deeplink navigation | | InsiderAppCardActionTypeAppReview | Prompt app store review | | InsiderAppCardActionTypeAppSettings | Open app settings | #### InsiderAppCardDeeplinkActionModel Deeplink action that navigates to a specific location. | Name | Type | Description | Required | | --- | --- | --- | --- | | JSONObject | [String: Any]? | JSON object containing deeplink data | No | | keysAndValues | [[String: Any]]? | Array of key-value dictionaries for deeplink data | No | **Deeplink URL types** | Constant | Description | | --- | --- | | InsiderAppCardDeeplinkURLTypeExternal | External browser URL | | InsiderAppCardDeeplinkURLTypeInternal | Internal webview URL | | InsiderAppCardDeeplinkURLTypeCustomScheme | Custom URL scheme | | InsiderAppCardDeeplinkURLTypeUnknown | Unknown or invalid URL | #### InsiderAppCardCategoryModel Model class representing a single category on a card. Categories are customer-defined metadata (e.g., promotion or onboarding) that you can use to drive per-card design or behavior in your app. | Name | Type | Nullable | Description | | --- | --- | --- | --- | | categoryId | NSInteger | No | The category identifier defined in Insider One | | name | NSString * | No | The category display name | > The property is named categoryId as id is a reserved keyword in Objective-C. The key on the wire is still id; the SDK maps it for you. > > Category names can be renamed by a campaign manager at any time. Use categoryId as the key in your logic and treat name as display text. #### InsiderAppCardsObserver (Protocol) Observer protocol for receiving app cards events. All methods are optional. | Method | Parameters | Description | | --- | --- | --- | | appCardsDidMarkAsRead | Set | Called when app cards are marked as read | | appCardsDidMarkAsUnread | Set | Called when app cards are marked as unread | | appCardsDidDeleteAppCards | Set | Called when app cards are deleted | | appCardsDidViewAppCardWithId | String | Called when an app card is viewed | | appCardsDidClickAppCardWithId | String | Called when an app card is clicked | | appCardsDidClickButtonWithId:inAppCardWithId | String, String | Called when a button in an app card is clicked | ### Campaigns Management #### getCampaigns Retrieves the user's app cards campaigns with all available messages. | Name | Type | Description | Required | | --- | --- | --- | --- | | completion | (InsiderAppCardCampaignsResponseModel, Error?) -> Void | Completion handler with campaigns model and optional error | Yes | > [!NOTE] > The completion handler is called on the main thread. Any in-flight campaigns requests are cancelled when this method is called. **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; [appCards getCampaigns:^(InsiderAppCardCampaignsResponseModel *campaigns, NSError *error) {    if (error) {        NSLog(@"Failed to fetch campaigns: %@", error.localizedDescription);        return;    }    for (InsiderAppCardModel *appCard in campaigns.appCards) {        NSLog(@"App Card ID: %@, Read: %d", appCard.appCardId, appCard.isRead);    } }]; ``` **Swift** ```swift let appCards = Insider.appCards() appCards.getCampaigns { campaigns, error in    if let error = error {        print("Failed to fetch campaigns: \(error.localizedDescription)")        return    }    for appCard in campaigns.appCards {        print("App Card ID: \(appCard.appCardId), Read: \(appCard.isRead)")    } } ``` #### markAsRead Marks specified app cards as read. Synchronizes with the Insider backend. | Name | Type | Description | Required | | --- | --- | --- | --- | | appCardIds | Set | Set of app card identifiers to mark as read | Yes | | completion | (Error?) -> Void | Completion handler with optional error | Yes | **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; NSSet *ids = [NSSet setWithArray:@[@"card_123", @"card_456"]]; [appCards markAsRead:ids completion:^(NSError *error) {    if (error) {        NSLog(@"Failed: %@", error.localizedDescription);    } else {        NSLog(@"App cards marked as read");    } }]; ``` **Swift** ```swift let appCards = Insider.appCards() appCards.markAsRead(appCardIds: ["card_123", "card_456"]) { error in    if let error = error {        print("Failed: \(error.localizedDescription)")    } else {        print("App cards marked as read")    } } ``` #### markAsUnread Marks specified app cards as unread. Synchronizes with the Insider backend. | Name | Type | Description | Required | | --- | --- | --- | --- | | appCardIds | Set | Set of app card identifiers to mark as unread | Yes | | completion | (Error?) -> Void | Completion handler with optional error | Yes | **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; NSSet *ids = [NSSet setWithArray:@[@"card_123"]]; [appCards markAsUnread:ids completion:^(NSError *error) {    if (error) {        NSLog(@"Failed: %@", error.localizedDescription);    } else {        NSLog(@"App cards marked as unread");    } }]; ``` **Swift** ```swift let appCards = Insider.appCards() appCards.markAsUnread(appCardIds: ["card_123"]) { error in    if let error = error {        print("Failed: \(error.localizedDescription)")    } else {        print("App cards marked as unread")    } } ``` #### deleteAppCards Permanently deletes specified app cards. Synchronizes with the Insider backend. | Name | Type | Description | Required | | --- | --- | --- | --- | | appCardIds | Set | Set of app card identifiers to delete | Yes | | completion | (Error?) -> Void | Completion handler with optional error | Yes | **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; NSSet *ids = [NSSet setWithArray:@[@"card_123", @"card_456"]]; [appCards deleteAppCards:ids completion:^(NSError *error) {    if (error) {        NSLog(@"Failed: %@", error.localizedDescription);    } else {        NSLog(@"App cards deleted successfully");    } }]; ``` **Swift** ```swift let appCards = Insider.appCards() appCards.delete(appCardIds: ["card_123", "card_456"]) { error in    if let error = error {        print("Failed: \(error.localizedDescription)")    } else {        print("App cards deleted successfully")    } } ``` #### categories Holds the categories of the card. The value is never nil. A card without categories exposes an empty array, so you can iterate it without a nil check. | Returns | Type | Description | | --- | --- | --- | | categories | NSArray | A non-nil array of the card’s categories | **Methods** **Objective-C** ```objectivec @property (nonatomic, copy) NSArray *categories; ``` **Swift** ```swift var categories: [InsiderAppCardCategoryModel] ``` **Examples** A card can belong to more than one category, so the list may contain multiple elements. **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; [appCards getCampaigns:^(InsiderAppCardCampaignsResponseModel *campaigns,                         NSError *error) {    if (error != nil) {        NSLog(@"Failed to get campaigns: %@", error.localizedDescription);        return;    }    for (InsiderAppCardModel *card in campaigns.appCards) {        NSArray *categories = card.categories;        if (categories.count == 0) {            NSLog(@"No category for card: %@", card.appCardId);            continue;        }        for (InsiderAppCardCategoryModel *category in categories) {            NSLog(@"Category: %ld - %@",                  (long)category.categoryId, category.name);        }    } }]; ``` **Swift** ```swift Task { @MainActor in    do {        let campaigns = try await Insider.appCards().getCampaigns()        for card in campaigns.appCards {            let categories = card.categories            if categories.isEmpty {                print("No category for card: \(card.appCardId)")                continue            }            for category in categories {                print("Category: \(category.categoryId) - \(category.name)")            }        }    } catch {        print("Failed to get campaigns: \(error)")    } } ``` ### App Card Interaction #### viewAppCard Records a view event for an app card. Call this when a user views an app card in the UI. | Name | Type | Description | Required | | --- | --- | --- | --- | | appCard | InsiderAppCardModel | The app card model that was viewed | Yes | **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; [appCards viewAppCard:appCard]; ``` **Swift** ```swift let appCards = Insider.appCards() appCards.view(appCard: appCard) ``` > [!TIP] > You can also call view directly on the app card model: [appCard view] / appCard.view() #### clickAppCard Records a click event for an app card. Executes the app card's deeplink action (if present). | Name | Type | Description | Required | | --- | --- | --- | --- | | appCard | InsiderAppCardModel | The app card model that was clicked | Yes | **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; [appCards clickAppCard:appCard]; ``` **Swift** ```swift let appCards = Insider.appCards() appCards.click(appCard: appCard) ``` > [!TIP] > You can also call click directly on the app card model: [appCard click] / appCard.click() #### clickAppCardButton Records a click event for a button within an app card. Executes the button's action (if present). | Name | Type | Description | Required | | --- | --- | --- | --- | | button | InsiderAppCardButtonModel | The button model that was clicked | Yes | **Objective-C** ```objectivec InsiderAppCards *appCards = [Insider appCards]; for (InsiderAppCardButtonModel *button in appCard.buttons) {    // Handle button tap in your UI    [appCards clickAppCardButton:button]; } ``` **Swift** ```swift let appCards = Insider.appCards() for button in appCard.buttons ?? [] {    // Handle button tap in your UI    appCards.click(appCardButton: button) } ``` > [!TIP] > You can also call click directly on the button model: [button click] / button.click() ### App Card Model Methods ### markAsRead (on app card) Marks an individual app card as read. For bulk operations, use InsiderAppCards.markAsRead:completion: instead. **Objective-C** ```objectivec [appCard markAsRead:^(NSError *error) {    if (!error) {        NSLog(@"App card marked as read");    } }]; ``` **Swift** ```swift appCard.markAsRead { error in    if error == nil {        print("App card marked as read")    } } ``` #### markAsUnread (on app card) Marks an individual app card as unread. For bulk operations, use InsiderAppCards.markAsUnread:completion: instead. **Objective-C** ```objectivec [appCard markAsUnread:^(NSError *error) {    if (!error) {        NSLog(@"App card marked as unread");    } }]; ``` **Swift** ```swift appCard.markAsUnread { error in    if error == nil {        print("App card marked as unread")    } } ``` ### Deeplink Methods #### deeplinkURL Constructs and returns the deeplink URL from the action model's data. #### deeplinkURLType Returns the type classification of the deeplink URL. **Objective-C** ```objectivec InsiderAppCardDeeplinkActionModel *deeplink = appCard.deeplink; if (deeplink) {    NSURL *url = [deeplink deeplinkURL];    InsiderAppCardDeeplinkURLType urlType = [deeplink deeplinkURLType];    if ([urlType isEqualToString:InsiderAppCardDeeplinkURLTypeExternal]) {        [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];    } } ``` **Swift** ```swift if let deeplink = appCard.deeplink {    let url = deeplink.deeplinkURL()    let urlType = deeplink.deeplinkURLType()    if urlType == InsiderAppCardDeeplinkURLTypeExternal, let url = url {        UIApplication.shared.open(url)    } } ``` ### Error Handling The InsiderAppCardsError domain provides typed error codes for app cards operations. **Error domain**: InsiderAppCardsErrorDomain | Error Code | Value | Description | | --- | --- | --- | | InsiderAppCardsErrorCodeUnknown | 0 | Unknown error | | InsiderAppCardsErrorCodeSdkNotInitialized | 1 | SDK is not initialized | | InsiderAppCardsErrorCodeInvalidParameter | 2 | Invalid parameter provided | | InsiderAppCardsErrorCodeNetworkError | 3 | Network request failed | | InsiderAppCardsErrorCodeServerError | 4 | Server returned an error | | InsiderAppCardsErrorCodeParseError | 5 | Response parsing failed | ### Observer InsiderAppCards inherits from InsiderObservable, providing a type-safe observer mechanism for reacting to app cards events. #### addObserver / removeObserver | Method | Parameter | Description | | --- | --- | --- | | addObserver | InsiderAppCardsObserver | Registers an observer (weak reference) | | removeObserver | InsiderAppCardsObserver | Unregisters a previously added observer | #### Observer Protocol Methods All methods in InsiderAppCardsObserver are optional. **Objective-C** ```objectivec @protocol InsiderAppCardsObserver @optional - (void)appCardsDidMarkAsRead:(NSSet *)appCardIds; - (void)appCardsDidMarkAsUnread:(NSSet *)appCardIds; - (void)appCardsDidDeleteAppCards:(NSSet *)appCardIds; - (void)appCardsDidViewAppCardWithId:(NSString *)appCardId; - (void)appCardsDidClickAppCardWithId:(NSString *)appCardId; - (void)appCardsDidClickButtonWithId:(NSString *)buttonId inAppCardWithId:(NSString *)appCardId; @end ``` **Swift** ```swift @objc protocol InsiderAppCardsObserver: NSObjectProtocol {    @objc optional func appCardsDidMarkAsRead(_ appCardIds: Set)    @objc optional func appCardsDidMarkAsUnread(_ appCardIds: Set)    @objc optional func appCardsDidDeleteAppCards(_ appCardIds: Set)    @objc optional func appCardsDidViewAppCard(withId appCardId: String)    @objc optional func appCardsDidClickAppCard(withId appCardId: String)    @objc optional func appCardsDidClickButton(withId buttonId: String, inAppCardWithId appCardId: String) } ``` #### Observer example **Objective-C** ```objectivec @interface MyViewController () @end @implementation MyViewController - (void)viewDidLoad {    [super viewDidLoad];    [[Insider appCards] addObserver:self]; } - (void)dealloc {    [[Insider appCards] removeObserver:self]; } - (void)appCardsDidMarkAsRead:(NSSet *)appCardIds {    NSLog(@"App cards marked as read: %@", appCardIds); } - (void)appCardsDidClickAppCardWithId:(NSString *)appCardId {    NSLog(@"App card clicked: %@", appCardId); } @end ``` **Swift** ```swift class MyViewController: UIViewController, InsiderAppCardsObserver {    override func viewDidLoad() {        super.viewDidLoad()        Insider.appCards().addObserver(self)    }    deinit {        Insider.appCards().removeObserver(self)    }    func appCardsDidMarkAsRead(_ appCardIds: Set) {        print("App cards marked as read: \(appCardIds)")    }    func appCardsDidClickAppCard(withId appCardId: String) {        print("App card clicked: \(appCardId)")    } } ``` ## A complete example A complete example showing how to fetch, display, and interact with app cards. **Objective-C** ```objectivec @interface AppCardsViewController () @property (nonatomic, strong) InsiderAppCards *appCards; @property (nonatomic, strong) NSArray *cards; @end @implementation AppCardsViewController - (void)viewDidLoad {    [super viewDidLoad];    self.appCards = [Insider appCards];    [self.appCards addObserver:self];    [self fetchCampaigns]; } - (void)dealloc {    [self.appCards removeObserver:self]; } - (void)fetchCampaigns {    [self.appCards getCampaigns:^(InsiderAppCardCampaignsResponseModel *campaigns, NSError *error) {        if (error) {            NSLog(@"Error: %@", error.localizedDescription);            return;        }        self.cards = campaigns.appCards;        [self.tableView reloadData];    }]; } - (void)didSelectAppCard:(InsiderAppCardModel *)appCard {    // Track view    [self.appCards viewAppCard:appCard];    // Mark as read    [appCard markAsRead:^(NSError *error) {        if (!error) {            [self.tableView reloadData];        }    }];    // Handle deeplink if present    if (appCard.deeplink) {        NSURL *url = [appCard.deeplink deeplinkURL];        if (url) {            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];        }    } } - (void)didTapButton:(InsiderAppCardButtonModel *)button {    [self.appCards clickAppCardButton:button]; } - (void)deleteAppCard:(InsiderAppCardModel *)appCard {    NSSet *ids = [NSSet setWithObject:appCard.appCardId];    [self.appCards deleteAppCards:ids completion:^(NSError *error) {        if (!error) {            [self fetchCampaigns];        }    }]; } #pragma mark - InsiderAppCardsObserver - (void)appCardsDidMarkAsRead:(NSSet *)appCardIds {    [self.tableView reloadData]; } - (void)appCardsDidDeleteAppCards:(NSSet *)appCardIds {    [self.tableView reloadData]; } @end ``` **Swift** ```swift class AppCardsViewController: UIViewController, InsiderAppCardsObserver {    private let appCards = Insider.appCards()    private var cards: [InsiderAppCardModel] = []    override func viewDidLoad() {        super.viewDidLoad()        appCards.addObserver(self)        fetchCampaigns()    }    deinit {        appCards.removeObserver(self)    }    private func fetchCampaigns() {        appCards.getCampaigns { campaigns, error in            if let error = error {                print("Error: \(error.localizedDescription)")                return            }            self.cards = campaigns.appCards            self.tableView.reloadData()        }    }    func didSelectAppCard(_ appCard: InsiderAppCardModel) {        // Track view        appCards.view(appCard: appCard)        // Mark as read        appCard.markAsRead { error in            if error == nil {                self.tableView.reloadData()            }        }        // Handle deeplink if present        if let deeplink = appCard.deeplink, let url = deeplink.deeplinkURL() {            UIApplication.shared.open(url)        }    }    func didTapButton(_ button: InsiderAppCardButtonModel) {        appCards.click(appCardButton: button)    }    func deleteAppCard(_ appCard: InsiderAppCardModel) {        appCards.delete(appCardIds: [appCard.appCardId]) { error in            if error == nil {                self.fetchCampaigns()            }        }    }    // MARK: - InsiderAppCardsObserver    func appCardsDidMarkAsRead(_ appCardIds: Set) {        tableView.reloadData()    }    func appCardsDidDeleteAppCards(_ appCardIds: Set) {        tableView.reloadData()    } } ```