Native SwiftUI chat UI for Chatty — zero WebView, zero compromise.
Drop a fully native, on-brand support chat into any iOS app in minutes. Talks directly to the
same /api/widget/* backend as the Chatty web widget, and renders every bubble, avatar, and
composer with real SwiftUI views — no WKWebView, no JS bridge, no compromise on feel.
Install · Quick start · Design gallery · API reference · Example app
Call ChattyClient(botId: botId).createVoiceToken(sessionId:) and connect the
returned token with the official LiveKit Swift client. ChattyEmbedView
already includes the complete widget voice UI and real-time transcript for
hosts that prefer zero native LiveKit setup.
For a standalone screen, use ChattyVoiceView(botId:); it loads the same
official LiveKit UI in voice-only mode.
Note
Developed and code-reviewed in an environment without an Xcode/swiftc toolchain, so changes
are never compiled locally before pushing. ci.yml runs
swift build / swift test on real macOS runners on every push — check the badge above is
green before relying on a pre-release build, and please open an issue if it isn't.
| No WebView, anywhere | Every bubble, avatar, and the composer are real SwiftUI views — no iframe, no JS bridge, no WKWebView overhead. |
| Matches your dashboard automatically | Fetches the bot's theme and renders with the exact colors, corner radii, and launcher shape chosen in the dashboard — no manual styling. |
| Two integration shapes | A floating ChattyLauncher button + sheet, or an embedded ChattyChatView inside your own view hierarchy. |
| A real composer, not a stub | Full-Unicode emoji picker (search + categories, ~1,850 emoji), animated attach menu (camera, photo library, documents, location), and mic-to-text voice notes — built in, not bolted on. |
| Zero third-party dependencies | Only Apple's own SwiftUI, PhotosUI, AVFoundation, and Foundation. |
In Xcode: File → Add Package Dependencies… and enter:
https://github.com/PersonaliAI/chatty-ios-sdk
Or add it to Package.swift directly:
dependencies: [
.package(url: "https://github.com/PersonaliAI/chatty-ios-sdk", from: "1.1.0")
]CocoaPods
pod 'ChattySDK', '~> 1.1.0'Find your bot ID in the Chatty dashboard under Embed & Integrate → iOS SDK.
Floating launcher (recommended) — a button that expands into a chat sheet, the native equivalent of the web widget's launcher:
import ChattySDK
struct RootView: View {
var body: some View {
ContentView()
.overlay(ChattyLauncher(botId: "YOUR_BOT_ID"))
}
}Embedded full-screen chat — place it directly in your own navigation, e.g. as a "Support" tab:
import ChattySDK
struct SupportScreen: View {
var body: some View {
ChattyChatView(botId: "YOUR_BOT_ID")
}
}The SDK ships all 10 Chatty widget designs as SwiftUI color/radius tokens, ported 1:1 from the
web widget's globals.css, so a native screen looks like whatever design is chosen in the
dashboard rather than one generic look. No configuration required — the SDK fetches the bot's
theme and resolves the matching token set automatically, including legacy widget_style IDs
from older presets.
| Design | Accent |
|---|---|
minimal |
|
playful |
|
corporate |
|
dark-sleek |
|
gradient-glow |
|
glassmorphism |
|
ecommerce |
|
healthcare-calm |
|
neubrutalism |
|
luxury-editorial |
Font pairing (each web design uses a distinct Google Font) is intentionally out of scope for this release; color, radius, and header/bubble treatment carry most of a design's identity.
public init(
botId: String,
baseURL: String = chattyDefaultBaseURL,
host: String? = nil,
position: ChattyPosition = .bottomTrailing,
color: Color? = nil,
onNotificationBellPress: (() -> Void)? = nil,
enableVoiceNotes: Bool = true,
enableNotificationBell: Bool = true,
enableLocationSharing: Bool = true
)| Param | Description |
|---|---|
botId |
Required. Your bot's ID from the dashboard. |
baseURL |
Chatty backend base URL. Defaults to the production API. |
host |
Advisory only — sent to the backend but not used for access control. See Notes. |
position |
.bottomLeading or .bottomTrailing. Default .bottomTrailing. |
color |
Overrides the launcher color. Defaults to the active design's accent color. |
onNotificationBellPress |
Forwarded to ChattyChatView's header notification bell. See Notes. |
enableVoiceNotes |
Forwarded to ChattyChatView. See Permissions. |
enableNotificationBell |
Forwarded to ChattyChatView. See Permissions. |
enableLocationSharing |
Forwarded to ChattyChatView. See Permissions. |
public init(
botId: String,
baseURL: String = chattyDefaultBaseURL,
host: String? = nil,
onMessage: ((ChattyMessage) -> Void)? = nil,
onNotificationBellPress: (() -> Void)? = nil,
onClose: (() -> Void)? = nil,
enableVoiceNotes: Bool = true,
enableNotificationBell: Bool = true,
enableLocationSharing: Bool = true
)| Param | Description |
|---|---|
botId |
Required. Your bot's ID from the dashboard. |
baseURL |
Chatty backend base URL. Defaults to the production API. |
host |
Advisory only — sent to the backend but not used for access control. See Notes. |
onMessage |
Called for every inbound message — useful for unread badges or analytics. |
onNotificationBellPress |
Header notification-bell button tapped, after the OS permission prompt resolves. See Notes. |
onClose |
Renders a close (✕) button in the header when set. ChattyLauncher passes this for you. |
enableVoiceNotes |
Default true. Set false to hide the composer's mic button — the SDK then never calls AVAudioSession.requestRecordPermission at all. See Permissions. |
enableNotificationBell |
Default true. Set false to hide the header's bell button — the SDK then never calls UNUserNotificationCenter.requestAuthorization at all. See Permissions. |
enableLocationSharing |
Default true. Set false to hide the attach menu's Location option — the SDK then never calls CLLocationManager's authorization/location APIs at all. See Permissions. |
Permissions — what this SDK requests, and how to opt out
As a Swift Package, this SDK can't embed its own Info.plist usage-description keys — your app's Info.plist must declare them (see Requirements below). But when the underlying OS permission API actually fires is controlled entirely by this SDK, in direct response to a specific button being tapped:
| API called | Risk | Used for | Requested when |
|---|---|---|---|
AVAudioSession.requestRecordPermission |
Dangerous (microphone) | Composer mic button → voice-note transcription | User taps the mic button, only if enableVoiceNotes (default true) |
UNUserNotificationCenter.requestAuthorization |
Sensitive (notifications) | Header bell button → local notification-permission ask | User taps the bell, only if enableNotificationBell (default true) |
CLLocationManager authorization/location |
Dangerous (location) | Attach menu's Location option → drops a Google Maps link into the composer text | User taps Location, only if enableLocationSharing (default true) |
Camera and Photo Library don't appear here because this SDK never calls their permission APIs
directly — UIImagePickerController(sourceType: .camera) and PhotosPicker/PHPickerViewController
trigger their own OS-managed prompts (or, for the photo picker, no prompt at all) when presented,
outside this SDK's control. Documents (UIDocumentPickerViewController) needs no permission or
Info.plist key at all — it's a sandboxed system file browser, iOS-only (no macOS equivalent is
wired up, same as Camera).
The SDK never calls a permission API speculatively or on load — only in direct response to
the matching button being tapped. If you don't want your app requesting mic access, notification
authorization, or location access through this SDK at all, set the matching enable* param to
false; the button disappears and the SDK will never touch that API. Your app remains free to
request AVAudioSession/UNUserNotificationCenter/CLLocationManager permission itself, on its
own schedule, for its own purposes — this SDK's opt-out only stops this SDK from requesting them.
If enableVoiceNotes is false, you can also drop NSMicrophoneUsageDescription from your own
Info.plist (nothing in the SDK will ever trigger the mic prompt to need it). Same for
NSLocationWhenInUseUsageDescription if enableLocationSharing is false.
Security — bot_id and domain restriction
bot_id is not a secret — it's extractable from any client, web or mobile. Domain restriction
(allowed_domains in the dashboard) is enforced by the backend as a rate-limit tier, not a
hard reject: verified web traffic gets 30 msgs/60s per bot+IP, everything else (including all
mobile SDK traffic — there's no way for a native app to obtain a "verified" token the way a
browser's Referer allows) gets throttled to 5 msgs/120s. The host param this SDK sends is
advisory only and isn't used for access control. If your bot is mobile-primary, leave
allowed_domains empty to get the normal 30/60s tier instead.
Notification bell — what it does and doesn't do
Tapping it requests the OS notification permission and then calls onNotificationBellPress.
That's as far as this SDK goes — actually delivering a push when a reply arrives while the app
is backgrounded needs APNs (or a wrapper like OneSignal) wired up at the app level: register the
device token, send it to your backend, store it against the session/user, and have the backend
call APNs when a message lands for a session that isn't actively polling. None of that exists
yet — it's backend work in chatty-backend.
- Lead capture and meeting booking happen conversationally (the assistant decides to ask/act) — there's no separate REST call to trigger them from the SDK.
- Polling for human-agent takeover messages runs every 4s while
ChattyChatViewis active, matching the web widget's behavior. - Conversation history is persisted locally (
UserDefaults), mirroring the web widget'slocalStoragecache, so a returning user sees their prior messages.
Example/ChattySDKExample.xcodeproj is a minimal, runnable SwiftUI app demonstrating
both integration styles side by side. Open it in Xcode (it resolves ChattySDK as a local Swift
Package pointing at the repo root) and run on a simulator to try the floating launcher and the
embedded full-screen chat against a live demo bot.
-
iOS 15+ (macOS 13+ for the library target)
-
Swift 5.7+, SwiftUI
-
Uses
async/await,@StateObject— no third-party dependencies -
Add these keys to your app's
Info.plistto use the composer's mic, camera, and attach-menu Location button (a library target can't injectInfo.plistentries — this has to be in the consuming app):<key>NSMicrophoneUsageDescription</key> <string>Used to record voice messages in chat.</string> <key>NSCameraUsageDescription</key> <string>Used to attach photos in chat.</string> <key>NSLocationWhenInUseUsageDescription</key> <string>Used to share your location in chat.</string>
Without these, tapping the mic/camera silently does nothing (iOS kills the process on a missing usage string rather than showing an error).
Contributing — bug reports, design-parity fixes, and PRs are welcome.
Licensed under MIT © PersonaliAI