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
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 | Emoji picker, animated attach menu (camera + photo library), 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.0.4")
]CocoaPods
pod 'ChattySDK', '~> 1.0.4'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,
onVoiceCallPress: (() -> Void)? = nil,
onNotificationBellPress: (() -> Void)? = nil
)| 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. |
onVoiceCallPress |
Forwarded to ChattyChatView's header voice-call button. See Notes. |
onNotificationBellPress |
Forwarded to ChattyChatView's header notification bell. See Notes. |
public init(
botId: String,
baseURL: String = chattyDefaultBaseURL,
host: String? = nil,
onMessage: ((ChattyMessage) -> Void)? = nil,
onVoiceCallPress: (() -> Void)? = nil,
onNotificationBellPress: (() -> Void)? = nil,
onClose: (() -> Void)? = nil
)| 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. |
onVoiceCallPress |
Header voice-call button tapped. Only shown when the bot's dashboard has voice enabled. See Notes. |
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. |
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.
Voice-call button
Only shown when the bot's dashboard has voice enabled, and only fires onVoiceCallPress — this
SDK doesn't bundle a voice-call implementation (a separate LiveKit integration, out of scope
here).
- 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 and camera buttons (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>
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