# Custom Views for the Apple SDK

Register custom SwiftUI views with the AirshipCustomViewManager to use them in Scenes.

![Custom Views for the Apple SDK](https://www.airship.com/docs/images/custom-views-apple_hu_32d8fbb68fdc44c2.webp)

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](https://www.airship.com/docs/developer/sdk-integration/apple/installation/getting-started/).


#### Swift



```swift
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)
}
```




<!-- TODO: Add image: custom-view-registration.png - Screenshot showing where to register custom views in the Airship dashboard or code -->

## 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.


#### Swift



First, define the view and its properties:

```swift
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:

```swift
AirshipCustomViewManager.shared.register(name: "map", builder: { args in
    if let args: CustomMapView.Args = try? args.properties?.decode() {
        CustomMapView(args: args)
    }
})
```


<!-- TODO: Add image: custom-map-view-in-scene.png - Screenshot showing a Custom Map View rendered within a Scene, displaying a map with a location marker -->




## Scene Control

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.


#### Swift


```swift
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.


#### Swift


```swift
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.


#### Swift


```swift
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.


#### Swift


```swift
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
            )
    }
}
```




![Custom Views for the Apple SDK](https://www.airship.com/docs/images/custom-views-map-scene-apple_hu_46a1471648ebe081.webp)

## Embedding Airship Views

Airship views like Preference Center can be embedded as custom views.


#### Swift


```swift
// 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
        )
}
```




![Custom Views for the Apple SDK](https://www.airship.com/docs/images/custom-views-preference-center-scene-apple_hu_6e4d0728f598b316.webp)
