# Message Center Implement Message Center to provide an inbox for rich HTML-based messages, including display, theming, embedding, and advanced customization. # Get started with Message Center on Apple > Message Center provides an inbox for rich HTML-based messages that users can view at their convenience, with support for custom theming and display handling. ![Message Center inbox on iOS](https://www.airship.com/docs/images/message-center-apple.webp) *Message Center inbox on iOS* Message Center provides an inbox for rich, HTML-based messages. Learn more about Message Center in our [feature guide](https://www.airship.com/docs/guides/features/messaging/message-center/). ## Display the Message Center Display the Message Center with a single method call: #### Swift ```swift Airship.messageCenter.display() ``` #### Objective-C ```objc [UAirship.messageCenter display]; ``` This displays the Message Center as an overlay window, allowing users to view and manage their messages. When the user closes the Message Center, any changes (such as marking messages as read) are automatically synced with Airship. > **Note:** To embed the Message Center directly in your app's navigation instead of displaying it as an overlay, see [Embedding the Message Center](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/embedding/). You can also [intercept display requests](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/embedding/#handling-display-requests) to handle navigation to your embedded Message Center. ## Applying a Custom Theme You can customize the appearance of the Message Center by creating a `MessageCenterTheme` instance and setting its properties. The theme applies globally to all Message Centers displayed in your app. ### Setting the Theme Programmatically (Swift) #### Swift ```swift var theme = MessageCenterTheme() theme.cellTitleFont = .title theme.cellDateFont = .body theme.cellTitleColor = .primary theme.cellDateColor = .secondary theme.unreadIndicatorColor = .blue // Set the theme on the Message Center Airship.messageCenter.theme = theme ``` ### Setting the Theme from a Plist You can also customize the theme without writing code by creating a plist file. All keys in the plist correspond to properties on the `MessageCenterTheme` class. **Color Format:** - **Named colors**: Must correspond to a named color defined in a color asset within the main bundle - **Hexadecimal colors**: Use separate keys for light/dark mode (e.g., `cellTitleColor` and `cellTitleColorDark`) > **Note:** If your app is written in Objective-C, you must use the plist file to customize your theme, as `MessageCenterTheme` is a Swift struct. Save the plist as `MessageCenterTheme.plist` in your app bundle. #### Example Theme Plist **MessageCenterTheme.plist** ```xml refreshTintColor #333333 refreshTintColorDark #DDDDDD iconsEnabled placeholderIcon placeholderIcon cellTitleFont fontName ChalkboardSE-Regular fontSize 16 cellDateFont fontName ChalkboardSE-Regular fontSize 14 cellColor #DDDDDD cellColorDark #333333 cellTitleColor #000000 cellTitleColorDark #FFFFFF cellDateColor #222222 cellDateColorDark #CCCCCC cellSeparatorStyle none cellSeparatorColor #FFFFFF cellSeparatorColorDark #000000 cellTintColor #FF0000 cellTintColorDark #00FF00 unreadIndicatorColor #FF0000 unreadIndicatorColorDark #FF0000 selectAllButtonTitleColor #333333 selectAllButtonTitleColorDark #DDDDDD deleteButtonTitleColor #333333 deleteButtonTitleColorDark #DDDDDD markAsReadButtonTitleColor #333333 markAsReadButtonTitleColorDark #DDDDDD hideDeleteButton editButtonTitleColor #333333 editButtonTitleColorDark #DDDDDD cancelButtonTitleColor #333333 cancelButtonTitleColorDark #DDDDDD backButtonColor #333333 backButtonColorDark #DDDDDD navigationBarTitle Nav Bar Title ``` ## Working with Messages The Message Center provides methods to fetch, mark as read, and delete messages programmatically. ### Fetch Messages Retrieve messages from the inbox: #### Swift ```swift let messages = await Airship.messageCenter.inbox.messages ``` #### Objective-C ```objc [UAirship.messageCenter.inbox getMessagesWithCompletionHandler:^(NSArray *messages) { // Handle messages }]; ``` ### Listen for Message Updates Subscribe to message updates using Combine publishers: #### Swift ```swift Airship.messageCenter.inbox.messagePublisher .receive(on: RunLoop.main) .sink(receiveValue: { messages in // Update your UI with the new messages self.messages = messages }) .store(in: &self.subscriptions) ``` #### Objective-C ```objc // Not available in Objective-C. Use KVO or polling instead. ``` ### Listen for Unread Count Changes Subscribe to unread count updates: #### Swift ```swift Airship.messageCenter.inbox.unreadCountPublisher .receive(on: RunLoop.main) .sink { unreadCount in // Update badge or UI self.unreadCount = unreadCount } .store(in: &self.subscriptions) ``` #### Objective-C ```objc // Not available in Objective-C. Use KVO or polling instead. ``` ### Refresh Messages Manually refresh the message list from the server: #### Swift ```swift let refreshed = await Airship.messageCenter.inbox.refreshMessages() ``` #### Objective-C ```objc [UAirship.messageCenter.inbox refreshMessagesWithCompletionHandler:^(BOOL result) { // Handle result }]; ``` ### Mark Messages as Read Mark one or more messages as read: #### Swift ```swift await Airship.messageCenter.inbox.markRead(messageIDs: [messageID]) ``` #### Objective-C ```objc [UAirship.messageCenter.inbox markReadWithMessageIDs:@[messageID] completionHandler:^{ // Marked read }]; ``` ### Delete Messages Delete one or more messages: #### Swift ```swift await Airship.messageCenter.inbox.delete(messageIDs: [messageID]) ``` #### Objective-C ```objc [UAirship.messageCenter.inbox deleteWithMessageIDs:@[messageID] completionHandler:^{ // Deleted }]; ``` ## Filter Messages by Named User By default, Message Center displays all messages sent to the device's channel. If multiple users log into your app on the same device, they'll all see the same messages. To filter messages by named user, set up filtering in your custom Message Center implementation. See [Message Center Filtering](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/embedding/#message-center-filtering) in the Embedding guide. When creating Message Center messages, include a custom key with `named_user_id` as the key and the user's actual ID as the value: - **For the API**: Use the `extra` object in the [Message Center object](https://www.airship.com/docs/developer/rest-api/ua/schemas/push/#messageobject). - **In the dashboard**: See [Add custom keys](https://www.airship.com/docs/guides/messaging/messages/content/app/message-center/#add-custom-keys) in the Message Center content guide. ### Filtering Behavior With named user filtering enabled: - If you target `User A` in a message while they are logged in, the message appears in their inbox. - If you target `User B` in a message while they are logged in, the message appears in their inbox. - If you target `User A` or `User B` while the other is logged in, the message does not appear. - If you target `User A` or `User B` while neither is logged in, the message does not appear. # 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](https://www.airship.com/docs/developer/sdk-integration/apple/installation/getting-started/) 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 #### Swift ```swift import SwiftUI import AirshipMessageCenter struct MyMessageCenterScreen: View { var body: some View { // Default navigation style (adaptive) MessageCenterView() } } ``` #### Objective-C ```objc // Not supported. Use display callbacks to show custom UI (see below). ``` ### Navigation Styles `MessageCenterView` supports different navigation styles via the `navigationStyle` parameter: #### Swift ```swift // 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. #### Swift ```swift 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: #### Swift ```swift MessageCenterContent( controller: controller, predicate: CustomPredicate() ) ``` > **Note:** To apply a custom theme globally, see [Getting Started: Applying a Custom Theme](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/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: #### Swift ```swift 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: #### Swift ```swift 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: #### Swift ```swift MessageCenterView() .messageCenterViewStyle(CustomMessageCenterViewStyle()) ``` ### Customize Item and Message Views Customize the Message Center item view and message view: #### Swift ```swift 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: #### Swift ```swift 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() ``` #### Objective-C ```objc // Not supported. Filtering requires Swift implementation. ``` ### Custom Filtering Create custom predicates for any filtering logic: #### Swift ```swift 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() ``` #### Objective-C ```objc // Not supported. Filtering requires Swift implementation. ``` If you're embedding `MessageCenterView` directly, pass the predicate through the view modifier: #### Swift ```swift 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 {#handling-display-requests} Set display callbacks after `takeOff` to handle Message Center display events: #### Swift ```swift 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 } ``` #### Objective-C ```objc - (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: #### Swift ```swift Task { UIApplication.shared.applicationIconBadgeNumber = await Airship.messageCenter.inbox.unreadCount } ``` #### Objective-C ```objc [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.