跳到主要内容

Apple

概述​

本指南介绍如何开始使用 Apple 运行时库。Rive 运行时库是开源的,源代码可在其 GitHub 仓库 中获取。

该库包含一个 API,供 Apple 应用在 UIKit/AppKit 和 SwiftUI 中轻松集成 Rive 资源。运行时可通过 CocoaPods 或 Swift Package Manager 安装。

Apple 运行时目前支持 iOS 14.0+、visionOS 1.0+、tvOS 16.0+、macOS 13.1+ 和 Mac Catalyst 14.0+。

新的 Apple 运行时设计为 Swift 优先的 API,利用 Swift Concurrency,并改进了 Rive 的多线程支持。

入口点是 Rive 类型,它是 Rive 视图配置的容器,包括文件、画板、状态机、适配方式和背景颜色。

添加 Rive 视图的方法是创建一个带有 Rive 对象的 RiveUIView,然后将其添加到视图层次结构中。在 SwiftUI 中,只需在 RiveUIView 对象上调用 .view() 即可。

需要注意的是,虽然支持多线程,但 Rive 对象的调用仍必须在主线程上进行。这通过将函数和类型标记为 @MainActor 在编译时强制执行。

大多数 API 都标记为 throws,并为不同的 Rive 原语提供了 Error 类型。

快速开始​

按照以下步骤快速开始将 Rive 集成到你的 Apple 应用中。

当前运行时​

安装运行时​

随着 CocoaPods 进入维护模式,我们推荐使用 Swift Package Manager。

要通过 Xcode 安装,你可以按照 Apple 的向应用添加包依赖说明,使用 Apple 运行时的 GitHub URL:https://github.com/rive-app/rive-ios。

或者,你可以手动添加依赖,将以下内容添加到你的 Package.swift 文件中:

dependencies: [
.package(url: "https://github.com/rive-app/rive-ios", from: "6.24.0")
]

然后将依赖添加到你的 target:

targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "RiveRuntime", package: "rive-ios")
]
)
]

导入 Rive​

新的 Apple 运行时与旧版运行时在同一个 Swift 包和 CocoaPods pod 中可用。两个运行时 API 都在同一个包中,因此你可以使用相同的导入语句导入运行时。

import RiveRuntime

创建 Worker​

Worker 负责处理 Rive 运行时中的并发。此类型负责启动后台线程进行处理,此外还处理全局(带外)资源。

Worker 必须在 Rive 使用期间保持存活。File 会创建对 Worker 的强引用,因此 Worker 至少会在 File 使用期间保持存活,除非在文件之外保留了 Worker 的引用。

// 在 async 上下文中
// 渲染 3D 内容需要启用 GPU Canvas。
let worker = try await Worker(
configuration: .init(enableGPUCanvas: true)
)

有关此 worker 配置的更多信息,请参见 GPU Canvas。

有关线程的更多信息,请参见线程。

加载文件​

创建 Worker 后,你可以继续创建 File。每个 File 对象需要一个源和一个 worker。

File 初始化器标记为 @MainActor。

// 本地文件
let worker = try await Worker(
configuration: .init(enableGPUCanvas: true)
)
let file = try await File(source: .local("my_file", Bundle.main), worker: worker)
// 远程 URL
let worker = try await Worker(
configuration: .init(enableGPUCanvas: true)
)
let url: URL = URL(string: "https://example.com/my_file.riv")!
let file = try await File(source: .url(url), worker: worker)
// Data 数据
let worker = try await Worker(
configuration: .init(enableGPUCanvas: true)
)
let data: Data = ...
let file = try await File(source: .data(data), worker: worker)

添加视图​

创建 File 后,你可以继续创建 Rive 对象。此对象定义视图的配置。最基本的实现仅通过一个文件创建;Rive 将自动加载默认画板和状态机进行渲染。此外,如果可用,会将编辑器中创作的主视图模型和全局视图模型默认实例绑定到状态机。

创建 Rive 对象后,你可以初始化视图。在 UIKit 中,通过创建 RiveUIView 并将其添加到视图层次结构中。在 SwiftUI 中,使用 RiveUIViewRepresentable 和 AsyncRiveUIViewRepresentable 类型。

// UIKit
let riveView = RiveUIView({
let worker = try await Worker(
configuration: .init(enableGPUCanvas: true)
)
let file = try await File(source: .local("my_file", Bundle.main), worker: worker)
return try await Rive(file: file)
})
view.addSubview(riveView)
// SwiftUI
var body: some View {
// 创建 Rive 对象后,你可以初始化视图。
RiveUIViewRepresentable(rive)
}
// SwiftUI (异步)
var body: some View {
// 创建 Rive 对象后,你可以初始化视图。
AsyncRiveUIViewRepresentable {
let worker = try await Worker(
configuration: .init(enableGPUCanvas: true)
)
let file = try await File(source: .local("my_file", Bundle.main), worker: worker)
return try await Rive(file: file)
}
}

上述示例使用文件的默认画板和状态机,并在可用时将编辑器中创作的主视图模型和全局视图模型默认实例绑定到状态机。这是在不指定画板或状态机的情况下初始化 Rive 对象时的默认行为。

或者,你也可以指定要使用的画板和状态机。有关手动选择画板的文档和示例,请参见画板文档。有关手动选择状态机的文档和示例,请参见状态机文档。

数据绑定​

数据绑定是一项功能,允许你从代码动态更新 Rive 图形,包括字符串、数字、布尔值等。

Rive(file:) 会创建状态机,并在可用时绑定编辑器中创作的主视图模型和全局默认实例。若需要访问属性,请自行创建并保留这些实例:

let artboard = try await file.createArtboard()
let viewModelInstance = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
let stateMachine = try await artboard.createStateMachine(binding: viewModelInstance)
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)

将 viewModelInstance 保存在应用的模型中。状态机没有实例 getter,使用这些初始化器时,已弃用的 rive.viewModelInstance getter 会返回 nil。共享绑定见全局视图模型;在应用中创建并保留实例的做法见设置实例。

设置数据绑定后,你可以在运行时更新和监听数据绑定属性。

// 设置值
// 使用设置阶段保留的 viewModelInstance。
let stringProperty = StringProperty(path: "path/to/string")
viewModelInstance.setValue(of: stringProperty, to: "Hello, Rive")
// 获取值
// 使用设置阶段保留的 viewModelInstance。
let stringProperty = StringProperty(path: "path/to/string")

// 获取属性的当前值
let value = try await viewModelInstance.value(of: stringProperty)

// 获取属性的值流,当属性发生变化时发出新值。
let valueStream = viewModelInstance.valueStream(of: stringProperty)
do {
for try await value in valueStream {
print(value)
}
} catch let error as ViewModelInstanceError {
print(error)
} catch {
print(error)
}

示例​

有关基本用法,请参见示例应用中的 Marty 示例。

有关更完整的示例,请参见示例应用中的 Quick Start 示例,它演示了如何使用数据绑定。

有关如何暂停和恢复动画以及设置帧率的示例,请参见示例应用中的 Player 示例。

旧版运行时​

安装运行时​

Swift Package Manager

要通过 Swift Package Manager 安装,在 Xcode 的包查找器中搜索 rive-ios 或完整的 GitHub 路径:https://github.com/rive-app/rive-ios

CocoaPods

将以下内容添加到你的 Podspec 文件:

pod 'RiveRuntime'

导入 Rive​

在使用 Rive 运行时的文件顶部添加以下内容:

import RiveRuntime

使用方式​

你将使用的主要对象是 RiveViewModel。它负责创建 Rive 资源并与之交互。

SwiftUI​
// 本地文件
struct AnimationView: View {
var body: some View {
RiveViewModel(fileName: "cool_rive_animation").view()
}
}
// 远程 URL
struct AnimationView: View {
var body: some View {
RiveViewModel(
webURL: "https://cdn.rive.app/animations/off_road_car_v7.riv"
).view()
}
}
UIKit​

你可以通过代码将 Rive 添加到视图控制器,方法是创建 RiveViewModel,让它创建一个新的 RiveView,然后将其添加到视图层次结构中。

或者,你也可以使用 Storyboard 将 Rive 添加到控制器,方法是创建 RiveViewModel,并将其视图设置为在 Storyboard 中创建的 RiveView。

// 编程方式
class AnimationViewController: UIViewController {
var simpleVM = RiveViewModel(fileName: "cool_rive_animation")

override func viewWillAppear(_ animated: Bool) {
let riveView = simpleVM.createRiveView()
view.addSubview(riveView)
riveView.frame = view.bounds
}
}
// Storyboard
class AnimationViewController: UIViewController {
@IBOutlet weak var riveView: RiveView!
var simpleVM = RiveViewModel(fileName: "cool_rive_animation")

override public func viewDidLoad() {
simpleVM.setView(riveView)
}
}

播放 / 暂停​

当前运行时​

// UIKit
let rive = try await Rive(...)
let riveView = RiveUIView(rive: rive)
riveView.isPaused = true // 或 false 恢复
// SwiftUI
@State var isPaused = false
var body: some View {
RiveUIViewRepresentable(rive)
.paused(isPaused)
}
// SwiftUI (异步)
@State var isPaused = false
var body: some View {
AsyncRiveUIViewRepresentable {
let worker = try await Worker()
let file = try await File(source: ..., worker: worker)
let rive = try await Rive(file: file)
return rive
}
.paused(isPaused)
}

旧版运行时​

let viewModel = RiveViewModel(fileName: "...")
viewModel.pause() // 或 play() 恢复

帧率​

当前运行时​

// UIKit
let rive = try await Rive(...)
let riveView = RiveUIView(rive: rive)
// 设置固定帧率
riveView.frameRate = .fps(30)
// 设置帧率范围(注意:在 iOS < 15 / macOS < 14 上,这与使用 .fps 相同)
riveView.frameRate = .range(minimum: 30, maximum: 60)
// 恢复默认帧率
riveView.frameRate = .default
// SwiftUI
var body: some View {
RiveUIViewRepresentable(rive)
.frameRate(.fps(30))
// 或
.frameRate(.range(minimum: 30, maximum: 60))
// 或
.frameRate(.default)
}
// SwiftUI (异步)
var body: some View {
AsyncRiveUIViewRepresentable {
let worker = try await Worker()
let file = try await File(source: ..., worker: worker)
let rive = try await Rive(file: file)
return rive
}
.frameRate(.fps(30))
// 或
.frameRate(.range(minimum: 30, maximum: 60))
// 或
.frameRate(.default)
}

旧版运行时​

let viewModel = RiveViewModel(fileName: "...")
viewModel.setPreferredFramesPerSecond(preferredFramesPerSecond: 30)
// 或者,在 iOS >= 15 / macOS >= 14 上
viewModel.setPreferredFrameRateRange(preferredFrameRateRange: CAFrameRateRange(minimum: 30, maximum: 60))

语义化​

Rive 视图可以向 VoiceOver 暴露在编辑器中定义的语义化信息。语义化功能仅在当前 Apple Runtime 中可用。

请参阅 Semantics 页面了解可用模式和更多细节。

线程​

当前运行时​

新的运行时通过引入 Worker 对象支持多线程。

// 在 async 上下文中
let worker = try await Worker()
let file = try await File(source: ..., worker: worker)

Worker 对象负责为 Rive 实例创建和管理后台线程。

每个 worker 共享全局带外资源,如图像、字体和音频。使用 File.getAssets() 查询文件,并以资源的 uniqueName 在其 worker 上注册替换。该替换会应用到使用该 worker 的文件中的匹配资源,包括已经加载的文件以及之后加载的文件。示例请参见加载资源。

一个 worker 大致相当于一个后台线程。如果你渲染多个重量级 Rive 图形,可以为每个文件创建一个 Worker,让每个文件在各自的后台线程上处理。

Worker 对象的数量受系统可用性限制;具体来说,Worker 由 DispatchQueue 支持,它处理线程的创建和重用。

需要注意的是,虽然支持多线程,但 Rive 对象的 API 调用仍必须在主 actor 上进行。这通过将函数和类型标记为 @MainActor 在编译时强制执行。

共享 Worker​

要在多个 File 对象之间共享带外资源或引用资源,请使用共享的 Worker。以下是一个示例实现。

下面的 AsyncShared 以及 Worker 扩展属于应用层的辅助代码,并不包含在 RiveRuntime 中。

📌 AsyncShared.swift

/// 懒加载并缓存一个值。初始化失败后可以重试。
@MainActor
final class AsyncShared<Value> {
private let makeValue: @MainActor () async throws -> Value
private var cachedValue: Value?
private var loadingTask: Task<Value, Error>?

init(_ makeValue: @escaping @MainActor () async throws -> Value) {
self.makeValue = makeValue
}

func value() async throws -> Value {
if let cachedValue {
return cachedValue
}

let task = loadingTask ?? Task {
defer { loadingTask = nil }
let value = try await makeValue()
cachedValue = value
return value
}
loadingTask = task

return try await task.value
}
}

📌 AsyncShared+Worker.swift

import RiveRuntime

extension AsyncShared where Value == Worker {
convenience init(configuration: Worker.Configuration = .init()) {
self.init {
try await Worker(configuration: configuration)
}
}
}

extension Worker {
@MainActor
private static let sharedWorker = AsyncShared<Worker>()

@MainActor
private static let sharedGPUCanvasWorker = AsyncShared<Worker>(
configuration: .init(enableGPUCanvas: true)
)

@MainActor
static func shared() async throws -> Worker {
try await sharedWorker.value()
}

@MainActor
static func sharedGPUCanvas() async throws -> Worker {
try await sharedGPUCanvasWorker.value()
}
}

然后,在创建 Rive 对象时,使用 Worker.shared() 获取共享 worker。

let worker = try await Worker.shared()
let file = try await File(source: ..., worker: worker)
let rive = try await Rive(file: file)

旧版运行时​

旧版运行时目前在主线程上是单线程的。这意味着所有 Rive 调用必须在主线程上进行。建议如果你在后台线程上,应在进行任何 Rive 调用之前切换到主队列。

日志​

当前运行时​

新的运行时尚未包含日志功能,但将在不久的将来添加。

旧版运行时​

启用日志只需将 RiveLogger.isEnabled 设置为 true。

RiveLogger.isEnabled = true

有关日志级别、类别和详细日志的更多详细信息,请参见日志页面。

请参阅后续运行时页面,了解如何控制动画播放、状态机等。

示例应用​

你可以从 Rive GitHub 仓库运行我们的 Apple 示例应用。

git clone https://github.com/rive-app/rive-ios

在 Xcode 中打开 Example-iOS 应用,并确保选择 Preview (iOS) 或 Preview (macOS) scheme。其他 scheme 用于开发目的,需要额外配置,参见 CONTRIBUTING.MD。

Image

资源​

GitHub:https://github.com/rive-app/rive-ios

示例:

看完还有疑问?进群交流下!
与众多 Rive 创作者、开发者一起交流探讨与答疑解惑。
加入交流群