rivetkit-client-swiftui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRivetKit SwiftUI Client
RivetKit SwiftUI 客户端
Use this skill when building SwiftUI apps that connect to Rivet Actors with .
RivetKitSwiftUI在使用构建连接到Rivet Actors的SwiftUI应用时,可以参考本指南。
RivetKitSwiftUIVersion
版本
RivetKit version: 2.0.42
RivetKit 版本:2.0.42
Install
安装
Add the Swift package dependency and import :
RivetKitSwiftUIswift
// 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")
]
)
]RivetKitSwiftUIRivetKitClientSwiftUI添加Swift包依赖并导入:
RivetKitSwiftUIswift
// 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")
]
)
]RivetKitSwiftUIRivetKitClientSwiftUIMinimal 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 property wrapper always uses get-or-create semantics and accepts:
@Actor- (required)
name - as
keyorString(required)[String] - (optional connection parameters)
params - (optional creation input)
createWithInput - (optional creation hint)
createInRegion - (toggle connection lifecycle)
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)")
}
}@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 when contains user data. Use arrays instead to prevent key injection attacks.
"org:\(userId)"userId键用于唯一标识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 or once at the root of your view tree:
.rivetKit(endpoint:).rivetKit(client:)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 , the client is created once and cached per endpoint. When using , store the client as a property on (not inside ) since SwiftUI can call multiple times.
.rivetKit(endpoint:).rivetKit(client:)Appbodybody在视图树的根节点调用或进行配置:
.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)
}
}
}使用时,客户端会根据端点URL创建并缓存。使用时,请将客户端存储为的属性(不要放在内),因为SwiftUI可能会多次调用方法。
.rivetKit(endpoint:).rivetKit(client:)AppbodybodyEnvironment Variables
环境变量
ClientConfig- - Namespace (can also be in endpoint URL)
RIVET_NAMESPACE - - Authentication token (can also be in endpoint URL)
RIVET_TOKEN - - Runner name (defaults to
RIVET_RUNNER)"default"
The endpoint is always required. There is no default endpoint.
ClientConfig- - 命名空间(也可以包含在端点URL中)
RIVET_NAMESPACE - - 认证令牌(也可以包含在端点URL中)
RIVET_TOKEN - - 运行器名称(默认值为
RIVET_RUNNER)"default"
端点是必填项,没有默认值。
Endpoint Format
端点格式
Endpoints support URL auth syntax:
https://namespace:token@api.rivet.devYou can also pass the endpoint without auth and provide and separately. For serverless deployments, set the endpoint to your app's URL. See Endpoints for details.
RIVET_NAMESPACERIVET_TOKEN/api/rivet端点支持URL认证语法:
https://namespace:token@api.rivet.devAPI Reference
API 参考
Property Wrapper
属性包装器
- - SwiftUI property wrapper for actor connections
@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)
- - 用于Actor连接的SwiftUI属性包装器
@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)
View Modifiers
视图修饰符
- - Configure client with an endpoint URL (creates cached client)
.rivetKit(endpoint:) - - Configure client with a custom instance
.rivetKit(client:) - - Subscribe to actor events (supports 0–5 typed args)
.onActorEvent(actor, event) { ... } - - Handle actor errors
.onActorError(actor) { error in ... }
- - 使用端点URL配置客户端(创建缓存的客户端实例)
.rivetKit(endpoint:) - - 使用自定义客户端实例进行配置
.rivetKit(client:) - - 订阅Actor事件(支持0-5个类型化参数)
.onActorEvent(actor, event) { ... } - - 处理Actor错误
.onActorError(actor) { error in ... }
ActorObservable
ActorObservable
- - Async action call
actor.action(name, args..., as:) - - Fire-and-forget action
actor.send(name, args...) - - AsyncStream of typed events
actor.events(name, as:) - - Current connection status
actor.connStatus - - Whether connected
actor.isConnected - - Underlying
actor.handle(optional)ActorHandle - - Underlying
actor.connection(optional)ActorConnection - - Most recent error (optional)
actor.error
- - 异步调用操作方法
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
类型定义
- - Connection status enum (
ActorConnStatus,.idle,.connecting,.connected,.disconnected).disposed - - Typed actor errors with
ActorError,group,code,messagemetadata
- - 连接状态枚举(
ActorConnStatus,.idle,.connecting,.connected,.disconnected).disposed - - 类型化的Actor错误,包含
ActorError、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/skillsThen use the skill for backend guidance.
rivetkit如果你需要了解更多关于Rivet Actors、注册中心或服务端RivetKit的内容,请添加主技能包:
bash
npx skills add rivet-dev/skills然后使用技能获取服务端相关指南。
rivetkit