In-App Automation

In-app messaging can improve your engagement strategy by providing messages that can be customized to be visually consistent with the rest of your app.

In-app messages appear regardless of a user’s opt-in/out status for notifications. While standard in-app messages appear as banners, In-App Automation messages have various style and layout options. They are stored on the user’s device then displayed according to the triggers you define.

Most customization can be accomplished through the dashboard composer, however more advanced customization can be obtained by modifying the Airship SDK. This guide outlines how to customize iOS in-app messages.

In-App Messaging

In Airship SDK 13.0+, in-app messaging services are available as part of the Airship framework as well as a standalone framework called AirshipAutomation.

To access the in-app messaging from the Airship framework, simply import Airship where necessary.

@import Airship;
import AirshipExtendedActions

To access in-app messaging from the AirshipMessageCenter standalone framework, you must add a separate import statement in your code, as shown below:

@import AirshipAutomation;
import AirshipAutomation

Feature enablement

The in-app automation component can be enabled or disabled. When disabled it prevents any automations from executing and any events from counting towards a trigger’s goal.

Disabling the automations:

UAInAppAutomation.shared.enabled = NO;
InAppAutomation.shared.isEnabled = false

Pausing automations

Pausing is similar to disabling, but automations can continue to be triggered and queued up for execution. This is useful for preventing in-app message displays on screens where it would be detrimental to the user experience, such as splash screens, settings screens, or landing pages.

Pausing the manager:

UAInAppAutomation.shared.paused = YES;
InAppAutomation.shared.isPaused = true

Display interval

The display interval controls the amount of time to wait before the manager is able to display the next triggered in-app message. The default value is set to 30 seconds but can be adjusted to any amount of time in seconds.

Setting the display interval to 10 seconds:

UAInAppAutomation.shared.inAppMessageManager.displayInterval = 10;
InAppAutomation.shared.inAppMessageManager.displayInterval = 10

Implementing the UAInAppMessagingDelegate protocol

Example delegate:

- (UAInAppMessage *)extendMessage:(UAInAppMessage *)message {
    // Can be used to modify the message before it is displayed
    return message;
}

-(void)messageWillBeDisplayed:(UAInAppMessage \*)message scheduleID:(NSString \*)scheduleID {
  // Message displayed
}

-(void)messageFinishedDisplaying:(UAInAppMessage \*)message scheduleID:(NSString \*)scheduleID resolution:(UAInAppMessageResolution \*)resolution {
  // Message finished
}

- (UIWindowScene *)sceneForMessage:(UAInAppMessage *)message defaultScene:(nullable UIWindowScene *)defaultScene) {
 // Allows overriding choice of message scene (iOS 13 +)
}
func extend(_ message: InAppMessage) -> InAppMessage {
    // Can be used to modify the message before it is displayed
    return message
}

func messageWillBeDisplayed(_ message: InAppMessage, scheduleID: String) {
  // Message displayed
}

func messageFinishedDisplaying(_ message: InAppMessage, scheduleID: String, resolution: UAInAppMessageResolution) {
    // Message finished
}

func scene(for message: InAppMessage, defaultScene: UIWindowScene?) -> UIWindowScene {
 // Allows overriding choice of message scene (iOS 13 +)
}

Setting the delegate:

UAInAppAutomation.shared.inAppMessageManager.delegate = automationDelegate;
InAppAutomation.shared.inAppMessageManager.delegate = automationDelegate

The InAppMessagingDelegate can be implemented to receive callback when a message is displayed, finished displaying, and to modify the message before being displayed.

Fonts

Custom Fonts

Fonts added to the app bundle are available for use with in-app messaging. To add fonts, please read the The UIKit Custom Fonts Guide.

Dynamic fonts With HTML in-app messages

Most In-App message styles support automatically scaling fonts through the use of Dynamic Type. However, automatically scaling fonts in HTML In-App messages requires you to use the following Apple system fonts when specifying the CSS font property:

  • -apple-system-body
  • -apple-system-headline
  • -apple-system-subheadline
  • -apple-system-caption1
  • -apple-system-caption2
  • -apple-system-footnote
  • -apple-system-short-body
  • -apple-system-short-headline
  • -apple-system-short-subheadline
  • -apple-system-short-caption1
  • -apple-system-short-footnote
  • -apple-system-tall-body

For example, to have the HTML body default to the Apple system font body style:

body {
 font: -apple-system-body; // available on Apple devices only
}

For more information about dynamic type, please see this WWDC video.

Customization

In-app messages are fully customizable. Minor modifications can be accomplished by overriding the styles with a plist, but more advanced customizations can employ an adapter for the given message type.

Styles

Plists can be used to modify any of the default message styles that the SDK provides. Each message type can be customized with a different plist:

  • Banner: UAInAppMessageBannerStyle.plist
  • HTML: UAInAppMessageHTMLStyle.plist
  • FullScreen: UAInAppMessageFullScreenStyle.plist
  • Modal: UAInAppMessageModalStyle.plist

Individual messages can be customized by setting a custom factory block on the in-app message which configures a display adapter with the appropriate style:

[UAInAppAutomation.shared.inAppMessageManager setFactoryBlock:^id<UAInAppMessageAdapterProtocol> (UAInAppMessage *message) {
    UAInAppMessageHTMLAdapter *adapter = [UAInAppMessageHTMLAdapter adapterForMessage:message];
    adapter.style = [UAInAppMessageHTMLStyle style];
    adapter.style.additionalPadding = [UAPadding paddingWithTop:20 bottom:20 leading:0 trailing:0];
    return adapter;
} forDisplayType:UAInAppMessageDisplayTypeHTML];
InAppAutomation.shared.inAppMessageManager.setFactoryBlock({ (message) -> InAppMessageAdapterProtocol in
    let adapter = InAppMessageHTMLAdapter.adapter(for:message)
    let style = InAppMessageHTMLStyle()
    style.additionalPadding = Padding(20, 20, 0, 0)
    adapter.style = style
    return adapter
}, for: .HTML)
#### Plist values

*Banner*

- `additionalPadding`: _Padding_. Adds padding around the view.
- `headerStyle`: _Text Style_. Customizes the message's header.
- `bodyStyle`: _Text Style_. Customizes the message's body.
- `mediaStyle`: _Media Style_. Customizes the message's media.
- `buttonStyle`: _Buttons Style_. Customzies the message's buttons.
- `maxWidth`: _Points_. Max width.

*FullScreen*

- `headerStyle`: _Text Style_. Customizes the banner's header.
- `bodyStyle`: _Text Style_. Customizes the banner's body.
- `mediaStyle`: _Media Style_. Customizes the banner's media.
- `buttonStyle`: _Buttons Style_. Customzies the banner's buttons.
- `dismissIconResource`: String. Resource name for a custom dismiss icon.

*Modal*

- `additionalPadding`: _Padding_. Adds padding around the view.
- `headerStyle`: _Text Style_. Customizes the banner's header.
- `bodyStyle`: _Text Style_. Customizes the banner's body.
- `mediaStyle`: _Media Style_. Customizes the banner's media.
- `buttonStyle`: _Buttons Style_. Customzies the banner's buttons.
- `dismissIconResource`: String. Resource name for a custom dismiss icon.
- `maxWidth`: _Points_. Max width.
- `maxHeight`: _Points_. Max height.

*HTML*

- `additionalPadding`: _Padding_. Adds padding around the view.
- `dismissIconResource`: String. Resource name for a custom dismiss icon.
- `maxWidth`: _Points_. Max width.
- `maxHeight`: _Points_. Max height.

*Padding*

- `top`: _Points_. Top padding.
- `botttom`: _Points_. Bottom padding.
- `leading`: _Points_. Leading padding.
- `trailing`: _Points_. Trailing padding.

*Buttons Style*

- `additionalPadding`: _Padding_. Adds padding around the button area.
- `buttonHeight`: _Points_. Button height.
- `stackedButtonSpacing`: _Points_. Button spacing in the stacked layout.
- `separatedButtonSpacing`: _Points_. Button spacing in the separated layout.
- `borderWidth`: _Points_. Button's border width.
- `buttonTextStyle`: _Text Style_. Text style for each button.

*Text Style*

- `additionalPadding`: _Padding_. Adds padding around the view.
- `letterSpacing`: _Points_. Spacing between the letters.
- `lineSpacing`: _Points_. Spacing between lines.

*Media Style*

- `additionalPadding`: _Padding_. Adds padding around the view.


### Custom adapters

Providing an adapter allows defining the behavior of the custom type or overriding
any of the default message types. The adapter will be created by the in-app messaging
manager when a message's schedule is triggered. Once created, the adapter will be
called to first prepare the in-app message, giving the adapter time to download
any resources such as images. After the adapter prepares the message, the adapter
will be called to display the message.

After the message is displayed, the caller of the display method must be notified
that the message is finished displaying by passing a `UAInAppMessageResolution` into
the display method's completion handler. This will allow for subsequent in-app
messages to be displayed.

> **Example custom adapter:**

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-obj-c" data-lang="obj-c"><span style="color:#66d9ef">@implementation</span> <span style="color:#a6e22e">CustomInAppMessageAdapter</span>

+(<span style="color:#66d9ef">instancetype</span>)<span style="color:#a6e22e">adapterForMessage:</span>(UAInAppMessage <span style="color:#f92672">*</span>)message {
    <span style="color:#66d9ef">return</span> [[CustomInAppMessageAdapter alloc] initWithMessage:message];
}

-(<span style="color:#66d9ef">instancetype</span>)<span style="color:#a6e22e">initWithMessage:</span>(UAInAppMessage <span style="color:#f92672">*</span>)message {
    self <span style="color:#f92672">=</span> [super init];

    <span style="color:#66d9ef">if</span> (self) {
        self.message <span style="color:#f92672">=</span> message;
    }

    <span style="color:#66d9ef">return</span> self;
}

- (<span style="color:#66d9ef">void</span>)<span style="color:#a6e22e">prepareWithAssets:</span>(nonnull UAInAppMessageAssets <span style="color:#f92672">*</span>)assets
        <span style="color:#a6e22e">completionHandler:</span>(nonnull <span style="color:#66d9ef">void</span> (<span style="color:#f92672">^</span>)(UAInAppMessagePrepareResult))completionHandler {

    <span style="color:#75715e">// Download any resources for the in-app message before displaying
</span><span style="color:#75715e"></span>
    <span style="color:#75715e">// Call the completion handler with the correct result
</span><span style="color:#75715e"></span>    completionHandler(UAInAppMessagePrepareResultSuccess)
}

-(<span style="color:#66d9ef">BOOL</span>)<span style="color:#a6e22e">isReadyToDisplay</span> {
    <span style="color:#75715e">// Return ready state
</span><span style="color:#75715e"></span>    <span style="color:#66d9ef">return</span> true
}

-(<span style="color:#66d9ef">void</span>)<span style="color:#a6e22e">display:</span>(<span style="color:#66d9ef">void</span> (<span style="color:#f92672">^</span>)(UAInAppMessageResolution <span style="color:#f92672">*</span>))completionHandler {
    <span style="color:#75715e">// Display the in-app message
</span><span style="color:#75715e"></span>
    <span style="color:#75715e">// Create a message resolution object corresponding to the correct resolution type
</span><span style="color:#75715e"></span>    UAInAppMessageResolution <span style="color:#f92672">*</span>messageResolution <span style="color:#f92672">=</span> [UAInAppMessageResolution messageClickResolution];

    <span style="color:#75715e">// Call the completion handler with the correct resolution
</span><span style="color:#75715e"></span>    completionHandler(messageResolution)
}

<span style="color:#66d9ef">@end</span></code></pre></div>

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">final</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CustomAdapter</span> : NSObject, InAppMessageAdapterProtocol {

    <span style="color:#66d9ef">var</span> message: InAppMessage

    <span style="color:#66d9ef">init</span>(message: InAppMessage) {
        <span style="color:#66d9ef">self</span>.message = message
    }

    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">adapter</span>(<span style="color:#66d9ef">for</span> message: InAppMessage) -&gt; CustomAdapter {
        <span style="color:#66d9ef">return</span> CustomAdapter(message: message)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">prepare</span>(with assets: InAppMessageAssets, completionHandler: @escaping (InAppMessagePrepareResult) -&gt; Void) {
        <span style="color:#75715e">// Download any resources for the in-app message before displaying</span>

        <span style="color:#75715e">// Call the completion handler with the correct result</span>
        completionHandler(.success)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">isReadyToDisplay</span>() -&gt; Bool {
        <span style="color:#75715e">// Return ready state</span>
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">display</span>(<span style="color:#66d9ef">_</span> completionHandler: @escaping (InAppMessageResolution) -&gt; Void) {
        <span style="color:#75715e">// Display the in-app message</span>

        <span style="color:#75715e">// When the display is finished, notify the completion handler with the result</span>
        completionHandler(InAppMessageResolution.messageClick())
    }
}</code></pre></div>

> **Set the factory block on the UAInAppMessageManager instance to provide the new adapter:**

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-obj-c" data-lang="obj-c">[UAInAppAutomation.shared.inAppMessageManager setFactoryBlock:<span style="color:#f92672">^</span><span style="color:#66d9ef">id</span><span style="color:#f92672">&lt;</span>UAInAppMessageAdapterProtocol<span style="color:#f92672">&gt;</span> (UAInAppMessage <span style="color:#f92672">*</span>message) {
    <span style="color:#66d9ef">return</span> [CustomInAppMessageAdapter adapterForMessage:message];
} forDisplayType:UAInAppMessageDisplayTypeCustom];</code></pre></div>

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">InAppAutomation.shared.inAppMessageManager.setFactoryBlock({ (message) -&gt; InAppMessageAdapterProtocol <span style="color:#66d9ef">in</span>
    <span style="color:#66d9ef">return</span> CustomAdapter(message: message)
}, <span style="color:#66d9ef">for</span>: .custom)</code></pre></div>

### Overriding the Scene

The UAInAppMessageSceneDelegate facilitates overriding the UIWindowScene on which a given in-app message is displayed.

> **Example delegate:**

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-obj-c" data-lang="obj-c">- (nullable UIWindowScene <span style="color:#f92672">*</span>)<span style="color:#a6e22e">sceneForMessage:</span>(UAInAppMessage <span style="color:#f92672">*</span>)message <span style="color:#a6e22e">defaultScene:</span>(nullable UIWindowScene <span style="color:#f92672">*</span>)defaultScene API_AVAILABLE(ios(<span style="color:#ae81ff">13.0</span>)) {
    <span style="color:#75715e">// Can be used to return the scene on which the message should display
</span><span style="color:#75715e"></span>    <span style="color:#66d9ef">return</span> myScene;
}</code></pre></div>

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">@available(iOS <span style="color:#ae81ff">13.0</span>, <span style="color:#f92672">*</span>)
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">scene</span>(<span style="color:#66d9ef">for</span> message: InAppMessage, defaultScene: UIWindowScene?) -&gt; UIWindowScene? {
    <span style="color:#75715e">// Can be used to return the scene on which the message should display</span>
    <span style="color:#66d9ef">return</span> myScene
}</code></pre></div>

> **Setting the delegate:**

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-obj-c" data-lang="obj-c">UAInAppMessageSceneManager.shared.delegate <span style="color:#f92672">=</span> exampleInAppMessagerSceneDelegate;</code></pre></div>

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">InAppMessageSceneManager.shared.delegate = exampleInAppMessagerSceneDelegate</code></pre></div>

## Standard In-App Messages {#standard}

Standard [in-app messages](https://docs.airship.com/guides/messaging/user-guide/messages/content/in-app-messages/)
delivered through push messages are managed by the legacy in-app message
manager. The `UALegacyInAppMessageBuilderExtender` allows customizing both the
schedule and message when the legacy message is being mapped to an in-app automation
banner message.

> **Example UALegacyInAppMessageBuilderExtender:**

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-obj-c" data-lang="obj-c">- (<span style="color:#66d9ef">void</span>)<span style="color:#a6e22e">extendScheduleBuilder:</span>(UAScheduleBuilder <span style="color:#f92672">*</span>)builder <span style="color:#a6e22e">message:</span>(UALegacyInAppMessage <span style="color:#f92672">*</span>)message {
    builder.limit <span style="color:#f92672">=</span> <span style="color:#ae81ff">2</span>;
}

- (<span style="color:#66d9ef">void</span>)<span style="color:#a6e22e">extendMessageBuilder:</span>(UAInAppMessageBuilder <span style="color:#f92672">*</span>)builder <span style="color:#a6e22e">message:</span>(UALegacyInAppMessage <span style="color:#f92672">*</span>)message {
    UAInAppMessageBannerDisplayContent <span style="color:#f92672">*</span>bannerDisplayContent <span style="color:#f92672">=</span> (UAInAppMessageBannerDisplayContent <span style="color:#f92672">*</span>) builder.displayContent;
    [bannerDisplayContent extend:<span style="color:#f92672">^</span>(UAInAppMessageBannerDisplayContentBuilder <span style="color:#f92672">*</span> _Nonnull builder) {
        builder.borderRadiusPoints <span style="color:#f92672">=</span> <span style="color:#ae81ff">10</span>;
    }];

    builder.displayContent <span style="color:#f92672">=</span> bannerDisplayContent;
}</code></pre></div>

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">extend</span>(<span style="color:#66d9ef">_</span> builder: ScheduleBuilder, message: LegacyInAppMessage) {
    <span style="color:#75715e">// Apply any schedule info changes to the builder</span>
    builder.limit = <span style="color:#ae81ff">2</span>
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">extend</span>(<span style="color:#66d9ef">_</span> builder: InAppMessageBuilder, message: LegacyInAppMessage) {
    <span style="color:#75715e">// Apply any message changes to the builder</span>
    <span style="color:#66d9ef">let</span> bannerDisplayContent = builder.displayContent <span style="color:#66d9ef">as</span>! InAppMessageBannerDisplayContent
    bannerDisplayContent.extend { (builder) <span style="color:#66d9ef">in</span>
        builder.borderRadiusPoints = <span style="color:#ae81ff">10</span>;
    }

    builder.displayContent = bannerDisplayContent
}</code></pre></div>


> **Setting the builder extender:**

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-obj-c" data-lang="obj-c">UALegacyInAppMessaging.shared.builderExtender <span style="color:#f92672">=</span> builderExtender;</code></pre></div>

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">LegacyInAppMessaging.shared.builderExtender = builderExtender</code></pre></div>


## Customizing HTML In-App Messages

<div class="note-wrapper">
    <div class="note note" >
        <header class="u-text-uppercase u-text-black">
            <span class="u-space-right-06">
                <svg class="dm-icon svg-icon">
                    <use xlink:href="#pencil"></use>
                </svg>
            </span>
            &nbsp;Note
        </header>
        <div class="content">
            <p>In order for the Airship JavaScript interface to be loaded into the webview, the URL must be specified in the <a href="https://docs.airship.com/platform/ios/getting-started/#url-allow-list">URL Allow List</a>.</p>
        </div>
    </div>
</div>



HTML in-app messages provide a way to display custom content inside a native web view. These types of in-app messages display with a dismiss button built in, but can also be customized to provide their own buttons capable of dismissing the view. Dismissing a view requires calling the dismiss function on the UAirship JavaScript interface with a button resolution object passed in as a parameter. The button resolution object is a JSON object containing information about the interaction type and the button performing the dismissal. It should match the following format:

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript">{
  <span style="color:#e6db74">&#34;type&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;button_click&#34;</span>,
  <span style="color:#e6db74">&#34;button_info&#34;</span> <span style="color:#f92672">:</span> {
    <span style="color:#e6db74">&#34;id&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;button identifier&#34;</span>,
    <span style="color:#e6db74">&#34;label&#34;</span> <span style="color:#f92672">:</span> {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;foo&#34;</span>}
  }
}</code></pre></div>

The button resolution requires each of the key fields shown above. These include:

- `type` : The type key with the value of resolution type `button_click`
- `button_info` : The button info object containing required id and label fields
    - `id`: The button identifier
    - `label` : Label object containing the required text key
        - `text` : The text key with a string value representing the label text

Providing a basic dismiss button in HTML:

<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html">&lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">onclick</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;UAirship.dismiss({
</span><span style="color:#e6db74">  &#39;type&#39; : &#39;button_click&#39;,
</span><span style="color:#e6db74">  &#39;button_info&#39; : {
</span><span style="color:#e6db74">    &#39;id&#39; : &#39;button identifier&#39;,
</span><span style="color:#e6db74">    &#39;label&#39; : {&#39;text&#39; : &#39;foo&#39;}
</span><span style="color:#e6db74">  }
</span><span style="color:#e6db74">}
</span><span style="color:#e6db74">);&#34;</span>&gt;Dismiss with resolution&lt;/<span style="color:#f92672">button</span>&gt;</code></pre></div>