---
title: "SwiftUI and UIKit native picker"
description: "Customize the iOS 0.3.4 picker or build a complete SwiftUI/UIKit layout from the public controller, map, and native components."
---

iOS `0.3.4` provides one native picker implementation for SwiftUI and UIKit.
The ready-made flow, customization layers, public component catalogue, scoped
composition, lifecycle recovery, and typed checkout handoff are part of the
tagged Swift package.

## Application and SDK ownership

| Native SwiftUI/UIKit owns | SeatLayer SDK owns |
| --- | --- |
| Event header, filters, floor/section navigation, confirmation, tiers and quantities, cart, hold state, checkout, loading/errors, Test Mode, attribution and Back | Seats, labels, venue geometry, hit testing, pan/pinch, camera, authored 3D, and panorama pixels |
| Dynamic Type, VoiceOver, safe areas, scene lifecycle, native presentation and haptics | Authoritative inventory state and supported map capabilities |

The picker exposes a replaceable native buyer component tree while SeatLayer
keeps venue geometry and inventory behavior behind the public SDK API.

## Integration ladder

| Path | API | Host ownership |
| --- | --- | --- |
| Ready SwiftUI | `SeatLayerPicker` | Configuration and callbacks |
| Ready UIKit | `SeatLayerPickerViewController` | Navigation and dismissal |
| Branded picker | Theme, strings, options, styles, builders | Brand or selected complete parts |
| Custom SwiftUI | `SeatLayerPickerScope` + public views | Entire SwiftUI hierarchy |
| Custom UIKit | `SeatLayerPickerMapView` or `SeatLayerPickerMapViewController` + controller | Entire UIKit hierarchy |
| Raw map | `SeatLayerView` | Every buyer surface and transition |

## Ready-picker contract

```swift
SeatLayerPicker(
    configuration: SeatLayerConfiguration(
        event: "ev_your_event_key",
        publicKey: "pk_test_your_public_key",
        locale: "en",
        currency: "USD"
    ),
    onCheckout: { handoff in
        try await checkoutBackend.begin(holdId: handoff.holdId)
    }
)
```

For private inventory, replace `publicKey` with a renewable
`buyerAccessTokenProvider` backed by your server. Return only the short-lived
native buyer-access token and treat the SDK access context as opaque.

UIKit's `SeatLayerPickerViewController` hosts the same public component tree.
Call `handleBack()` from host navigation and use
`updateAppearance(theme:themeMode:strings:styles:)` for in-place appearance
changes.

## Four customization layers

### Theme native and map roles together

```swift
let theme = SeatLayerPickerTheme(
    background: "#0F1522",
    surface: "#1A2234",
    text: "#EEF1F8",
    accent: "#FF6584",
    onAccent: "#111827",
    map: SeatLayerPickerMapTheme(
        background: "#0F1522",
        rowLabelColor: "#D7DEEA",
        textColor: "#EEF1F8",
        selectionColor: "#FF6584"
    )
)
```

`.auto` follows the current color scheme without rebuilding the picker.

### Style one surface

```swift
var styles = SeatLayerPickerStyles()
styles[.checkoutBar] = SeatLayerPickerPartStyle(
    background: "#FF6584",
    cornerRadius: 18,
    horizontalPadding: 12,
    verticalPadding: 8
)
```

A style changes appearance, not placement, inventory truth, or hit targets.

### Change optional chrome and wording

```swift
let options = SeatLayerPickerOptions(
    chrome: SeatLayerPickerChromeOptions(floorStrip: false)
)

let strings = SeatLayerPickerStrings(
    overrides: [
        SeatLayerPickerStringKey.continueWord.rawValue: "Review order"
    ],
    localeIdentifier: "fr-FR"
)
```

The 37 bundled locale dictionaries resolve exact BCP-47 tag, then language,
then English. Event and inventory data supplied by SeatLayer stays authoritative.

### Wrap or replace one complete part

```swift
var builders = SeatLayerPickerBuilders()
builders.header = { context in
    AnyView(
        context.defaultContent
            .background(.ultraThinMaterial)
    )
}
```

Every builder receives the current read-only state, controller, presentation state,
resolved style, and canonical `defaultContent`. Missing or throwing builders
fall back to the stock component. Test Mode and attribution deliberately have
no builder authority.

## Build your own SwiftUI hierarchy

`SeatLayerPickerScope` gives the tree one controller, one presentation model,
and one picker state.

```swift
let options = SeatLayerPickerOptions(confirmSelection: true)

SeatLayerPickerScope(options: options) { controller in
    ZStack {
        SeatLayerPickerMap(
            configuration: SeatLayerConfiguration(
                event: "ev_your_event_key",
                publicKey: "pk_test_your_public_key"
            ),
            options: options,
            controller: controller
        )

        VStack {
            SeatLayerPickerHeader()
            SeatLayerPickerPriceLegend()
            SeatLayerPickerFloorStrip()
            Spacer()
            SeatLayerPickerDockBar()
            SeatLayerPickerCartList()
        }
    }
}
```

Callbacks observe rather than own the flow. Custom UI invokes typed semantic
controller actions such as `focusSection`, `overview`, `setFloor`,
`setCategoryFilter`, `refreshAvailability`, and `checkout`.

UIKit applications that own all chrome use `SeatLayerPickerMapView` or
`SeatLayerPickerMapViewController`, observe `pickerController.snapshot`, and
call the same public controller actions.

## Public component catalogue

The ready picker is composed from these same public surfaces:

| Buyer job | Default components |
| --- | --- |
| Identity and venue navigation | `SeatLayerPickerHeader`, price legend, floor selector/strip, section navigator, dock bar |
| Map and filters | `SeatLayerPickerMap`, map controls, accessibility filters |
| Decisions | Best-seats form, seat confirmation/confirm card, tier choices, GA and table prompts |
| Cart and hold | Cart list/sheet, checkout bar, hold countdown, hold-lapse notice, action error |
| Immersive | `SeatLayerVenue3D`, `SeatLayerSeatViewChrome` |
| System states | Loading, retryable/fatal error, empty/sold-out/sales-closed |
| Truth components | `SeatLayerPickerTestModeIndicator`, `SeatLayerPickerAttribution` |

There are 25 replaceable builder parts. Test Mode and attribution are public
truth components outside that replacement matrix.

## State and checkout guarantees

- Picker state always belongs to the active Event and supersedes older state.
- Inventory-changing actions are serialized.
- Repeated Continue taps join one checkout flight.
- Pending tier choice is applied before confirmation and remains authoritative
  through cart and handoff.
- Ordinary picker state omits `holdId`; only
  `SeatLayerPickerCheckoutHandoff` exposes it.
- A successful callback transfers hold ownership to the host.
- A rejected callback rejects only that handoff and returns ownership safely.
- `initialHoldId` restores a host-owned hold; `readOnly: true` blocks mutation
  through both ready-made and custom controller paths.

## Lifecycle and recovery

The ready SwiftUI picker observes `scenePhase`; UIKit observes application
notifications. Foreground reconciliation consumes hold lapse, unavailable
inventory, and authoritative server hold state before new actions. Custom hosts
use `lifecycle`, `refreshAvailability()`, and `synchronize()`.

Back closes the most local state first: prompt → cart → confirmation → focused
section → venue → host. Immersive 3D and panorama actions are capability-gated,
so unsupported actions are never presented or sent.

## Test Mode and required attribution

Custom hosts must render truthful Test Mode state and the bottom-right
attribution when required by the SeatLayer account configuration. Chrome
options and builders cannot force or suppress it.

## Production checklist

- Pin exact Swift package `0.3.4`.
- Validate SwiftUI and UIKit paths used by your product.
- Exercise Dynamic Type, VoiceOver, RTL, rotation, foreground/background,
  interactive dismissal, hold lapse, unavailable inventory, and rejected
  checkout.
- Test real tiers, 3D targets, panorama navigation, Test Mode, and attribution.
- Verify supported physical devices; simulator success is not device proof.
- Confirm the trusted backend inspects and books the handoff hold.

## Product scope

The picker selects one Event. It does not accept native Performance
Group or Season configuration.

## Related pages

- [iOS quick start](/buyer-sdk/ios)
- [Raw-map API](/buyer-sdk/ios/raw-map)
- [Compare mobile SDKs](/buyer-sdk/mobile)
- [`v0.3.4` source](https://github.com/seatlayer/seatlayer-ios/tree/v0.3.4)
- [Tagged picker entry point](https://github.com/seatlayer/seatlayer-ios/blob/v0.3.4/Sources/SeatLayer/Picker/SeatLayerPicker.swift)
- [Tagged native-picker guide](https://github.com/seatlayer/seatlayer-ios/blob/v0.3.4/Docs/native-picker.md)
- [Tagged example](https://github.com/seatlayer/seatlayer-ios/tree/v0.3.4/Example)
- [Tagged changelog](https://github.com/seatlayer/seatlayer-ios/blob/v0.3.4/CHANGELOG.md)
- [Troubleshooting matrix](/buyer-sdk/mobile#troubleshooting)