Mobile SDKs
Swift (iOS)
VocallSDK -- async/await, Combine, SwiftUI
Swift SDK
The Swift SDK provides a native client for integrating Vocall into iOS and macOS applications.
It uses URLSessionWebSocketTask for transport and @Published properties with Combine for
reactive state management. No external dependencies are required.
Installation
Xcode: File > Add Package Dependencies, then enter the repository URL:
https://github.com/primoia/vocall-swift-sdk.git
Package.swift:
dependencies: [
.package(url: "https://github.com/primoia/vocall-swift-sdk.git", from: "0.1.0")
]
Requirements
| Requirement | Minimum Version | |-------------|-----------------| | Swift | 5.9+ | | iOS | 16.0+ | | macOS | 13.0+ | | Dependencies | None |
Creating a Client
VocallClient is marked @MainActor and conforms to ObservableObject, making it ready
for SwiftUI out of the box:
let client = VocallClient(
serverURL: URL(string: "wss://engine.example.com")!,
token: "your-auth-token",
visitorId: "user-abc-123"
)
Published Properties
Observe state changes reactively in SwiftUI or via Combine:
@Published var status: VocallStatus // Current connection/processing state
@Published var messages: [ChatMessage] // Chat history
@Published var sessionId: String? // Active session identifier
@Published var isConnected: Bool // WebSocket connection state
@Published var pendingConfirmSeq: Int? // Awaiting user confirmation
@Published var voiceEnabled: Bool // Whether voice is available
VocallStatus Enum
enum VocallStatus: String, Codable {
case idle
case connecting
case connected
case ready
case processing
case disconnected
case error
}
Use a switch to handle each state:
switch client.status {
case .idle:
Text("Not connected")
case .connecting:
ProgressView("Connecting...")
case .connected, .ready:
Text("Ready").foregroundColor(.green)
case .processing:
ProgressView("Thinking...")
case .disconnected:
Text("Disconnected").foregroundColor(.gray)
case .error:
Text("Error").foregroundColor(.red)
}
Callbacks
Register closures for engine-driven events:
client.onToast = { message, level in
toastManager.show(message, level: level)
}
client.onConfirm = { seq, message in
showConfirmAlert(seq: seq, message: message)
}
client.onCommand = { command in
handleCommand(command)
}
client.onStatusChange = { newStatus in
print("Status changed to: \(newStatus)")
}
client.onChatMessage = { message in
print("New message: \(message.text)")
}
Methods
// Connect with a manifest (async)
try await client.connect(manifest: manifest)
// Send a text message
client.sendText("Emitir nota fiscal para o tomador Maria")
// Respond to a confirmation prompt
client.sendConfirm(seq: 1, confirmed: true)
// Send a command result back to the engine
client.sendResult(seq: 2, success: true, data: ["id": "456"])
// Close the connection
client.disconnect()
Field Registry
Bind your UI elements so the engine can read values and trigger actions:
// Register a text field
client.fieldRegistry.registerField(
screenId: "emitir_nfse",
fieldId: "valor_servico",
getValue: { self.valorServico },
setValue: { value in self.valorServico = value }
)
// Register an action
client.fieldRegistry.registerAction(
screenId: "emitir_nfse",
actionId: "submit",
handler: { self.submitForm() }
)
// Navigation
client.fieldRegistry.onNavigate = { screenId in
self.selectedTab = screenId
}
// Modal management
client.fieldRegistry.onOpenModal = { modalId, data in
self.presentModal(id: modalId, data: data)
}
client.fieldRegistry.onCloseModal = { modalId in
self.dismissModal(id: modalId)
}
SwiftUI Integration
Use @StateObject to own the client in your view hierarchy:
struct ContentView: View {
@StateObject private var vocall = VocallClient(
serverURL: URL(string: "wss://engine.example.com")!,
token: "tok_abc",
visitorId: "visitor-1"
)
var body: some View {
VStack {
StatusBadge(status: vocall.status)
ScrollView {
ForEach(vocall.messages) { msg in
MessageBubble(message: msg)
}
}
HStack {
TextField("Type a message...", text: $inputText)
Button("Send") {
vocall.sendText(inputText)
inputText = ""
}
}
}
.task {
try? await vocall.connect(manifest: buildManifest())
}
}
@State private var inputText = ""
}