About rivetkit-client-swiftui
A skill for building SwiftUI apps that connect to Rivet Actors with `RivetKitSwiftUI`. It documents the `@Actor` property wrapper, action calls, event streams, connection status, and client configuration.
Key features
- 01Connect actors with the `@Actor` wrapper
- 02Call actions asynchronously or fire-and-forget
- 03Subscribe to typed events and streams
- 04Inspect connection status and errors
- 05Configure the client on the view tree
Use cases
- 01Build SwiftUI views backed by Rivet Actors
- 02Call actor actions from an app interface
- 03Display actor connection status and handle errors
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ rivet-dev/ skills --skill rivetkit-client-swiftuiAfter installing, type/rivetkit-client-swiftuiin Claude Code to confirm it loaded.
Bundled filesindex.json
Details
From the project READMERivetKit SwiftUI Client
Use this skill when building SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI.
Version
RivetKit version: 2.3.7
Error Handling Policy
- Prefer fail-fast behavior by default.
- Avoid broad
do/catchunless absolutely needed. - If a catch block is used, handle the error explicitly, at minimum by logging it.
Install
Add the Swift package dependency and import RivetKitSwiftUI:
// Package.swift
dependencies: [
.package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0")
]
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "RivetKitSwiftUI", package: "rivetkit-swift")
]
)
]
RivetKitSwiftUI re-exports RivetKitClient and SwiftUI, so a single import covers both.
Minimal Client
import RivetKitSwiftUI
import SwiftUI
@main
struct HelloWorldApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(endpoint: "https://my-namespace:[email protected]")
}
}
}
import RivetKitSwiftUI
import SwiftUI
struct ContentView: View {
@Actor("counter", key: ["my-counter"]) private var counter
@State private var count = 0
var body: some View {
VStack(spacing: 16) {
Text("\(count)")
.font(.system(size: 64, weight: .bold, design: .rounded))
Button("Increment") {
counter.send("increment", 1)
}
.disabled(!counter.isConnected)
}
.task {
count = (try? await counter.action("getCount")) ?? 0
}
.onActorEvent(counter, "newCount") { (newCount: Int) in
count = newCount
}
}
}
Actor Options
The @Actor property wrapper always uses get-or-create semantics and accepts:
name(required)keyasStringor[String](required)params(optional connection parameters)createWithInput(optional creation input)createInRegion(optional creation hint)enabled(toggle connection lifecycle)
import RivetKitSwiftUI
import SwiftUI
struct ConnParams: Encodable {
let authToken: String
}
struct ChatView: View {
@Actor(
"chatRoom",
key: ["general"],
params: ConnParams(authToken: "jwt-token"),
enabled: true
) private var chat
var body: some View {
Text("Chat: \(chat.connStatus.rawValue)")
}
}
Actions
import RivetKitSwiftUI
import SwiftUI
struct CounterView: View {
@Actor("counter", key: ["my-counter"]) private var counter
@State private var count = 0
@State private var name = ""
var body: some View {
VStack {
Text("Count: \(count)")
Text("Name: \(name)")
Button("Fetch") {
Task {
count = try await counter.action("getCount")
name = try await counter.action("rename", "new-name")
}
}
Button("Increment") {
counter.send("increment", 1)
}
}
}
}
Subscribing to Events
import RivetKitSwiftUI
import SwiftUI
struct GameView: View {
@Actor("game", key: ["game-1"]) private var game
@State private var count = 0
@State private var isGameOver = false
var body: some View {
VStack {
Text("Count: \(count)")
if isGameOver {
Text("Game Over!")
}
}
.onActorEvent(game, "newCount") { (newCount: Int) in
count = newCount
}
.onActorEvent(game, "gameOver") {
isGameOver = true
}
}
}
Async Event Streams
import RivetKitSwiftUI
import SwiftUI
struct ChatView: View {
@Actor("chatRoom", key: ["general"]) private var chat
@State private var messages: [String] = []
var body: some View {
List(messages, id: \.self) { message in
Text(message)
}
.task {
for await message in chat.events("message", as: String.self) {
messages.append(message)
}
}
}
}
Connection Status
import RivetKitSwiftUI
import SwiftUI
struct StatusView: View {
@Actor("counter", key: ["my-counter"]) private var counter
@State private var count = 0
var body: some View {
VStack {
Text("Status: \(counter.connStatus.rawValue)")
if counter.connStatus == .connected {
Text("Connected!")
.foregroundStyle(.green)
}
Button("Fetch via Handle") {
Task {
if let handle = counter.handle {
count = try await handle.action("getCount", as: Int.self)
}
}
}
.disabled(!counter.isConnected)
}
}
}
Error Handling
import RivetKitSwiftUI
import SwiftUI
struct UserView: View {
@Actor("user", key: ["user-123"]) private var user
@State private var errorMessage: String?
@State private var username = ""
var body: some View {
VStack {
TextField("Username", text: $username)
Button("Update Username") {
Task {
do {
let _: String = try await user.action("updateUsername", username)
} catch let error as ActorError {
errorMessage = "\(error.code): \(String(describing: error.metadata))"
}
}
}
if let errorMessage {
Text(errorMessage)
.foregroundStyle(.red)
}
}
.onActorError(user) { error in
errorMessage = "\(error.group).\(error.code): \(error.message)"
}
}
}
Concepts
Keys
Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:
import RivetKitSwiftUI
import SwiftUI
struct OrgChatView: View {
@Actor("chatRoom", key: ["org-acme", "general"]) private var room
var body: some View {
Text("Room: \(room.connStatus.rawValue)")
}
}
Don't build keys with string interpolation like "org:\(userId)" when userId contains user data. Use arrays instead to prevent key injection attacks.
Environment Configuration
Call .rivetKit(endpoint:) or .rivetKit(client:) once at the root of your view tree:
// With endpoint string (recommended for most apps)
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(endpoint: "https://my-namespace:[email protected]")
}
}
}
// With custom client (for advanced configuration)
@main
struct MyApp: App {
private let client = RivetKitClient(
config: try! ClientConfig(endpoint: "https://api.rivet.dev", token: "pk_...")
)
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(client: client)
}
}
}
When using .rivetKit(endpoint:), the client is created once and cached per endpoint. When using .rivetKit(client:), store the client as a property on App (not inside body) since SwiftUI can call body multiple times.
Environment Variables
ClientConfig reads optional values from environment variables:
RIVET_NAMESPACE- Namespace (can also be in endpoint URL)RIVET_TOKEN- Authentication token (can also be in endpoint URL)RIVET_RUNNER- Runner name (defaults to"default")
The endpoint is always required. There is no default endpoint.
Endpoint Format
Endpoints support URL auth syntax:
https://namespace:[email protected]
You can also pass the endpoint without auth and provide RIVET_NAMESPACE and RIVET_TOKEN separately. For serverless deployments, set the endpoint to your app's /api/rivet URL. See Endpoints for details.
API Reference
Property Wrapper
@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)- SwiftUI property wrapper for actor connections
View Modifiers
.rivetKit(endpoint:)- Configure client with an endpoint URL (creates cached client).rivetKit(client:)- Configure client with a custom instance.onActorEvent(actor, event) { ... }- Subscribe to actor events (supports 0–5 typed args).onActorError(actor) { error in ... }- Handle actor errors
ActorObservable
actor.action(name, args..., as:)- Async action callactor.send(name, args...)- Fire-and-forget actionactor.events(name, as:)- AsyncStream of typed eventsactor.connStatus- Current connection statusactor.isConnected- Whether connectedactor.handle- UnderlyingActorHandle(optional)actor.connection- UnderlyingActorConnection(optional)actor.error- Most recent error (optional)
Types
ActorConnStatus- Connection status enum (.idle,.connecting,.connected,.disconnected,.disposed)ActorError- Typed actor errors withgroup,code,message,metadata
Need More Than the Client?
If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:
npx skills add rivet-dev/skills
Then use the rivetkit skill for backend guidance.