rivetkit-client-swiftui

安装量: 1.6K
排名: #957

安装

npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-swiftui

RivetKit SwiftUI Client

Use this skill when building SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI.

Version

RivetKit version: 2.0.42-rc.1

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: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 Options

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

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: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.

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: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.

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 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) Types ActorConnStatus - Connection status enum (.idle, .connecting, .connected, .disconnected, .disposed) ActorError - Typed actor errors with 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:

npx skills add rivet-dev/skills

Then use the rivetkit skill for backend guidance.

返回排行榜