rivetkit-client-swiftui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

RivetKit SwiftUI Client

RivetKit SwiftUI 客户端

Use this skill when building SwiftUI apps that connect to Rivet Actors with
RivetKitSwiftUI
.
在使用
RivetKitSwiftUI
构建连接到Rivet Actors的SwiftUI应用时,可以参考本指南。

Version

版本

RivetKit version: 2.0.42
RivetKit 版本:2.0.42

Install

安装

Add the Swift package dependency and import
RivetKitSwiftUI
:
swift
// 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.
添加Swift包依赖并导入
RivetKitSwiftUI
:
swift
// 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
会自动重导出
RivetKitClient
SwiftUI
,因此只需一次导入即可使用两者的功能。

Minimal Client

最简客户端示例

swift
import RivetKitSwiftUI
import SwiftUI

@main
struct HelloWorldApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(endpoint: "https://my-namespace:pk_...@api.rivet.dev")
        }
    }
}
swift
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
        }
    }
}
swift
import RivetKitSwiftUI
import SwiftUI

@main
struct HelloWorldApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(endpoint: "https://my-namespace:pk_...@api.rivet.dev")
        }
    }
}
swift
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

Actor 配置选项

The
@Actor
property wrapper always uses get-or-create semantics and accepts:
  • name
    (required)
  • key
    as
    String
    or
    [String]
    (required)
  • params
    (optional connection parameters)
  • createWithInput
    (optional creation input)
  • createInRegion
    (optional creation hint)
  • enabled
    (toggle connection lifecycle)
swift
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)")
    }
}
@Actor
属性包装器始终采用“获取或创建”的语义,支持以下参数:
  • name
    (必填)
  • key
    ,类型为
    String
    [String]
    (必填)
  • params
    (可选的连接参数)
  • createWithInput
    (可选的创建输入参数)
  • createInRegion
    (可选的创建区域提示)
  • enabled
    (切换连接生命周期)
swift
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

操作方法

swift
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)
            }
        }
    }
}
swift
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

订阅事件

swift
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
        }
    }
}
swift
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

异步事件流

swift
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)
            }
        }
    }
}
swift
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

连接状态

swift
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)
        }
    }
}
swift
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

错误处理

swift
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)"
        }
    }
}
swift
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)

Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:
swift
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.
键用于唯一标识Actor实例。可以使用复合键(数组)进行分层寻址:
swift
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)")
    }
}
userId
包含用户数据时,不要使用字符串插值(如
"org:\(userId)"
)来构建键,应使用数组形式以防止键注入攻击。

Environment Configuration

环境配置

Call
.rivetKit(endpoint:)
or
.rivetKit(client:)
once at the root of your view tree:
swift
// With endpoint string (recommended for most apps)
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(endpoint: "https://my-namespace:pk_...@api.rivet.dev")
        }
    }
}

// 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.
在视图树的根节点调用
.rivetKit(endpoint:)
.rivetKit(client:)
进行配置:
swift
// 使用端点字符串(大多数应用推荐此方式)
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .rivetKit(endpoint: "https://my-namespace:pk_...@api.rivet.dev")
        }
    }
}

// 使用自定义客户端(适用于高级配置)
@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)
        }
    }
}
使用
.rivetKit(endpoint:)
时,客户端会根据端点URL创建并缓存。使用
.rivetKit(client:)
时,请将客户端存储为
App
的属性(不要放在
body
内),因为SwiftUI可能会多次调用
body
方法。

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.
ClientConfig
会从环境变量中读取可选值:
  • RIVET_NAMESPACE
    - 命名空间(也可以包含在端点URL中)
  • RIVET_TOKEN
    - 认证令牌(也可以包含在端点URL中)
  • RIVET_RUNNER
    - 运行器名称(默认值为
    "default"
端点是必填项,没有默认值。

Endpoint Format

端点格式

Endpoints support URL auth syntax:
https://namespace:token@api.rivet.dev
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.
端点支持URL认证语法:
https://namespace:token@api.rivet.dev
你也可以传递不带认证信息的端点,单独提供
RIVET_NAMESPACE
RIVET_TOKEN
。对于无服务器部署,请将端点设置为应用的
/api/rivet
URL。详情请参考端点

API Reference

API 参考

Property Wrapper

属性包装器

  • @Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)
    - SwiftUI property wrapper for actor connections
  • @Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)
    - 用于Actor连接的SwiftUI属性包装器

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
  • .rivetKit(endpoint:)
    - 使用端点URL配置客户端(创建缓存的客户端实例)
  • .rivetKit(client:)
    - 使用自定义客户端实例进行配置
  • .onActorEvent(actor, event) { ... }
    - 订阅Actor事件(支持0-5个类型化参数)
  • .onActorError(actor) { error in ... }
    - 处理Actor错误

ActorObservable

ActorObservable

  • actor.action(name, args..., as:)
    - Async action call
  • actor.send(name, args...)
    - Fire-and-forget action
  • actor.events(name, as:)
    - AsyncStream of typed events
  • actor.connStatus
    - Current connection status
  • actor.isConnected
    - Whether connected
  • actor.handle
    - Underlying
    ActorHandle
    (optional)
  • actor.connection
    - Underlying
    ActorConnection
    (optional)
  • actor.error
    - Most recent error (optional)
  • actor.action(name, args..., as:)
    - 异步调用操作方法
  • actor.send(name, args...)
    - 即发即弃式操作调用
  • actor.events(name, as:)
    - 类型化事件的异步流
  • actor.connStatus
    - 当前连接状态
  • actor.isConnected
    - 是否已连接
  • actor.handle
    - 底层的
    ActorHandle
    (可选)
  • actor.connection
    - 底层的
    ActorConnection
    (可选)
  • actor.error
    - 最近的错误信息(可选)

Types

类型定义

  • ActorConnStatus
    - Connection status enum (
    .idle
    ,
    .connecting
    ,
    .connected
    ,
    .disconnected
    ,
    .disposed
    )
  • ActorError
    - Typed actor errors with
    group
    ,
    code
    ,
    message
    ,
    metadata
  • ActorConnStatus
    - 连接状态枚举(
    .idle
    ,
    .connecting
    ,
    .connected
    ,
    .disconnected
    ,
    .disposed
  • ActorError
    - 类型化的Actor错误,包含
    group
    code
    message
    metadata
    字段

Need More Than the Client?

需要更多功能?

If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:
bash
npx skills add rivet-dev/skills
Then use the
rivetkit
skill for backend guidance.
如果你需要了解更多关于Rivet Actors、注册中心或服务端RivetKit的内容,请添加主技能包:
bash
npx skills add rivet-dev/skills
然后使用
rivetkit
技能获取服务端相关指南。