> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS In-App Messages

> Trigger-based banners, modals, and fullscreen messages

<Info>
  **Supported on:** iOS SDK `1.0.61+`
</Info>

<Tip>
  You can A/B test this in-app message with no extra code — create an experiment on it in the Console and the SDK serves the assigned variant automatically. See [Servable Surface Experiments](/sdks/ios/experiments#servable-surface-experiments).
</Tip>

In-app messages are configured in the AppDNA Console and displayed automatically by the SDK when trigger conditions are met. **No code is required** to show messages -- the SDK evaluates triggers on every tracked event and presents the message if conditions match.

## How It Works

1. You create an in-app message in the Console with content, layout, and trigger rules.
2. The message definition is synced to the SDK via the config bundle.
3. On every `track()` call (including auto-tracked events), the SDK evaluates all active message triggers.
4. If conditions match, the message is presented automatically.

<Info>
  In-app messages are fully server-driven. You can change the message content, trigger rules, and audience without an app update.
</Info>

## Message Types

| Type         | Description                                  |
| ------------ | -------------------------------------------- |
| `banner`     | Small bar at the top or bottom of the screen |
| `modal`      | Centered overlay with a dimmed background    |
| `fullscreen` | Full-screen takeover                         |
| `tooltip`    | Small popup anchored to a UI element         |

## Triggers

Messages trigger based on events and optional conditions, configured in the Console:

* **Event match** -- e.g., trigger on `workout_completed`
* **Property conditions** -- e.g., `duration >= 30`
* **Frequency** -- `once`, `once_per_session`, or `every_time`
* **Delay** -- wait N seconds after the trigger event before showing

## Module Access

```swift theme={null}
let messages = AppDNA.inAppMessages
```

### Module Methods

| Method            | Signature                                              | Description                          |
| ----------------- | ------------------------------------------------------ | ------------------------------------ |
| `suppressDisplay` | `suppressDisplay(_ suppress: Bool)`                    | Suppress or resume in-app messages   |
| `setDelegate`     | `setDelegate(_ delegate: AppDNAInAppMessageDelegate?)` | Set a delegate for message callbacks |

## Suppressing Messages

Suppress messages during critical flows like checkout or onboarding to avoid interrupting the user:

```swift theme={null}
// Suppress during purchase flow
AppDNA.inAppMessages.suppressDisplay(true)
startPurchaseFlow()

// Resume after purchase completes
func purchaseDidComplete() {
    AppDNA.inAppMessages.suppressDisplay(false)
}
```

## AppDNAInAppMessageDelegate

All 4 methods on this delegate fire from `MessageManager` for every in-app message lifecycle event. Register your delegate via `AppDNA.inAppMessages.setDelegate(...)`.

**`shouldShowMessage` is a true veto.** The SDK calls it BEFORE constructing the view or tracking the `in_app_message_shown` analytics event. If you return `false`, the message is suppressed entirely — no view, no analytics. The protocol's default extension returns `true`, so hosts that don't implement this method continue to see all messages.

Implement the delegate to respond to message lifecycle events:

```swift theme={null}
protocol AppDNAInAppMessageDelegate {
    func onMessageShown(messageId: String, trigger: String)
    func onMessageAction(messageId: String, action: String, data: [String: Any]?)
    func onMessageDismissed(messageId: String)
    func shouldShowMessage(messageId: String) -> Bool
}
```

### Example Implementation

```swift theme={null}
class MessageHandler: AppDNAInAppMessageDelegate {
    func onMessageShown(messageId: String, trigger: String) {
        print("Message shown: \(messageId) (trigger: \(trigger))")
    }

    func onMessageAction(messageId: String, action: String, data: [String: Any]?) {
        if let url = data?["url"] as? String {
            // Handle deep link or URL action
            navigate(to: url)
        }
    }

    func onMessageDismissed(messageId: String) {
        print("Message dismissed: \(messageId)")
    }

    func shouldShowMessage(messageId: String) -> Bool {
        // Return false to prevent the message from being shown
        return true
    }
}

AppDNA.inAppMessages.setDelegate(MessageHandler())
```

## Rich Media

In-app messages support rich media content configured in the Console:

* **Lottie animations** -- animated hero images or backgrounds
* **Video** -- inline video with optional autoplay and looping
* **Icon buttons** -- CTA buttons with icon references (Lucide, SF Symbols, Material, or emoji)
* **Haptic feedback** -- triggered on message display or button taps
* **Particle effects** -- confetti, sparkles, or other effects on message actions
* **Blur backdrop** -- glassmorphism-style blurred background for modals

See the [Rich Media](/sdks/ios/rich-media) guide for details on supported formats and configuration.

## Auto-Tracked Events

| Event                      | Trigger                            |
| -------------------------- | ---------------------------------- |
| `in_app_message_shown`     | An in-app message is displayed     |
| `in_app_message_clicked`   | User taps an action in the message |
| `in_app_message_dismissed` | Message is closed                  |

## Full Example

```swift theme={null}
import AppDNASDK

class AppCoordinator: AppDNAInAppMessageDelegate {
    func setup() {
        AppDNA.inAppMessages.setDelegate(self)
    }

    func startOnboarding() {
        // Suppress messages during onboarding
        AppDNA.inAppMessages.suppressDisplay(true)
        presentOnboardingFlow()
    }

    func onboardingDidFinish() {
        // Resume messages after onboarding
        AppDNA.inAppMessages.suppressDisplay(false)
    }

    // MARK: - AppDNAInAppMessageDelegate

    func onMessageShown(messageId: String, trigger: String) {
        // Optionally track in your own analytics
    }

    func onMessageAction(messageId: String, action: String, data: [String: Any]?) {
        switch action {
        case "deep_link":
            if let url = data?["url"] as? String { navigate(to: url) }
        case "dismiss":
            break
        default:
            break
        }
    }

    func onMessageDismissed(messageId: String) {
        // Message closed
    }

    func shouldShowMessage(messageId: String) -> Bool {
        return true
    }
}
```

<Note>
  In-app messages are created in the Console under **Engagement > In-App Messages**. Design the message, set trigger rules, and publish. The SDK handles everything else.
</Note>
