Custom Views for the Apple SDK
Register custom SwiftUI views with the AirshipCustomViewManager to use them in Scenes. Scene control: iOS SDK 20.0+

To use Custom Views, you must first register the view’s name with the AirshipCustomViewManager. The name is referenced when adding the Custom View to a Scene.
The view manager will call through to the view builders registered for that view’s name and provide the properties, name, and some layout hints as arguments.
All Custom Views should be registered after takeOff.
Registering Custom Views
struct CustomViewProperties: Decodable {
var text: String
}
AirshipCustomViewManager.shared.register(name: "custom_view") { args in
let viewProperties: CustomViewProperties = if let decoded = try? args.properties?.decode() {
decoded
} else {
CustomViewProperties(text: "fallback")
}
Text(viewProperties.text)
}Example custom view
The following example shows a Custom View that renders an embedded map when
called to render a Custom View named map.
In our example, we have properties that defines a single place field, which is the address of the location that the map should render.
Custom Map View
First, define the view and its properties:
import SwiftUI
import MapKit
import CoreLocation
struct CustomMapView: View {
struct Args: Decodable {
let place: String
}
let args: Args
@State private var region: MKCoordinateRegion?
@State private var pinCoordinate: CLLocationCoordinate2D?
var body: some View {
if let region = region, let pinCoordinate = pinCoordinate {
Map(coordinateRegion: .constant(region), annotationItems: [MapPin(coordinate: pinCoordinate)]) { pin in
MapMarker(coordinate: pin.coordinate, tint: Color.red)
}
} else {
Text("Loading map...")
.onAppear {
geocodePlace()
}
}
}
private func geocodePlace() {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(args.place) { placemarks, error in
if let placemark = placemarks?.first, let location = placemark.location {
let coordinate = location.coordinate
self.region = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
self.pinCoordinate = coordinate
} else {
print("Failed to geocode place: \(error?.localizedDescription ?? "Unknown error")")
}
}
}
}
struct MapPin: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}
struct CustomMapView_Previews: PreviewProvider {
static var previews: some View {
CustomMapView(args: CustomMapView.Args(place: "Eiffel Tower"))
}
}Then register the view after takeOff:
AirshipCustomViewManager.shared.register(name: "map", builder: { args in
if let args: CustomMapView.Args = try? args.properties?.decode() {
CustomMapView(args: args)
}
})Scene Control iOS SDK 20.0+
Custom views control their parent scene through the AirshipSceneController, which is automatically injected as an @EnvironmentObject.
Accessing SceneController
Declare the scene controller as an @EnvironmentObject property in your custom view.
Accessing SceneController
struct CustomMapView: View {
@EnvironmentObject var sceneController: AirshipSceneController
let args: Args
var body: some View {
// Your custom view content
}
}Dismissing scenes
Call dismiss() to close the scene, or set cancelFutureDisplays to prevent it from displaying again.
Dismissing scenes
struct CustomMapView: View {
@EnvironmentObject var sceneController: AirshipSceneController
var body: some View {
VStack {
// Map display code...
Button("Close Map") {
sceneController.dismiss()
}
Button("Got It") {
sceneController.dismiss(cancelFutureDisplays: true)
}
}
}
}Pager navigation
Navigate between pages using the pager controller’s canGoBack and canGoNext properties.
Pager navigation
struct CustomMapView: View {
@EnvironmentObject var sceneController: AirshipSceneController
var body: some View {
HStack {
if sceneController.pager.canGoBack {
Button("Back") {
sceneController.pager.navigate(request: .back)
}
}
if sceneController.pager.canGoNext {
Button("Next Location") {
sceneController.pager.navigate(request: .next)
}
}
}
}
}Sizing management
Use args.sizeInfo to determine appropriate sizing for your custom view.
Sizing with SizeInfo
AirshipCustomViewManager.shared.register(name: "map") { args in
if let args: CustomMapView.Args = try? args.properties?.decode() {
CustomMapView(args: args)
.frame(
width: args.sizeInfo.isAutoWidth ? 350 : nil,
height: args.sizeInfo.isAutoHeight ? 500 : nil
)
}
}
Embedding Airship Views
Airship views like Preference Center can be embedded as custom views.
Embedding Preference Center
// Full preference center with navigation bar
AirshipCustomViewManager.shared.register(name: "preference_center") { args in
let id = args.properties?.object?["id"]?.string ?? "default"
return PreferenceCenterView(preferenceCenterID: id)
.frame(
maxWidth: args.sizeInfo.isAutoWidth ? nil : .infinity,
maxHeight: args.sizeInfo.isAutoHeight ? nil : .infinity
)
}
// Content only (no navigation bar)
AirshipCustomViewManager.shared.register(name: "preference_center_content") { args in
let id = args.properties?.object?["id"]?.string ?? "default"
return PreferenceCenterContent(preferenceCenterID: id)
.frame(
maxWidth: args.sizeInfo.isAutoWidth ? nil : .infinity,
maxHeight: args.sizeInfo.isAutoHeight ? nil : .infinity
)
}
Categories