跳到主要内容

从旧版 Rive Apple 运行时迁移

概述

新的 Rive Apple 运行时是对公共 API 和内部架构的近乎完全重写。虽然从概念上讲大多数操作都有等效项,但两个 API 是不兼容的。任何你想要迁移的旧版 API 中的现有工作都必须使用新 API 重建。

本指南涵盖:

  1. 共享功能 - 常见操作及其等效项。
  2. 新的独占功能 - 仅在新运行时中可用的功能。
  3. 旧版独占功能 - 在新运行时中不再可用的功能及迁移指导。

本指南并非详尽无遗,因为那样会与现有通用文档重复。有关特定主题的更多详细信息,请参阅文档的相关章节。

包和导入

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

import RiveRuntime

异步 API

新的运行时围绕 Swift Concurrency 构建。大多数设置和查询操作都是异步的,应在 async 上下文中调用。

有关更多信息,请参见 Apple 快速开始指南,了解端到端的设置示例。

常见示例包括:

  • 异步创建 Worker
  • 创建 FileRive 对象
  • 创建画板、状态机和视图模型实例
let worker = try await Worker()
let file = try await File(source: .local("my_file", Bundle.main), worker: worker)
let rive = try await Rive(file: file)

生命周期和线程

旧版运行时实际上通过 RiveViewModelRiveView 在主线程上驱动。

新的运行时引入了 Worker 对象用于后台处理,同时仍然在主 actor 上强制执行 API 调用。在实践中:

  • Worker 拥有后台处理上下文
  • File 强引用其 Worker
  • 在 worker 上注册的带外资源可以在通过该 worker 创建的文件之间共享

有关更多信息,请参见线程部分获取其他详细信息。

对于大多数应用,推荐使用共享 worker:

actor WorkerProvider {
static let shared = WorkerProvider()

@MainActor
private var cachedWorker: Worker?

@MainActor
func worker() async throws -> Worker {
if let cachedWorker {
return cachedWorker
}

let worker = try await Worker()
cachedWorker = worker
return worker
}
}

共享功能

共享 API

主要的共享 API 领域是通过 RiveFont.fallbackFontsCallback 实现的回退字体。 有关回退字体类型和行为,请参见回退字体

从 RiveViewModel 到 Rive

使用以下部分作为迁移映射:从磁盘加载文件创建 Rive 视图数据绑定。旧版 RiveViewModel 在这些领域的流程变为显式的 File + Rive 设置,然后是新运行时中的 RiveUIView/SwiftUI 表示类型。

从磁盘加载文件

旧版运行时

// 缓存此文件以便重用
let file = try RiveFile(name: "my_rive_file")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model)

当前运行时

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let rive = try await Rive(file: file)

从 URL 加载文件

旧版运行时

let webURL = URL(string: "https://example.com/my_rive_file.riv")!
let file = RiveFile(httpUrl: webURL, loadCdn: false, with: self)
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model)

当前运行时

let worker = try await WorkerProvider.shared.worker()
let webURL = URL(string: "https://example.com/my_rive_file.riv")!
let file = try await File(
source: .url(webURL),
worker: worker
)
let rive = try await Rive(file: file)

从数据(字节)加载文件

旧版运行时

let data: Data = ...
let file = try RiveFile(data: data, loadCdn: false)
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model)

当前运行时

当你的 .riv 字节已在内存中可用时,使用 .data 文件源。

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

跟踪加载和错误状态

对于常见的设置操作(加载文件、创建画板、创建状态机),迁移模式是:

  • 旧版运行时:许多查找 API 返回 optional(失败时返回 nil),因此使用 guard let / 回退逻辑处理。
  • 新运行时:API 抛出错误,因此使用 do/catch

旧版运行时

旧版 API 通常在查找/创建类型的操作中返回 nil

let file = try RiveFile(name: "my_rive_file")

guard let artboard = file.artboard() else {
// 处理缺少画板的情况
return
}

guard let stateMachine = artboard.defaultStateMachine() else {
// 处理缺少状态机的情况
return
}

当前运行时

新的运行时 API 抛出错误,因此失败处理转移到 do/catch

do {
let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let artboard = try await file.createArtboard("My Artboard")
let stateMachine = try await artboard.createStateMachine("My State Machine")
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)
} catch {
// 处理文件/画板/状态机设置错误
}

此模式同样适用于 UIKit 和 SwiftUI。关键的迁移变化是旧版中的可选值解包 vs 新运行时中的错误捕获。

选择画板和状态机

有关更多信息,请参见画板文档获取更多详细信息。

有关更多信息,请参见状态机文档获取更多详细信息。

旧版运行时

let file = try RiveFile(name: "my_rive_file")
let model = RiveModel(riveFile: file)
model.setArtboard("My Artboard")
model.setStateMachine("My State Machine")

当前运行时

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let artboard = try await file.createArtboard("My Artboard")
let stateMachine = try await artboard.createStateMachine("My State Machine")
let rive = try await Rive(file: file, artboard: artboard, stateMachine: stateMachine)

创建 Rive 视图

旧版运行时

let viewModel = RiveViewModel(...)

// UIKit
let riveView = viewModel.createRiveView()

// SwiftUI
var body: some View {
viewModel.view()
}

当前运行时

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

// UIKit(同步对象已可用)
let riveView = RiveUIView(rive: rive)

// UIKit(异步加载)
let riveView = RiveUIView({
let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: ..., worker: worker)
return try await Rive(file: file)
})

// SwiftUI
var body: some View {
RiveUIViewRepresentable(rive)
}

// SwiftUI(异步)
var body: some View {
AsyncRiveUIViewRepresentable {
let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: ..., worker: worker)
return try await Rive(file: file)
}
}

设置适配和对齐

有关更多信息,请参见布局文档获取所有适配和对齐选项。

旧版运行时

let viewModel = RiveViewModel(
fileName: "my_rive_file",
fit: .contain,
alignment: .center
)

// 在运行时更新
viewModel.fit = .fitWidth
viewModel.alignment = .topCenter

在旧版运行时中使用画板布局尺寸:

let viewModel = RiveViewModel(fileName: "my_rive_file")
viewModel.fit = .layout
viewModel.layoutScaleFactor = RiveViewModel.layoutScaleFactorAutomatic // 默认行为
// 或显式设置缩放因子
viewModel.layoutScaleFactor = 2.0

当前运行时

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
var rive = try await Rive(file: file, fit: .contain(alignment: .center))

// 在运行时更新
rive.fit = .fitWidth(alignment: .topCenter)

在新运行时中使用画板布局尺寸:

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
var rive = try await Rive(file: file, fit: .layout(scaleFactor: .automatic))

// 或显式设置缩放因子
rive.fit = .layout(scaleFactor: .explicit(2.0))

默认布局缩放因子

使用布局适配时,两个运行时都支持自动和显式缩放因子。

旧版运行时

使用 RiveViewModel.layoutScaleFactorAutomatic(默认)或在 layoutScaleFactor 上设置显式数值。

当前运行时

Rive.fit 上使用 .layout(scaleFactor: .automatic).layout(scaleFactor: .explicit(...))

视图模型

有关更多信息,请参见数据绑定文档获取完整的视图模型实例 API。

旧版运行时

在旧版中,视图模型从 RiveFile 查询,然后从该查询的视图模型创建实例。

let riveViewModel = RiveViewModel(...)
let file = riveViewModel.riveModel!.riveFile

guard let viewModel = file.viewModelNamed("My View Model") else {
return
}

// 从查询的视图模型创建空白/默认/命名实例
let blankInstance = viewModel.createInstance()
let defaultInstance = viewModel.createDefaultInstance()
let namedInstance = viewModel.createInstance(fromName: "My Instance")

当前运行时

在新运行时中,视图模型表示为直接传递给 createViewModelInstance 的源元数据。

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)

// 通过显式视图模型名称
let blankInstance = try await file.createViewModelInstance(
from: .blank(from: .name("My View Model"))
)
let defaultInstance = try await file.createViewModelInstance(
from: .name("My View Model")
)
let namedInstance = try await file.createViewModelInstance(
from: .name("My Instance", from: .name("My View Model"))
)

你也可以从画板默认值获取视图模型:

let artboard = try await file.createArtboard()
let defaultFromArtboard = try await file.createViewModelInstance(
from: .viewModelDefault(from: .artboardDefault(artboard))
)

视图模型实例属性

旧版运行时

旧版数据绑定实例暴露你从实例查询的类型化属性对象。

let riveViewModel = RiveViewModel(...)
var instance: RiveDataBindingViewModel.Instance!

riveViewModel.riveModel?.enableAutoBind { boundInstance in
instance = boundInstance
}

guard let stringProperty = instance.stringProperty(fromPath: "path/to/string") else {
return
}

// 设置
stringProperty.value = "Hello, Rive"
// 获取
let currentValue = stringProperty.value

当前运行时

新的运行时使用类型化路径描述符,配合 ViewModelInstance 上的方法进行设置/获取/观察。

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let viewModelInstance = try await file.createViewModelInstance(
from: .name("My View Model")
)

let stringProperty = StringProperty(path: "path/to/string")

// 设置
viewModelInstance.setValue(of: stringProperty, to: "Hello, Rive")

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

// 随时间观察变化
let valueStream = viewModelInstance.valueStream(of: stringProperty)
for try await updatedValue in valueStream {
print(updatedValue)
}

可绑定画板

旧版运行时

旧版画板属性绑定使用可绑定画板包装器类型。

let instance: RiveDataBindingViewModel.Instance = ...
guard let artboardProperty = instance.artboardProperty(fromPath: "path/to/artboard") else {
return
}

let components = try RiveFile(name: "component_library")
guard let bindableArtboard = components.bindableArtboard(withName: "My Artboard") else {
return
}

artboardProperty.setValue(bindableArtboard)

当前运行时

新的运行时通过类型化属性描述符和实例上的 setValue 绑定画板。

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)
let viewModelInstance = try await file.createViewModelInstance(.viewModelDefault(from: .name("My View Model")))

let artboardProperty = ArtboardProperty(path: "path/to/artboard")
let artboard = try await file.createArtboard("My Artboard")
viewModelInstance.setValue(of: artboardProperty, to: artboard)

数据绑定

有关更多信息,请参见数据绑定文档获取完整 API 覆盖。

旧版运行时

let file = try RiveFile(...)
let model = RiveModel(riveFile: file)
model.enableAutoBind { instance in
self.viewModelInstance = instance
instance.stringProperty(fromPath: "...").value = "Hello, Rive"
}

你也可以手动绑定特定实例:

let file = try RiveFile(...)
guard let artboard = file.artboard() else { return }
guard let stateMachine = artboard.defaultStateMachine() else { return }
guard let viewModel = file.defaultViewModel(for: artboard) else { return }
guard let instance = viewModel.createDefaultInstance() else { return }
stateMachine.bindViewModelInstance(instance) // 绑定成功

当前运行时

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: ..., worker: worker)

// 自动绑定(默认)
let autoBoundRive = try await Rive(file: file, dataBind: .auto)

// 绑定特定实例
let artboard = try await file.createArtboard()
let viewModel = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
let instanceBoundRive = try await Rive(file: file, dataBind: .instance(viewModel))

// 退出数据绑定
let noBindingRive = try await Rive(file: file, dataBind: .none)

更新数据绑定会取消状态机的稳定状态

旧版和新的运行时行为在此有所不同:

  • 旧版:更改数据绑定属性后,如果图形已稳定,通常需要调用 play() 来推进/取消稳定并应用更改。
  • 新运行时:设置数据绑定属性不再需要显式的 play() 调用,简化了 API。

迁移的最佳实践:如果你需要初始值,请在创建和呈现视图之前设置它们。

这避免了在应用代码提供的值被应用之前显示默认值一帧。

旧版运行时

在创建视图后更新绑定值时,保留对实例的引用并在变更后调用 play()

let viewModel = RiveViewModel(...)
var instance: RiveDataBindingViewModel.Instance?

viewModel.riveModel?.enableAutoBind { boundInstance in
instance = boundInstance
}

let riveView = viewModel.createRiveView()

instance?.stringProperty(fromPath: "path/to/string")?.value = "Updated Value"
viewModel.play() // 变更后需要推进/取消稳定

当前运行时

在新运行时中,在创建视图后更新绑定值。不需要显式调用 play()

let worker = try await WorkerProvider.shared.worker()
let file = try await File(source: .local("my_rive_file", Bundle.main), worker: worker)

let instance = try await file.createViewModelInstance(.viewModelDefault(from: .name("My View Model")))
let property = StringProperty(path: "path/to/string")
let rive = try await Rive(file: file, dataBind: .instance(instance))
let riveView = RiveUIView(rive: rive)

instance.setValue(of: property, to: "Updated Value") // 无需 play() 即可应用

播放和暂停

有关更多信息,请参见 Apple 运行时指南中的播放控制。

旧版运行时

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

当前运行时

let rive = try await Rive(...)
let riveView = RiveUIView(rive: rive)
riveView.isPaused = true // 或 false 恢复

// SwiftUI
RiveUIViewRepresentable(rive)
.paused(true)

帧率

有关更多信息,请参见帧率控制和 ProMotion 注意事项。

旧版运行时

let viewModel = RiveViewModel(fileName: "...")
viewModel.setPreferredFramesPerSecond(preferredFramesPerSecond: 30)

当前运行时

let rive = try await Rive(...)
let riveView = RiveUIView(rive: rive)
riveView.frameRate = .fps(30)
// 或
riveView.frameRate = .range(minimum: 30, maximum: 60)
// 或
riveView.frameRate = .default

加载引用资源

旧版运行时

旧版运行时使用拉取模型,通过一个按需解析资源的回调。

let viewModel = RiveViewModel(fileName: "my_rive_file") { asset, _, factory in
if let imageAsset = asset as? RiveImageAsset {
let decodedImage = factory.decodeImage(Data(...))
imageAsset.renderImage(decodedImage)
return true
} else if let fontAsset = asset as? RiveFontAsset {
let decodedFont = factory.decodeFont(Data(...))
fontAsset.font(decodedFont)
return true
} else if let audioAsset = asset as? RiveAudioAsset {
let decodedAudio = factory.decodeAudio(Data(...))
audioAsset.audio(decodedAudio)
return true
} else {
return false
}
}

当前运行时

新的运行时使用推送模型,资源在使用之前在 worker 上注册。

let worker = try await WorkerProvider.shared.worker()
let image = try await worker.decodeImage(from: Data(...))
worker.addGlobalImageAsset(image, name: "MyImage-1234")

let font = try await worker.decodeFont(from: Data(...))
worker.addGlobalFontAsset(font, name: "MyFont-1234")

let audio = try await worker.decodeAudio(from: Data(...))
worker.addGlobalAudioAsset(audio, name: "MyAudio-1234")

日志

有关更多信息,请参见日志指南获取共享概念和过滤选项。

旧版运行时

RiveLogger.isEnabled = true
RiveLogger.levels = [.debug, .error]
RiveLogger.categories = [.stateMachine, .artboard, .viewModel]
RiveLogger.isVerbose = true

当前运行时

RiveLog.logger = RiveLog.system(levels: .default)

// 或使用你自己的 RiveLog.Logger 实现
RiveLog.logger = MyLogger()

// 禁用日志
RiveLog.logger = RiveLog.none

回退字体

两个运行时都支持回退字体。

  • RiveFont.fallbackFontsCallback 仍然可用
  • 现有的回退字体策略可以在两个运行时之间重用
RiveFont.fallbackFontsCallback = { style in
switch style.weight {
case .thin:
return [RiveFallbackFontDescriptor(weight: .thin)]
case .bold:
return [RiveFallbackFontDescriptor(weight: .bold)]
default:
return [RiveFallbackFontDescriptor()]
}
}

新的独占功能

基于 Worker 的并发

新的运行时引入 Worker 作为显式的并发原语。这与不暴露等效概念的旧版运行时相比是一个实质性的变化。

优势包括:

  • 更好地控制后台处理
  • 每个 worker 共享全局资源注册
  • 渲染多个文件时架构更可预测

共享 worker 通常是最好的默认选择。仅当需要额外的并行处理时(例如渲染多个重量级图形时),才使用多个 worker。

异步初始化 API

新的运行时支持异步构造函数和异步视图包装器(RiveUIView 带异步闭包和 AsyncRiveUIViewRepresentable),以更好地建模现实世界的加载流程。

当视图必须拥有自己的加载生命周期时,异步包装器很有用。如果你需要在屏幕之间重用和缓存,建议在更高级别创建和存储 File/Rive 对象。

旧版独占功能

旧版运行时中的某些功能有意不在新运行时中出现。

CDN 资源

旧版文件加载可能使用 CDN 支持的资源流程(例如通过 loadCdn 行为)。新运行时的资源模型是基于 worker 和推送的,通过显式的资源注册 API。

状态机输入

旧版运行时直接支持状态机输入 API。新运行时未暴露等效的输入 API,迁移应转向数据绑定属性(数字、布尔值、字符串、触发器类型的交互)。

Rive 编辑器在菜单 > 将输入转换为视图模型中提供了一个转换工具,可以帮助初始迁移。

目前没有计划在新运行时中重新引入直接的状态机输入 API。

事件

旧版运行时支持事件监听器。新运行时目前不暴露等效的事件监听器 API。对于许多迁移场景,数据绑定合约是应用-运行时通信的推荐替代方案。

简单的类事件行为通常可以通过面向触发器的视图模型属性建模。

目前没有计划在新运行时中重新引入旧版风格的事件监听器 API。

线性动画

旧版集成可以直接针对线性动画。在迁移中,图形需要包含状态机。

对于依赖线性动画的现有文件,在编辑器中创建一个包含单个状态的状态机,该状态播放所需的动画。

观察状态机状态

旧版集成通常使用状态更改代理回调(例如 RiveStateDelegate.stateChange(...))来响应动画状态。

新运行时未在本指南中暴露直接的状态名称观察 API。对于迁移,将这些面向应用的信号建模为数据绑定属性,并从绑定实例观察它们。

let property = StringProperty(path: "path/to/state_signal")
let stream = viewModelInstance.valueStream(of: property)
for try await value in stream {
// 响应从 Rive 文件发出的类似状态的变化
}

按索引获取

在可能的情况下,优先使用命名查询和显式源(例如 ViewModelSource 和命名画板/状态机查询),而不是基于索引的耦合。