# Build a custom Message Center Build a custom Message Center by composing the SDK's ready-to-use views, handling display requests, integrating with your navigation, and filtering messages. # Message Center inbox view > Show the complete Message Center inbox using MessageCenterView for an all-in-one view, MessageCenterContent for custom navigation, or a UIKit view controller. The inbox view shows the complete Message Center: the message list and, when a message is selected, its content. Choose the view that matches how much control you need over navigation. - `MessageCenterView` provides its own navigation and works on its own. - `MessageCenterContent` provides only the content, so you supply the navigation. - A UIKit view controller wraps `MessageCenterView` for UIKit apps. ## MessageCenterView `MessageCenterView` is a complete Message Center with a built-in navigation stack. Place it anywhere in your SwiftUI hierarchy. #### Swift ```swift import SwiftUI import AirshipMessageCenter struct MyMessageCenterScreen: View { var body: some View { MessageCenterView() } } ``` #### Objective-C ```objc // Not available in Objective-C. Use a UIKit view controller (see below). ``` The initializer accepts a navigation style and an optional controller: ```swift public init(navigationStyle: NavigationStyle = .auto, controller: MessageCenterController? = nil) ``` ### Navigation styles The `navigationStyle` parameter controls how the list and message panes are arranged: ```swift // Adaptive: split view on iPad, stack on iPhone MessageCenterView(navigationStyle: .auto) // Single-column navigation on all devices MessageCenterView(navigationStyle: .stack) // Two-column master-detail layout on all devices MessageCenterView(navigationStyle: .split) ``` - **`.auto`** (default): 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. ### Styling and filtering Apply view modifiers to theme the view, filter messages, or replace the list item and message views: ```swift MessageCenterView() .messageCenterTheme(theme) .messageCenterPredicate(CustomPredicate()) .messageCenterItemViewStyle(CustomListItemViewStyle()) .messageCenterMessageViewStyle(CustomMessageViewStyle()) ``` - `.messageCenterTheme(_:)` applies a [theme](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/getting-started/#applying-a-custom-theme). - `.messageCenterPredicate(_:)` [filters messages](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/custom/#filtering-messages) in this view only. - `.messageCenterItemViewStyle(_:)` replaces the [list item view](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/custom/list-view/#custom-list-item-view). - `.messageCenterMessageViewStyle(_:)` replaces the [message view](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/custom/message-view/#custom-message-view-style). ## MessageCenterContent For full control over navigation, use `MessageCenterContent`, which provides the message list without a built-in navigation stack. It requires a `MessageCenterController` to keep the list and message state in sync. Drive navigation with the controller's `path`, and provide a destination for the message route: #### Swift ```swift import SwiftUI import AirshipMessageCenter struct MyMessageCenterScreen: View { @StateObject private var controller = MessageCenterController() var body: some View { NavigationStack(path: $controller.path) { MessageCenterContent(controller: controller) .navigationTitle("Messages") .navigationDestination(for: MessageCenterController.Route.self) { route in switch route { case .message(let messageID): MessageCenterMessageViewWithNavigation(messageID: messageID) } } } } } ``` `MessageCenterController` publishes its navigation state so you can respond to selection changes: ```swift public enum Route: Sendable, Hashable { case message(String) } @Published public var path: [Route] // Navigate to a message, or pass nil to pop to the list public func navigate(messageID: String?) ``` To filter the messages shown, pass a predicate to the initializer: ```swift MessageCenterContent( controller: controller, predicate: CustomPredicate() ) ``` ## UIKit For UIKit apps, create a view controller that hosts the Message Center. The factory returns a standard view controller you can present or push. #### Swift ```swift let viewController = MessageCenterViewControllerFactory.make( controller: MessageCenterController() ) navigationController?.pushViewController(viewController, animated: true) ``` #### Objective-C ```objc UIViewController *viewController = [UAMessageCenterViewControllerFactory make]; [self.navigationController pushViewController:viewController animated:YES]; ``` To embed the Message Center inside an existing view controller in Objective-C, use `embed(in:)`, which adds the Message Center as a child and returns a container view: ```objc UIView *messageCenterView = [UAMessageCenterViewControllerFactory embedIn:self]; [self.containerView addSubview:messageCenterView]; ``` # Message Center list view > Show the Message Center message list on its own with MessageCenterListView, add a navigation bar, filter with a predicate, and customize the list item view. The list view shows the Message Center inbox without the message detail. Use it when you provide your own message display, or when you want the list in one part of your app and the message in another. - `MessageCenterListView` shows the list with no navigation bar. - `MessageCenterListViewWithNavigation` adds a navigation bar, title, and edit mode. ## MessageCenterListView Embed the list anywhere in your SwiftUI hierarchy. Handle selection yourself, typically by presenting a [message view](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/custom/message-view/). #### Swift ```swift import SwiftUI import AirshipMessageCenter struct MyListScreen: View { var body: some View { MessageCenterListView() } } ``` To show only a subset of messages, pass a predicate: ```swift MessageCenterListView(predicate: CustomPredicate()) ``` ## MessageCenterListViewWithNavigation `MessageCenterListViewWithNavigation` is the same list wrapped in a navigation bar, with a title and support for edit mode. Use it when the list is the root of its own navigation. ```swift MessageCenterListViewWithNavigation(predicate: CustomPredicate()) ``` ## Custom list item view {#custom-list-item-view} To replace the appearance of each row, implement `MessageCenterListItemViewStyle` and return your own view from `makeBody(configuration:)`. The configuration provides the message for the row. #### Swift ```swift struct CustomListItemViewStyle: MessageCenterListItemViewStyle { func makeBody(configuration: Configuration) -> some View { let message = configuration.message HStack { Text(message.title) Spacer() if message.unread { Circle() .fill(.blue) .frame(width: 8, height: 8) } } } } ``` Apply the style with the `.messageCenterItemViewStyle(_:)` modifier: ```swift MessageCenterListView() .messageCenterItemViewStyle(CustomListItemViewStyle()) ``` # Message Center message view > Show a single Message Center message with MessageCenterMessageView, add a navigation bar, customize the message view style, or render only the message body. The message view shows a single Message Center message. Use it alongside your own [list view](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/custom/list-view/), or to deep link directly to a message. - `MessageCenterMessageView` shows the message with no navigation bar. - `MessageCenterMessageViewWithNavigation` adds a navigation bar with a title and delete button. - `MessageCenterMessageContentView` renders only the message body, so you provide all surrounding UI. ## MessageCenterMessageView Show a message by its ID. Provide a `dismissAction` to handle closing the view. #### Swift ```swift import SwiftUI import AirshipMessageCenter struct MyMessageScreen: View { let messageID: String var body: some View { MessageCenterMessageView(messageID: messageID) { // Handle dismiss } } } ``` ## MessageCenterMessageViewWithNavigation `MessageCenterMessageViewWithNavigation` wraps the message in a navigation bar with a title and a delete button. It's the destination used by [MessageCenterContent](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/custom/inbox-view/#messagecentercontent) when a message is selected. ```swift public init( messageID: String, title: String? = nil, showBackButton: Bool? = nil, dismissAction: (@MainActor () -> Void)? = nil ) ``` - `title`: The title shown until the message loads. The final title comes from the message. - `showBackButton`: Whether to show a back button. When unset, the back button appears only when the view is presented modally. - `dismissAction`: Called when the view is dismissed. The delete button is hidden when `hideDeleteButton` is set in the [theme](https://www.airship.com/docs/developer/sdk-integration/apple/message-center/getting-started/#applying-a-custom-theme). ## Custom message view style {#custom-message-view-style} To customize the message presentation, implement `MessageViewStyle` and return your own view from `makeBody(configuration:)`. The configuration provides the message view model and the dismiss action. #### Swift ```swift struct CustomMessageViewStyle: MessageViewStyle { func makeBody(configuration: Configuration) -> some View { NavigationStack { MessageCenterMessageView( viewModel: configuration.viewModel, dismissAction: configuration.dismissAction ) .navigationTitle("Message") } } } ``` Apply the style with the `.messageCenterMessageViewStyle(_:)` modifier: ```swift MessageCenterView() .messageCenterMessageViewStyle(CustomMessageViewStyle()) ``` ## MessageCenterMessageContentView `MessageCenterMessageContentView` renders only the message body (either a web body or a native ([Scene](https://www.airship.com/docs/reference/glossary/#scene)) layout). It has no loading indicator, error UI, retry, or mark-as-read behavior. Instead, it reports its state through a `phase` binding so you can provide your own chrome. Use it when you need complete control over everything around the message content. ```swift public init( viewModel: MessageCenterMessageViewModel, phase: Binding, dismissAction: (@MainActor @Sendable () -> Void)? = nil ) ``` Set the phase to `.loading` to load the content. The view updates the binding to `.loaded` or `.error(_:)` as it loads: #### Swift ```swift import SwiftUI import AirshipMessageCenter struct MyMessageContentScreen: View { @StateObject private var viewModel: MessageCenterMessageViewModel @State private var phase: MessageCenterMessageContentPhase = .loading init(messageID: String) { _viewModel = .init(wrappedValue: MessageCenterMessageViewModel(messageID: messageID)) } var body: some View { ZStack { MessageCenterMessageContentView(viewModel: viewModel, phase: $phase) switch phase { case .loading: ProgressView() case .error: Text("Unable to load message") case .loaded: EmptyView() } } } } ```