从旧版 Rive Apple 运行时迁移
概述
新的 Rive Apple 运行时是对公共 API 和内部架构的近乎完全重写。虽然从概念上讲大多数操作都有等效项,但两个 API 是不兼容的。任何你想要迁移的旧版 API 中的现有工作都必须使用新 API 重建。
本指南涵盖:
本指南并非详尽无遗,因为那样会与现有通用文档重复。有关特定主题的更多详细信息,请参阅文档的相关 章节。
包和导入
新的 Apple 运行时与旧版运行时在同一个 Swift 包和 CocoaPods pod 中可用。两个运行时 API 都在同一个包中,因此你可以使用相同的导入语句导入运行时。
import RiveRuntime
异步 API
新的运行时围绕 Swift Concurrency 构建。大多数设置和查询操作都是异步的,应在 async 上下文中调用。
有关更多信息,请参见 Apple 快速开始指南,了解端到端的设置示例。
常见示例包括:
- 异步创建
Worker - 创建
File和Rive对象 - 创建画板、状态机和视图模型实例
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)
生命周期和线程
旧版运行时实际上通过 RiveViewModel 和 RiveView 在主线程上驱动。
新的运行时引入了 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。
线性动画
旧版集成可以直接针对线性动画。在迁移中,图形需要包含状态机。
对于依赖线性动画的现有文件,在编辑器中创建一个包含单个状态的状态机,该状态播放所需的动画。