বিষয়বস্তুতে যান

Setting up Pushwoosh InboxKit iOS

এই বিষয়বস্তু এখনও আপনার ভাষায় উপলব্ধ নয়।

Available since iOS SDK 7.0.40.

Pushwoosh InboxKit ships a modern UIKit inbox screen on top of the existing inbox backend. Three default cell layouts cover the common Braze-style content card shapes, inline CTA buttons handle the most common interactions, and the entire surface is open for subclassing if you need a bespoke look.

InboxKit feed showing banner, captioned and classic cards with inline buttons and unread indicators

Default InboxKit feed with banner, captioned, and classic cards.

When to use InboxKit

Anchor link to

Use InboxKit for any new iOS integration. It is the recommended replacement for the older Objective-C PushwooshInboxUI module.

InboxKit gives you:

  • Three built-in cell types — banner, captioned, classic — selected per message via actionParams["displayType"] from your push payload, or forced from code via attributes.forceCellKind.
  • Inline CTA buttons with a typed PushwooshInboxButtonAction enum (openURL, dismiss, markRead, custom). The SDK handles the first three automatically; your delegate routes custom to your own logic.
  • Pinning support: messages with actionParams["pinned"] == true float to the top of the feed and render a pin glyph.
  • Swipe-to-delete, pull-to-refresh, automatic mark-as-read on disappear — all toggleable via PushwooshInboxKitAttributes.
  • Persistent storage: deletes and read state survive a process restart even if the network call has not been acknowledged yet.
  • An open PushwooshInboxCell base class for fully custom layouts.

The server contract is unchanged — the same Pushwoosh inbox backend, payloads, and dashboard tooling work as before.

Choose your integration method

Anchor link to

Read custom data from a message

Anchor link to

To make a push appear in the inbox, the Messages API createMessage request must include inbox_image, inbox_date, or inbox_days — without one of those fields the push is delivered as a regular notification and never reaches the inbox feed. Free-form custom data goes under the data key, which the SDK delivers to the client as the u parameter:

POST https://api.pushwoosh.com/json/1.3/createMessage
{
"request": {
"application": "XXXXX-XXXXX",
"auth": "API_TOKEN",
"notifications": [{
"send_date": "now",
"ios_title": "Summer sale",
"content": "30% off everything — limited time only",
"inbox_image": "https://cdn.example.com/inbox/summer.png",
"inbox_days": 7,
"data": {
"promo_id": "SUMMER2026",
"screen": "promo_details"
},
"ios_root_params": {
"displayType": "captioned"
},
"platforms": [1]
}]
}
}

The SDK exposes that object on the inbox message through actionParams. Read it from the delegate when the user taps the row or an inline CTA:

extension MyInboxHost: PushwooshInboxKitDelegate {
func inboxKit(_ vc: PushwooshInboxKitViewController,
didSelect message: PWInboxMessageProtocol) -> Bool {
guard let params = message.actionParams as? [String: Any] else { return true }
// The custom `data` object arrives under the "u" key —
// either as a nested dictionary or as a JSON-encoded string,
// depending on how the payload was built upstream.
let custom: [String: Any]? = {
if let dict = params["u"] as? [String: Any] { return dict }
if let raw = params["u"] as? String,
let bytes = raw.data(using: .utf8),
let parsed = try? JSONSerialization.jsonObject(with: bytes) as? [String: Any] {
return parsed
}
return nil
}()
if let promoId = custom?["promo_id"] as? String {
navigateToPromo(promoId)
return false // we handled the tap; SDK should not run the default action
}
return true
}
}

The same actionParams["u"] lookup works inside inboxKit(_:didTapButton:onMessage:) for inline CTA buttons. For the typed CTA cases (openURL, dismiss, markRead) the SDK already performs the default action — return true to keep that behavior, or false to suppress it and run your own.

Add inline CTA buttons

Anchor link to

A message can carry up to three inline call-to-action buttons. Buttons live alongside other custom data inside data as a buttons array. The SDK auto-renders them inside the captioned and classic cells:

POST https://api.pushwoosh.com/json/1.3/createMessage
{
"request": {
"application": "XXXXX-XXXXX",
"auth": "API_TOKEN",
"notifications": [{
"send_date": "now",
"ios_title": "New promo card",
"content": "Tap a button to claim or save",
"inbox_image": "https://cdn.example.com/inbox/promo.png",
"inbox_days": 7,
"data": {
"promo_id": "SUMMER2026",
"buttons": [
{ "title": "Claim", "url": "https://example.com/promo/SUMMER2026" },
{ "title": "Read", "action": "markRead" },
{ "title": "Save", "action": "custom", "tag": "save_promo" }
]
},
"ios_root_params": { "displayType": "captioned" },
"platforms": [1]
}]
}
}

Each button object has these fields:

FieldTypeWhen
titlestringRequired. Visible button label.
urlstringA non-empty parseable URL produces an openURL action. The SDK opens it via UIApplication.shared.open unless your delegate suppresses it.
actionstringExplicit action token: dismiss (removes the message from the feed), markRead (marks the message read), or custom (host-handled). Case-insensitive.
Anything elseanyWhen action is custom, every key on the button object except title and action is forwarded to your delegate as the custom payload — agree on a key with the marketer (e.g. tag) and dispatch on it.

Resolution priority: explicit action token first, then url if non-empty, otherwise the button falls into custom carrying the full payload (minus title and action).

Intercept taps from your delegate. The button.action property is the typed PushwooshInboxButtonAction enum:

extension MyInboxHost: PushwooshInboxKitDelegate {
func inboxKit(_ vc: PushwooshInboxKitViewController,
didTapButton button: PushwooshInboxButton,
onMessage message: PWInboxMessageProtocol) -> Bool {
switch button.action {
case .openURL(let url):
// Default behavior is fine — let SDK open the URL.
return true
case .dismiss, .markRead:
// SDK handles both. Return false if you want to override.
return true
case .custom(let payload):
// Marketer-defined custom button. Dispatch on a key you agreed on.
if let tag = payload["tag"] as? String {
switch tag {
case "save_promo":
saveCurrentPromoLocally(message: message)
default:
break
}
}
return true // ignored for custom — SDK never runs a default action here
}
}
}