Skip to content

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.

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.

import SwiftUI
import TwaiCore
import TwaiLiveKit
import 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.

  • TwaiCore — pure Swift. Codable protocol types + TwaiAPIClient (createSession + getAvatar) + TwaiCredentials + typed TwaiError ladder.
  • TwaiLiveKit — Transport 0 engine (actor-owned LiveKit Room + mint-closure flow + event stream + SEND_GRACE_MS warm-up).
  • TwaiPlayer — SwiftUI TwaiPlayerView + TwaiPlayerHandle imperative control.

Depend on TwaiPlayer for the typical case; it transitively pulls Core + LiveKit.

let result = try await api.getAvatar(owner: "alman", name: "dr-alman")
print(result.avatar.displayName)
print(result.avatar.avatarId)

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 UIViewRepresentable wrapper.
  • twai-protocol Swift 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).