Customizing Your Inbox
This tutorial explains how to customize the appearance and behavior of the Inbox in your application.
Overview
The InboxUI provides extensive customization options to match your app's design system. You can customize:
- Visual Appearance: Background, spacing, padding, and unread indicators
- Pull-to-Refresh: Enable user-driven content refresh
- Custom Views: Loading, error, empty state, and heading views
- Content Cards: Individual card appearance through the
ContentCardCustomizingprotocol
All customizations are applied to the InboxUI instance before or after displaying the view.
Visual Appearance Customization
Background
Set a custom background for the inbox container. The background can be a color, gradient, image, or any SwiftUI view.
Setting a Color Background
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))// Solid colorinboxUI.setBackground(Color.white)// System colors (adapt to light/dark mode)inboxUI.setBackground(Color(.systemBackground))// Custom colors with opacityinboxUI.setBackground(Color.blue.opacity(0.1))
Setting a Gradient Background
Copied to your clipboardinboxUI.setBackground(LinearGradient(colors: [Color.blue.opacity(0.1), Color.purple.opacity(0.05)],startPoint: .top,endPoint: .bottom))
Setting an Image Background
Copied to your clipboardinboxUI.setBackground(Image("inbox_background").resizable().aspectRatio(contentMode: .fill))
Default: Color(.systemGroupedBackground) - adapts to light and dark mode.
Card Spacing
Control the spacing between content cards in the inbox.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))// Tighter spacinginboxUI.cardSpacing = 8// Default spacinginboxUI.cardSpacing = 16// Looser spacinginboxUI.cardSpacing = 24
Default: 16 points.
Content Padding
Set the padding around the content area of the inbox.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))// Uniform padding on all sidesinboxUI.contentPadding = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)// Different padding for each sideinboxUI.contentPadding = EdgeInsets(top: 20, leading: 12, bottom: 20, trailing: 12)// Minimal paddinginboxUI.contentPadding = EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
Default: EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16).
Custom Views
The Inbox provides hooks to customize the loading, error, empty state, and heading views.
Custom Loading View
Replace the default loading view with your own custom view.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))inboxUI.setLoadingView {AnyView(VStack(spacing: 16) {ProgressView().scaleEffect(1.5)Text("Loading your inbox...").font(.headline).foregroundColor(.secondary)}.frame(maxWidth: .infinity, maxHeight: .infinity))}
Custom Error View
Replace the default error view with your own custom view that displays the error.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))inboxUI.setErrorView { error inAnyView(VStack(spacing: 20) {Image(systemName: "exclamationmark.triangle.fill").font(.system(size: 60)).foregroundColor(.red)Text("Oops! Something went wrong").font(.title2).fontWeight(.bold)Text(error.localizedDescription).font(.body).foregroundColor(.secondary).multilineTextAlignment(.center).padding(.horizontal)Button("Try Again") {inboxUI.refresh()}.buttonStyle(.borderedProminent)}.frame(maxWidth: .infinity, maxHeight: .infinity).padding())}
Custom Empty State View
Replace the default empty state view with your own custom view.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))inboxUI.setEmptyView { emptyStateSettings inAnyView(VStack(spacing: 20) {// Use configured image and message if availableif let image = emptyStateSettings?.image {image.view.frame(maxWidth: 120, maxHeight: 120)} else {Image(systemName: "tray").font(.system(size: 80)).foregroundColor(.gray)}if let message = emptyStateSettings?.message {message.view.multilineTextAlignment(.center)} else {Text("No messages").font(.headline).foregroundColor(.secondary)}Button("Refresh") {inboxUI.refresh()}.buttonStyle(.bordered)}.frame(maxWidth: .infinity, maxHeight: .infinity).padding())}
The emptyStateSettings parameter contains the following properties:
image: AEPImage?- Image to display in the empty statemessage: AEPText?- Message to display in the empty state
Use emptyStateSettings values when available and fall back to your custom defaults when settings are not provided.
Custom Heading View
Replace the default heading view or add a custom header to the inbox.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))inboxUI.setHeadingView { heading inAnyView(HStack {Image(systemName: "tray.fill").font(.title2).foregroundColor(.blue)heading.text.view.font(.title).fontWeight(.bold)Spacer()// Add a custom badgeif inboxUI.contentCards.count > 0 {Text("\(inboxUI.contentCards.count)").font(.caption).padding(.horizontal, 8).padding(.vertical, 4).background(Color.blue).foregroundColor(.white).clipShape(Capsule())}}.padding(.horizontal, 20).padding(.vertical, 16).background(LinearGradient(colors: [Color.blue.opacity(0.1), Color.purple.opacity(0.05)],startPoint: .leading,endPoint: .trailing)))}
The heading parameter contains:
text: AEPText- The heading text configured in Adobe Journey Optimizer
If heading properties are not configured in Adobe Journey Optimizer, the default heading view is still displayed with default styling. The custom heading view takes priority if provided.
Customizing Content Cards
Individual content cards within the Inbox can be customized using the ContentCardCustomizing protocol. This is the same protocol used for standalone content cards.
Applying Content Card Customizations
Copied to your clipboardclass InboxCardCustomizer: ContentCardCustomizing {func customize(template: SmallImageTemplate) {// Customize UI elementstemplate.title.textColor = .primarytemplate.title.font = .headlinetemplate.body?.textColor = .secondarytemplate.body?.font = .subheadline// Customize backgroundtemplate.backgroundColor = .white// Customize buttonstemplate.buttons?.first?.text.font = .callouttemplate.buttons?.first?.backgroundColor = .blue}func customize(template: LargeImageTemplate) {// Customize large image cards differentlytemplate.title.font = .title2template.backgroundColor = Color(.systemBackground)}}// Apply the customizer to the inboxlet inboxSurface = Surface(path: "inbox")let inboxUI = Messaging.getInboxUI(for: inboxSurface,customizer: InboxCardCustomizer())
For detailed information on customizing content cards, see:
Best Practices
1. Apply Customizations Early
Apply all customizations to the InboxUI instance immediately after creation, before the view is displayed.
Copied to your clipboardlet inboxUI = Messaging.getInboxUI(for: surface)// Apply customizations immediatelyinboxUI.cardSpacing = 12inboxUI.setBackground(Color.blue.opacity(0.1))inboxUI.isPullToRefreshEnabled = true// Then displayreturn inboxUI.view
2. Respect Campaign Configuration
When customizing empty state and heading views, use configured values when available.
Copied to your clipboardinboxUI.setEmptyView { emptyStateSettings inAnyView(VStack {// Use configured image if available, otherwise use defaultif let configuredImage = emptyStateSettings?.image {configuredImage.view} else {Image("default_empty_icon")}// Use configured message if available, otherwise use defaultif let configuredMessage = emptyStateSettings?.message {configuredMessage.view} else {Text("No content available")}})}
3. Test Light and Dark Modes
Always test your customizations in both light and dark mode.
Copied to your clipboard// Use system colors that adapt automaticallyinboxUI.setBackground(Color(.systemGroupedBackground))
4. Maintain Consistency
Keep your Inbox customizations consistent with your app's overall design system.
Copied to your clipboard// Define constants for consistencystruct DesignSystem {static let cardSpacing: CGFloat = 16static let contentPadding = EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)static let primaryGradient = LinearGradient(colors: [Color.blue.opacity(0.1), Color.purple.opacity(0.05)],startPoint: .top,endPoint: .bottom)}// Apply consistentlyinboxUI.cardSpacing = DesignSystem.cardSpacinginboxUI.contentPadding = DesignSystem.contentPaddinginboxUI.setBackground(DesignSystem.primaryGradient)
5. Performance Considerations
- Avoid Heavy Views: Keep custom loading, error, and empty views lightweight
- Optimize Images: Use appropriately sized images for backgrounds
- Minimize Redraws: Avoid dynamic content in custom views that might cause frequent redraws
Customization Reference
| Property/Method | Type | Default | Description |
|---|---|---|---|
setBackground() | Method | Color(.systemGroupedBackground) | Sets the inbox background view |
cardSpacing | CGFloat | 16 | Spacing between content cards |
contentPadding | EdgeInsets | (12, 16, 12, 16) | Padding around content area |
unreadIconSize | CGFloat | 16 | Size of unread indicator icon |
isPullToRefreshEnabled | Bool | false | Enables pull-to-refresh |
setLoadingView() | Method | Default loading view | Custom loading view |
setErrorView() | Method | Default error view | Custom error view |
setEmptyView() | Method | Default empty state | Custom empty state view |
setHeadingView() | Method | Default heading | Custom heading view |
Next Steps
- Displaying Inbox - Learn how to fetch and display the Inbox
- Listening to Inbox Events - Respond to user interactions
- ContentCardCustomizing Protocol - Deep dive into card customization
