Embed the Preference Center

Embed the Preference Center view directly in your app’s navigation instead of displaying it as an overlay.

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

Embedding the Preference Center View

The PreferenceCenterView (Swift) or UAPreferenceCenterViewControllerFactory (Objective-C) provides a complete Preference Center with a built-in navigation stack.

Embed Preference Center

import SwiftUI
import AirshipPreferenceCenter

struct MyPreferenceCenterScreen: View {
    var body: some View {
        PreferenceCenterView(preferenceCenterID: "my_preference_center_id")
    }
}
@import AirshipCore;

// Create a view controller
UIViewController *preferenceCenterVC = [UAPreferenceCenterViewControllerFactory 
    makeViewControllerWithPreferenceCenterID:@"my_preference_center_id"];

// Present or push the view controller
[self.navigationController pushViewController:preferenceCenterVC animated:YES];

Or embed it in a container view:

@import AirshipCore;

// Embed the preference center in a container view
NSError *error = nil;
UIView *containerView = [UAPreferenceCenterViewControllerFactory 
    embedWithPreferenceCenterID:@"my_preference_center_id"
    preferenceCenterThemePlist:nil
    inParentViewController:self
    error:&error];

if (error) {
    NSLog(@"Failed to embed preference center: %@", error);
} else {
    // Add the container view to your view hierarchy
    containerView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:containerView];
    
    [NSLayoutConstraint activateConstraints:@[
        [containerView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
        [containerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [containerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [containerView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
    ]];
}

Content View Without Navigation Stack

For more control over the navigation, use PreferenceCenterContent (Swift only) which provides just the content without a built-in navigation stack.

import SwiftUI
import AirshipPreferenceCenter

struct MyPreferenceCenterScreen: View {
    var body: some View {
        NavigationView {
            PreferenceCenterContent(preferenceCenterID: "my_preference_center_id")
                .navigationTitle("Preferences")
        }
    }
}
 Note

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

Customizing View Styles (Swift Only)

For SwiftUI views, you can apply style overrides to customize individual components within the Preference Center. These view modifiers allow granular control over specific sections without affecting the global theme.

Available Style Overrides

  • .channelSubscriptionStyle(_:) - Customize channel subscription views
  • .commonSectionViewStyle(_:) - Adjust common sections
  • .contactManagementSectionStyle(_:) - Modify contact management sections
  • .contactSubscriptionGroupStyle(_:) - Tailor contact subscription groups
  • .contactSubscriptionStyle(_:) - Customize individual contact subscriptions
  • .labeledSectionBreakStyle(_:) - Define labeled section breaks
  • .alertStyle(_:) - Adjust alert appearance

Example

import SwiftUI
import AirshipPreferenceCenter

struct MyPreferenceCenterScreen: View {
    var body: some View {
        PreferenceCenterView(preferenceCenterID: "my_preference_center_id")
            .channelSubscriptionStyle(MyCustomChannelStyle())
            .contactSubscriptionStyle(MyCustomContactStyle())
            .alertStyle(MyCustomAlertStyle())
    }
}

// Define custom styles by conforming to the respective protocols
struct MyCustomChannelStyle: ChannelSubscriptionStyle {
    // Implement required style methods
}

struct MyCustomContactStyle: ContactSubscriptionStyle {
    // Implement required style methods
}

struct MyCustomAlertStyle: AlertStyle {
    // Implement required style methods
}

For detailed information on creating custom styles and the available properties for each style protocol, see the AirshipPreferenceCenter View extension documentation.

Advanced: Custom Loading (Swift Only)

For SwiftUI apps, you can customize the loading behavior and respond to phase changes using PreferenceCenterContent:

import SwiftUI
import AirshipPreferenceCenter

struct MyPreferenceCenterScreen: View {
    var body: some View {
        NavigationView {
            PreferenceCenterContent(
                preferenceCenterID: "my_preference_center_id",
                onLoad: { preferenceCenterID in
                    // Custom loading logic
                    // Return a PreferenceCenterContentPhase
                    return await loadPreferenceCenter(preferenceCenterID)
                },
                onPhaseChange: { phase in
                    switch phase {
                    case .loading:
                        print("Loading preference center...")
                    case .error(let error):
                        print("Failed to load: \(error)")
                    case .loaded(let state):
                        print("Loaded with state: \(state)")
                    }
                }
            )
            .navigationTitle("Preferences")
        }
    }
    
    func loadPreferenceCenter(_ id: String) async -> PreferenceCenterContentPhase {
        // Custom loading implementation
        // Return .loading, .error, or .loaded
        return .loading
    }
}

The PreferenceCenterViewPhase enum represents the current state of the Preference Center:

  • .loading — The view is loading
  • .error(Error) — The view failed to load the config
  • .loaded(PreferenceCenterState) — The view is loaded with the state

Handling Display Requests

When embedding a Preference Center, you need to intercept Airship’s display requests and navigate to your embedded view instead of showing the default overlay.

Handle display requests

Airship.preferenceCenter.onDisplay = { preferenceCenterID in
    guard preferenceCenterID == "my_embedded_preference_center_id" else {
        // Not the embedded one, allow Airship to display it as an overlay
        return false
    }
    
    // Navigate to your embedded view
    // Example: Use your app's navigation system to show the screen
    NotificationCenter.default.post(
        name: .showEmbeddedPreferenceCenter,
        object: nil,
        userInfo: ["id": preferenceCenterID]
    )
    
    // Return true to indicate you handled the display
    return true
}
@import AirshipCore;

// In your app delegate or preference center manager
@interface MyAppDelegate () <UAPreferenceCenterOpenDelegate>
@end

@implementation MyAppDelegate

- (BOOL)application:(UIApplication *)application 
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // Set the open delegate
    UAirship.preferenceCenter.openDelegate = self;
    
    return YES;
}

- (BOOL)openPreferenceCenter:(NSString *)preferenceCenterID {
    if (![preferenceCenterID isEqualToString:@"my_embedded_preference_center_id"]) {
        // Not the embedded one, allow Airship to display it as an overlay
        return NO;
    }
    
    // Navigate to your embedded view
    [[NSNotificationCenter defaultCenter] 
        postNotificationName:@"ShowEmbeddedPreferenceCenter"
        object:nil
        userInfo:@{@"id": preferenceCenterID}];
    
    // Return YES to indicate you handled the display
    return YES;
}

@end

By returning true (or YES in Objective-C), you tell Airship that you’ve handled the display request, preventing the default overlay from appearing.