Embed the Message Center

Customize the Message Center appearance with SwiftUI view styles, create custom implementations with UIKit, and filter messages by named user.

By default, Airship displays the Message Center as an overlay on top of your app. For tighter integration with your app’s navigation flow, you can embed the Message Center views directly into your app.

Prerequisites

Message Center requires the AirshipMessageCenter module. See the SDK installation guide for setup instructions.

Embedding the Message Center View

The MessageCenterView (Swift) provides a complete Message Center with a built-in navigation stack. You can choose between different navigation styles for optimal display on iPhone and iPad.

Using MessageCenterView with Navigation

Embed Message Center

import SwiftUI
import AirshipMessageCenter

struct MyMessageCenterScreen: View {
    var body: some View {
        // Default navigation style (adaptive)
        MessageCenterView()
    }
}
// Not supported. Use display callbacks to show custom UI (see below).

MessageCenterView supports different navigation styles via the navigationStyle parameter:

Navigation styles

// Auto navigation (default - adaptive based on device)
MessageCenterView(navigationStyle: .auto)

// Stack navigation (single column)
MessageCenterView(navigationStyle: .stack)

// Split navigation (master-detail for iPad)
MessageCenterView(navigationStyle: .split)
  • .auto (default): Automatically uses split view on iPad and stack view on iPhone
  • .stack: Single-column navigation for all devices
  • .split: Two-column master-detail layout for all devices

Content View Without Navigation Stack

For full control over the navigation, use MessageCenterContent (Swift only) which provides just the content without a built-in navigation stack. This requires a MessageCenterController to manage the message list state.

Custom navigation with MessageCenterContent

import SwiftUI
import AirshipMessageCenter

struct MyMessageCenterScreen: View {
    @StateObject
    private var controller = MessageCenterController()
    
    var body: some View {
        NavigationStack {
            MessageCenterContent(controller: controller)
                .navigationTitle("Messages")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("Settings") {
                            // Custom action
                        }
                    }
                }
        }
    }
}

You can also provide a predicate to filter messages:

MessageCenterContent with predicate

MessageCenterContent(
    controller: controller,
    predicate: CustomPredicate()
)
 Note

To apply a custom theme globally, see Getting Started: Applying a Custom Theme.

Using MessageCenterController with NavigationView (Legacy)

For apps that need to support older iOS versions or integrate with NavigationView, you can use MessageCenterController to manually manage navigation state:

Legacy NavigationView

import SwiftUI
import AirshipMessageCenter

struct MyMessageCenterScreen: View {
    @StateObject
    private var messageCenterController = MessageCenterController()
    
    var body: some View {
        NavigationView {
            ZStack {
                MessageCenterContent(controller: self.messageCenterController)
                NavigationLink(
                    destination: Group {
                        if case .message(let messageID) = self.messageCenterController.path.last {
                            MessageCenterMessageViewWithNavigation(messageID: messageID) {
                                // Clear selection on close
                                self.messageCenterController.path.removeAll()
                            }
                        } else {
                            EmptyView()
                        }
                    },
                    isActive: Binding(
                        get: { self.messageCenterController.path.last != nil },
                        set: { isActive in
                            if !isActive { self.messageCenterController.path.removeAll() }
                        }
                    )
                ) {
                    EmptyView()
                }
                .hidden()
            }
        }
    }
}

This pattern is also useful for UIKit integration where you need manual control over navigation state.

Customizing View Styles (Swift Only)

You can customize the appearance of the Message Center by creating custom styles for the view wrapper and message list.

Custom Message Center View Style

Create a custom style that implements MessageCenterViewStyle to control the navigation wrapper around the Message Center content:

Custom view style

struct CustomMessageCenterViewStyle: MessageCenterViewStyle {
    @ViewBuilder
    func makeBody(configuration: Configuration) -> some View {
        if #available(iOS 16.0, *) {
            NavigationStack {
                configuration.content
                    .navigationBarTitleDisplayMode(.inline)
                    .navigationTitle(Text("Custom Message Center"))
                    .toolbarBackground(.mint, for: .navigationBar)
                    .toolbarBackground(.visible, for: .navigationBar)
                    .toolbarColorScheme(configuration.colorScheme)
            }
        } else {
            NavigationView {
                configuration.content
                    .navigationTitle(Text("Custom Message Center"))
                    .navigationBarTitleDisplayMode(.large)
            }
            .navigationViewStyle(.stack)
        }
    }
}

Apply the custom style:

Apply custom style

MessageCenterView()
    .messageCenterViewStyle(CustomMessageCenterViewStyle())

Customize Item and Message Views

Customize the Message Center item view and message view:

Customize views

MessageCenterView()
    .messageCenterTheme(theme)
    .setMessageCenterItemViewStyle(messageCenterListItemViewStyle)
    .setMessageCenterMessageViewStyle(messageViewStyle)

Message Center Filtering

Filter messages using a predicate. Only messages that match the predicate will be displayed.

Filter by Named User

Filter messages to show only those for the current named user:

Filter by named user

class NamedUserPredicate: MessageCenterPredicate {
    func evaluate(message: MessageCenterMessage) -> Bool {
        guard let namedUserID = Airship.contact.namedUserID else {
            return false
        }
        
        // Check if message has matching named_user_id in extras
        if let extras = message.extras,
           let messageNamedUserID = extras["named_user_id"] as? String {
            return messageNamedUserID == namedUserID
        }
        
        return false
    }
}

Airship.messageCenter.predicate = NamedUserPredicate()
// Not supported. Filtering requires Swift implementation.

Custom Filtering

Create custom predicates for any filtering logic:

Custom filtering

class CustomPredicate: MessageCenterPredicate {
    func evaluate(message: MessageCenterMessage) -> Bool {
        // Example: Only show messages with "cool" in the title
        return message.title.contains("cool")
    }
}

Airship.messageCenter.predicate = CustomPredicate()
// Not supported. Filtering requires Swift implementation.

If you’re embedding MessageCenterView directly, pass the predicate through the view modifier:

Predicate with embedded view

MessageCenterView(
    controller: MessageCenterController()
)
.messageCenterPredicate(CustomPredicate())

Custom Message Center Implementation

For complete control over Message Center placement and navigation, create a custom implementation using the Message Center components.

Key Components

MessageCenter
The main entry point for fetching messages and handling callbacks. Access via Airship.messageCenter.
MessageCenterInboxProtocol
Provides an interface for retrieving messages asynchronously and accessing the local message array.
 Note

The message list uses CoreData. Message objects are ephemeral references refreshed with the list. Don’t hold onto individual message instances indefinitely.

MessageCenterMessage
Model object representing an individual message. Instances don’t contain the message body—they point to authenticated URLs that should be displayed in a webview.
Display Callbacks
Set Airship.messageCenter.onDisplay to handle when messages should be displayed, and Airship.messageCenter.onDismissDisplay to handle dismiss events.
NativeBridge
For custom webview implementations, set a NativeBridge instance as the navigation delegate on your WKWebView to enable JavaScript bridge functionality.

Handling Display Requests

Set display callbacks after takeOff to handle Message Center display events:

Custom display handling

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    // Call takeOff
    try! Airship.takeOff(config, launchOptions: launchOptions)
    
    // Set Message Center display callback
    Airship.messageCenter.onDisplay = { messageID in
        // Navigate to your custom Message Center UI
        // messageID is optional - nil means show the full list
        
        // Return true to prevent default SDK display
        return true
    }
    
    // Set Message Center dismiss callback
    Airship.messageCenter.onDismissDisplay = {
        // Dismiss your custom Message Center UI
    }
    
    return true
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Call takeOff
    [UAirship takeOff:config launchOptions:launchOptions error:nil];
    
    // Set Message Center display callback
    UAirship.messageCenter.onDisplay = ^BOOL(NSString * _Nullable messageID) {
        // Navigate to your custom Message Center UI
        // messageID is optional - nil means show the full list
        
        // Return YES to prevent default SDK display
        return YES;
    };
    
    // Set Message Center dismiss callback
    UAirship.messageCenter.onDismissDisplay = ^{
        // Dismiss your custom Message Center UI
    };
    
    return YES;
}

Badge Updates

Update the app badge to reflect the Message Center unread count:

Update badge with unread count

Task {
    UIApplication.shared.applicationIconBadgeNumber = await Airship.messageCenter.inbox.unreadCount
}
[UAirship.messageCenter.inbox getUnreadCountWithCompletionHandler:^(NSInteger unreadCount) {
    dispatch_async(dispatch_get_main_queue(), ^{
        UIApplication.sharedApplication.applicationIconBadgeNumber = unreadCount;
    });
}];
 Important

If you use this method, don’t include badge values in push notification payloads, as the Message Center will override them.