iOS native SDK (Swift)
The iOS native SDK is for Swift-first teams who don’t want to add Flutter to their stack. Pure Swift, SwiftUI primary, Swift Package Manager distribution. Verified end-to-end on Xcode + physical iPhone.
Repo: 2waiorg/2wai-toolkit-public under ios-native/ (candidate for extraction to sibling twai-ios-sdk repo).
Reference sample: ios-native/Samples/HelloAvatar/ — ~70 LOC SwiftUI app, runs on physical iPhone.
Install
Section titled “Install”Add to your Package.swift:
dependencies: [ .package( url: "https://github.com/2waiorg/2wai-toolkit-public", branch: "main" ),]
.target(name: "YourApp", dependencies: [ .product(name: "TwaiPlayer", package: "2wai-toolkit"),])Requires iOS 15+, Swift 5.9+, Xcode 15+. The package transitively pulls livekit/client-sdk-swift ~ 2.3.
Minimum working integration (SwiftUI)
Section titled “Minimum working integration (SwiftUI)”import SwiftUIimport TwaiCoreimport TwaiLiveKitimport TwaiPlayer
struct AvatarScreen: View { @State private var handle: TwaiPlayerHandle?
private let api = TwaiAPIClient( credentials: .mintProxy(url: URL(string: "https://my-app.com/api/mint")!) )
var body: some View { TwaiPlayerView(mint: mint) .onReady { handle = $0 } .task { try? await handle?.send("Hi — tell me what you can do.") } }
private func mint() async -> TwaiMintResult { do { let s = try await api.createSession(.init( avatarId: "avt_...", voiceId: "vc_...", mode: .video )) return .enabled(url: s.livekitUrl, token: s.participantToken, room: s.roomName) } catch TwaiError.rateLimited(let secs, _) { return .rateLimited(retryAfterSeconds: secs ?? 60) } catch { return .unavailable(message: "\(error)", cause: error) } }}That’s a complete working mint-and-play app. TwaiPlayerView owns the engine, calls your mint closure on appear, joins the LiveKit room, renders the agent’s video track.
Three library products
Section titled “Three library products”TwaiCore— pure Swift. Codable protocol types +TwaiAPIClient(createSession + getAvatar) +TwaiCredentials+ typedTwaiErrorladder.TwaiLiveKit— Transport 0 engine (actor-owned LiveKit Room + mint-closure flow + event stream + SEND_GRACE_MS warm-up).TwaiPlayer— SwiftUITwaiPlayerView+TwaiPlayerHandleimperative control.
Depend on TwaiPlayer for the typical case; it transitively pulls Core + LiveKit.
Resolve by handle (catalog hydrate)
Section titled “Resolve by handle (catalog hydrate)”let result = try await api.getAvatar(owner: "alman", name: "dr-alman")print(result.avatar.displayName)print(result.avatar.avatarId)v0.1 scope + what’s deferred
Section titled “v0.1 scope + what’s deferred”In v0.1: TwaiAPIClient (REST + types + errors), TwaiEngine (Transport 0, mint-closure, event stream), TwaiPlayerView (SwiftUI), one sample app.
Deferred to v0.2:
TwaiMic+TwaiTranscriber— mic lifecycle (needs its own iPhone verification pass against the AVAudioSession coexistence gotchas).- UIKit
UIViewRepresentablewrapper. twai-protocolSwift conformance (needs Swift codegen added to that repo first).- XCFramework distribution (SPM only in v0.1).
- Call mode (deferred until Mason’s web SDK PR #4 lands).
- Design doc — shape rationale.
- PR #36 — the merged v0.1 PR (device-verified: greeting, audio, Q&A, idle).
- HelloAvatar sample — ~70 LOC SwiftUI, verified running on physical iPhone.
- LLM prompt for adding this to your app