iOS quickstart
Install the package, implement one delegate, present the assistant. Your app ships no assistant UI of its own.
The SDK is a UIKit host with a SwiftUI interior, distributed as a binary XCFramework over Swift Package Manager, targeting iOS 16 and above. Android, Flutter, and React Native follow the same four steps.
Install the SDK
Add the package in Xcode and pick the VoqalSDK library. There is nothing to vendor and no build phase to configure.
// File → Add Package Dependencies… → paste the URL, or add it to Package.swift:.package(url: "https://github.com/VoqalAI/voqal-ios", from: "2.0.0")Then import VoqalSDK wherever you configure the app.
Configure at launch
Call setup once, as early as you can. Calling prewarm straight after opens the backend connection in the background so the first turn skips a cold handshake — it is a no-op until your delegate can supply a token.
import VoqalSDKfunc application( _ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { var configuration = VoqalSDKConfiguration( requestId: "prod-\(session.userId)", theme: VoqalTheme(accent: "#2D5BFF", accent2: "#5EC5F1", appearance: .auto) ) configuration.apiKey = "pk_live_…" // sent as X-Voqal-Key VoqalSDKManager.shared.setup(configuration: configuration) VoqalSDKManager.shared.prewarm(delegate: coordinator) return true}Configuration options
| Option | Type | Required | Description |
|---|---|---|---|
requestId | String | Required | Routes the turn by prefix: prod- reaches your production backend, stg- reaches staging. |
apiKey | String? | Required | Your publishable key, sent as X-Voqal-Key. Resolves which tenant config to load. |
agentURL | URL? | Optional | Overrides the baked engine URL. Point it at localhost while developing. |
theme | VoqalTheme | Optional | Accent pair, appearance (.light, .dark, .auto), and corner radius. |
presentationStyle | VoqalPresentationStyle | Optional | .sheet (default) or .fullScreen. |
recordingInteractionMode | RecordingInteractionMode | Optional | Tap-to-toggle or hold-to-talk on the mic control. |
Implement the delegate
VocalButtonDelegate is the whole integration surface: five methods, all synchronous, all called on the main thread.
extension AppCoordinator: VocalButtonDelegate { // Read live on every request — always hand back a current token. func getToken() -> String { session.accessToken } func getMetaData() -> String? { #"{"country_code":"EGY","user_id":"84213"}"# } func getViewController() -> UIViewController { navigationController } func voqalButton(didUploadRecording result: String) { analytics.track("voqal_turn", result) } func voqalButton(didFailWith error: Error) { logger.error("voqal", error) }}| Method | Returns | Called when |
|---|---|---|
getToken() | String | Every request. Return your user's current backend token — the SDK never caches it. |
getMetaData() | String? | Every request. A JSON string of client context, sent as X-Client-Metadata. |
getViewController() | UIViewController | The assistant needs a presenter, for example to push one of your own screens. |
voqalButton(didUploadRecording:) | Void | A voice turn finished. The payload is the transcript result. |
voqalButton(didFailWith:) | Void | A turn failed. Log it — the assistant has already shown the user a recoverable state. |
getToken() every time. The SDK reads it live on each request rather than holding a copy, so handing back a token you captured at login will start failing the moment it expires.Present the assistant
Open it from your own button, tab, or deep link. It comes up as a near-full sheet by default and manages its own navigation from there.
@objc func openAssistant() { VoqalSDKManager.shared.presentChat(from: self, delegate: coordinator)}// Optional: forward every SDK event into your own logging.VoqalSDKManager.shared.addLogSink(VoqalConsoleLogSink())Add NSMicrophoneUsageDescription to your Info.plist before you run — iOS terminates the app on first microphone access without it.
Next steps
You now have a working assistant. From here, most teams pick up one of these:
- Connect your backend's MCP server so the agent answers from real data instead of general knowledge.
- Read the widget catalogue to see what the agent can draw, and which kinds require a confirm step.
- Attach a log sink and forward SDK diagnostics into your existing error reporting.
- Work through the engine API if you need to drive a turn from somewhere other than an app.
