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
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))
// Solid color
inboxUI.setBackground(Color.white)
// System colors (adapt to light/dark mode)
inboxUI.setBackground(Color(.systemBackground))
// Custom colors with opacity
inboxUI.setBackground(Color.blue.opacity(0.1))
Setting a Gradient Background
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
inboxUI.setBackground(
LinearGradient(
colors: [Color.blue.opacity(0.1), Color.purple.opacity(0.05)],
startPoint: .top,
endPoint: .bottom
)
)
Setting an Image Background
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
inboxUI.setBackground(
Image("inbox_background")
.resizable()
.aspectRatio(contentMode: .fill)
)
data-variant=info
data-slots=text
Color(.systemGroupedBackground) - adapts to light and dark mode.Card Spacing
Control the spacing between content cards in the inbox.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))
// Tighter spacing
inboxUI.cardSpacing = 8
// Default spacing
inboxUI.cardSpacing = 16
// Looser spacing
inboxUI.cardSpacing = 24
data-variant=info
data-slots=text
16 points.Content Padding
Set the padding around the content area of the inbox.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))
// Uniform padding on all sides
inboxUI.contentPadding = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
// Different padding for each side
inboxUI.contentPadding = EdgeInsets(top: 20, leading: 12, bottom: 20, trailing: 12)
// Minimal padding
inboxUI.contentPadding = EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
data-variant=info
data-slots=text
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.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let 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.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))
inboxUI.setErrorView { error in
AnyView(
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.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))
inboxUI.setEmptyView { emptyStateSettings in
AnyView(
VStack(spacing: 20) {
// Use configured image and message if available
if 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
data-variant=info
data-slots=text
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.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: Surface(path: "inbox"))
inboxUI.setHeadingView { heading in
AnyView(
HStack {
Image(systemName: "tray.fill")
.font(.title2)
.foregroundColor(.blue)
heading.text.view
.font(.title)
.fontWeight(.bold)
Spacer()
// Add a custom badge
if 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
data-variant=info
data-slots=text
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
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
class InboxCardCustomizer: ContentCardCustomizing {
func customize(template: SmallImageTemplate) {
// Customize UI elements
template.title.textColor = .primary
template.title.font = .headline
template.body?.textColor = .secondary
template.body?.font = .subheadline
// Customize background
template.backgroundColor = .white
// Customize buttons
template.buttons?.first?.text.font = .callout
template.buttons?.first?.backgroundColor = .blue
}
func customize(template: LargeImageTemplate) {
// Customize large image cards differently
template.title.font = .title2
template.backgroundColor = Color(.systemBackground)
}
}
// Apply the customizer to the inbox
let 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.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
let inboxUI = Messaging.getInboxUI(for: surface)
// Apply customizations immediately
inboxUI.cardSpacing = 12
inboxUI.setBackground(Color.blue.opacity(0.1))
inboxUI.isPullToRefreshEnabled = true
// Then display
return inboxUI.view
2. Respect Campaign Configuration
When customizing empty state and heading views, use configured values when available.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
inboxUI.setEmptyView { emptyStateSettings in
AnyView(
VStack {
// Use configured image if available, otherwise use default
if let configuredImage = emptyStateSettings?.image {
configuredImage.view
} else {
Image("default_empty_icon")
}
// Use configured message if available, otherwise use default
if 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.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
// Use system colors that adapt automatically
inboxUI.setBackground(Color(.systemGroupedBackground))
4. Maintain Consistency
Keep your Inbox customizations consistent with your app's overall design system.
data-slots=heading, code
data-repeat=1
data-languages=Swift
Swift
// Define constants for consistency
struct DesignSystem {
static let cardSpacing: CGFloat = 16
static 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 consistently
inboxUI.cardSpacing = DesignSystem.cardSpacing
inboxUI.contentPadding = DesignSystem.contentPadding
inboxUI.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
setBackground()Color(.systemGroupedBackground)cardSpacingCGFloat16contentPaddingEdgeInsets(12, 16, 12, 16)unreadIconSizeCGFloat16isPullToRefreshEnabledBoolfalsesetLoadingView()setErrorView()setEmptyView()setHeadingView()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