The official iOS SDK for embedding interactive SeatLayer reserved-seating maps.
It hosts the SeatLayer buyer experience in WKWebView and exposes selection,
holds, best available, general admission, floors, and live events through a
typed Swift API.
Developer docs · Live demo · Website · Flutter SDK · Native Android · React Native · AI Toolkit
Production views load the immutable, version-pinned mobile document and its lazy
assets from https://cdn.seatlayer.io. This canonical HTTPS origin is required
for origin-bound private buyer sessions; no event key or bearer is put in the
page URL.
- Swift package (SPM), iOS 15+
- Hosted runtime:
seatlayer-js@0.66.0/mobile.html - Explicit offline demo/test fixture:
seatlayer-js@0.59.0 - Protocol revision: 1
In Xcode, choose File → Add Package Dependencies and enter:
https://github.com/seatlayer/seatlayer-ios.git
Or declare it explicitly in a manifest:
dependencies: [
.package(url: "https://github.com/seatlayer/seatlayer-ios.git", from: "0.2.0")
]Add the SeatLayer product to your iOS target, then import it:
import SeatLayerlet map = SeatLayerView()
map.delegate = self
var config = SeatLayerConfiguration(event: "ev_xxx", currency: "USD")
config.apiBase = "https://api.seatlayer.io"
let info = try await map.load(config)
if case .test = info.mode { showTestBadge() } // books no real inventory
let hold = try await map.hold()For private channel inventory, mint short-lived sessions on your backend for
the exact allowed origin https://cdn.seatlayer.io and provide renewals in
memory:
config.buyerAccessTokenProvider = { context in
try await buyerBackend.mintSeatLayerAccess(reason: context.reason)
}Give SeatLayerView an explicit height or make it full-screen.
The iOS app selects and holds inventory. Your trusted backend inspects and books the hold after payment or order validation.
- Never ship a SeatLayer secret key in the app binary or WebView.
- Send only the
holdIdand your normal checkout context to your backend. - Calculate the charge from server-inspected hold items, not app input.
- Reuse your stable order id as
bookingReffor safe booking retries.
Read how the integration works before connecting checkout.
The map must be a fixed-height or full-screen box. Do not put it inside a
UIScrollView, List, or SwiftUI ScrollView. The canvas consumes pan and
pinch to drive its own zoom, so an enclosing scroll view and the map fight over
every gesture and neither behaves. Give it a definite frame.
The SDK already disables the WebView affordances that fight the canvas: scroll, bounce, double-tap zoom, long-press callout, and text selection.
hold · resumeHold · extendHold · release · releaseLabels ·
bestAvailable · holdGA · setSeatTier · getSelection · getCurrentHold ·
selectObjects · deselectObjects · clearSelection · selectCategories ·
deselectCategories · setSelectableObjects · setMaxSelection ·
getSelectionValidity · refreshAccess · getGAAreas · getFloors ·
setFloor · setColorblindSafe · setViewMode · getViewMode · zoomIn ·
zoomOut · zoomToFit · destroy — all
async throws, all named to match the web SeatingChart so the two SDKs read
as one product.
Events reach SeatLayerViewDelegate, which has a no-op default for every
method: ready, selectionChanged, selection validity/valid/invalid/limit,
buyer-access expired/unavailable, selected-object unavailable, holdChanged,
holdRestored, holdExpired, gaClick, hint, seatHover, deckTap, error, plus
didReceiveUnknownEvent for anything a newer bundle introduces.
The bundle ships new enum values to apps compiled a year earlier, so every
bridged enum has an unknown(String) case and no decoder throws on an
unfamiliar value:
EventMode,TransportName,ObjectType,SeatStatus,SeatLayerViewMode,EnvelopeKind- unknown payload fields survive on
JSONValueand are ignored by the typed structs - error
codeis an open string set — API codes likesold_outpass through untouched - an unknown command name comes back as
unsupported_command, never a crash
BundleInfo.supports(command:) lets an app hide UI an older bundle lacks rather
than discovering unsupported_command at tap time.
Five deliberate divergences, each forced by the platform rather than chosen:
EnvelopeKind.unknownis tolerated, not rejected. The webdecode()returnsnullfor an unrecognisedk, because a page receives unrelatedpostMessagetraffic it must filter out. On iOS the only writer to ourWKScriptMessageHandleris our own bundle, so an unknownkmeans a newer bundle, not foreign traffic. It decodes as.unknownand the router drops it, which keeps an old app forward-compatible.sl_timeouthas no web-side counterpart. The web bridge answers everycmd, so a missing reply means the WebView stalled or was torn down. The native client enforces its own 15s deadline; a late reply for a timed-out id is dropped rather than delivered.init.protocolis sent as a{min,max}range, not a bare number. The web accepts both; a range is what makes the intersection meaningful in both upgrade directions.- Negotiation runs natively before replying. The web side also checks, but
failing first means the app never asks for a chart it could not drive, and
the caller gets a typed
.incompatibleerror instead of a blank view. chrome.seatTooltipdefaults tofalse. A hover tooltip is a pointer affordance; on touch the host should draw its own seat sheet fromseatHoverDidChange.
Event coalescing is not mirrored — the web side already coalesces
seat.hover and selection.changed to one envelope per frame, so the native
side receives pre-coalesced traffic and only needs the stale-n filter.
xcodebuild -scheme SeatLayer -destination 'generic/platform=iOS' # library
xcodebuild -project Example/SeatLayerDemo.xcodeproj \
-scheme SeatLayerDemo -destination 'generic/platform=iOS Simulator' # example
swift test # contract suite
Tests cover envelope encode/decode, correlation, concurrent commands, timeout
and late-reply dropping, version negotiation in both directions, stale-event
filtering, and unknown-enum tolerance. None of them requires a WebView —
BridgeChannel is a protocol and the tests substitute a double.
Example/SeatLayerDemo.xcodeproj runs on a simulator against a real SeatLayer
API. Its explicit local fixture override is retained for development; production
uses the pinned hosted page. The example fetches chart and object data, upgrades
to the event WebSocket, and exercises hold, extend and release. By default it expects
the locally seeded ios-e2e-show event at http://localhost:8787; replace
those two constants with a production HTTPS API and event key for a hosted
smoke test.
Docs/simulator-handshake.png is the historical first successful bridge
capture, taken against bundle 0.29.0. It is a record of that run, not a
current-state screenshot — re-capture it when the simulator evidence is next
refreshed.
The first run rendered the chart correctly but timed out at
sl_handshake_timeout (Docs/simulator-before-evt-fix.png). JavaScript has a
single number type, so n: 1 reaches WKScriptMessage as an NSNumber holding
a double; the decoder demanded a strict integer and rejected every evt —
including sys.ready. hello, init, res and err carry no n, which is
exactly why the handshake got as far as a fully drawn map and no further. The
decoder now matches the web's isFiniteInt (any finite, integral number), with
a regression test.
- Mobile SDK guide
- Buyer SDK installation
- Holds and checkout
- Complete checkout example
- JavaScript and React SDKs
- SeatLayer Flutter SDK
- Agent-readable documentation
| Surface | Package or source |
|---|---|
| JavaScript | @seatlayer/js |
| React | @seatlayer/react |
| React Native | @seatlayer/react-native |
| Flutter | seatlayer |
| Android | seatlayer-android |
| Server SDKs | Node.js, Python, PHP, Ruby, .NET, Java, and Go |
MIT © SeatLayer