rivetkit-client-swiftui by rivet-dev/skills
npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-swiftui在构建使用 RivetKitSwiftUI 连接 Rivet Actors 的 SwiftUI 应用时使用此技能。
RivetKit 版本:2.1.6
do/catch。添加 Swift 包依赖并导入 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 重新导出了 RivetKitClient 和 ,因此单个导入即可覆盖两者。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
SwiftUIimport RivetKitSwiftUI
import SwiftUI
@main
struct HelloWorldApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(endpoint: "https://my-namespace:pk_...@api.rivet.dev")
}
}
}
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 属性包装器始终使用获取或创建语义,并接受以下参数:
name (必需)key 作为 String 或 [String] (必需)params (可选连接参数)createWithInput (可选创建输入)createInRegion (可选创建提示)enabled (切换连接生命周期)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)")
}
}
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)
}
}
}
}
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
}
}
}
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)
}
}
}
}
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)
}
}
}
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)"
}
}
}
键唯一标识 Actor 实例。使用复合键(数组)进行分层寻址:
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)")构建键。应改用数组以防止键注入攻击。
在视图树的根部调用一次 .rivetKit(endpoint:) 或 .rivetKit(client:):
// 使用端点字符串(推荐用于大多数应用)
@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:) 时,客户端会按端点创建一次并缓存。使用 .rivetKit(client:) 时,请将客户端存储为 App 上的属性(而不是在 body 内部),因为 SwiftUI 可能会多次调用 body。
ClientConfig 从环境变量中读取可选值:
RIVET_NAMESPACE - 命名空间(也可以在端点 URL 中)RIVET_TOKEN - 身份验证令牌(也可以在端点 URL 中)RIVET_RUNNER - 运行器名称(默认为 "default")端点始终是必需的。没有默认端点。
端点支持 URL 身份验证语法:
https://namespace:token@api.rivet.dev
你也可以传递不带身份验证信息的端点,并单独提供 RIVET_NAMESPACE 和 RIVET_TOKEN。对于无服务器部署,将端点设置为应用的 /api/rivet URL。详情请参阅 端点。
@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:) - 用于 Actor 连接的 SwiftUI 属性包装器.rivetKit(endpoint:) - 使用端点 URL 配置客户端(创建缓存的客户端).rivetKit(client:) - 使用自定义实例配置客户端.onActorEvent(actor, event) { ... } - 订阅 Actor 事件(支持 0–5 个类型化参数).onActorError(actor) { error in ... } - 处理 Actor 错误actor.action(name, args..., as:) - 异步操作调用actor.send(name, args...) - 发送后不管的操作actor.events(name, as:) - 类型化事件的 AsyncStreamactor.connStatus - 当前连接状态actor.isConnected - 是否已连接actor.handle - 底层的 ActorHandle (可选)actor.connection - 底层的 ActorConnection (可选)actor.error - 最近的错误(可选)ActorConnStatus - 连接状态枚举(.idle, .connecting, .connected, .disconnected, .disposed)ActorError - 类型化的 Actor 错误,包含 group, code, message, metadata如果你需要更多关于 Rivet Actors、注册表或服务器端 RivetKit 的信息,请添加主技能:
npx skills add rivet-dev/skills
然后使用 rivetkit 技能获取后端指导。
每周安装量
1.9K
仓库
GitHub 星标
8
首次出现
2026年1月26日
安全审计
安装于
github-copilot1.9K
codex1.1K
gemini-cli1.1K
opencode1.1K
antigravity1.1K
claude-code1.1K
Use this skill when building SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI.
RivetKit version: 2.1.6
do/catch unless absolutely needed.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.
import RivetKitSwiftUI
import SwiftUI
@main
struct HelloWorldApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(endpoint: "https://my-namespace:pk_...@api.rivet.dev")
}
}
}
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
}
}
}
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)
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
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)
}
}
}
}
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
}
}
}
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)
}
}
}
}
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)
}
}
}
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)"
}
}
}
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.
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: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.
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.
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.
@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:) - SwiftUI property wrapper for actor connections.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 errorsactor.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 - Underlying ActorHandle (optional)actor.connection - Underlying ActorConnection (optional)actor.error - Most recent error (optional)ActorConnStatus - Connection status enum (.idle, .connecting, .connected, .disconnected, .disposed)ActorError - Typed actor errors with group, code, message, metadataIf 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.
Weekly Installs
1.9K
Repository
GitHub Stars
8
First Seen
Jan 26, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot1.9K
codex1.1K
gemini-cli1.1K
opencode1.1K
antigravity1.1K
claude-code1.1K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
var body: some View {
Text("Chat: \(chat.connStatus.rawValue)")
}
}