加载资源
概览
Rive 资源(如图像、字体和音频)可以通过多种方式加载。本指南介绍如何在运行时动态加载和替换资源。
资源加载方法
Rive 支持三种类型的资源:
- 嵌入式资源:直接编码在
.riv文件中 - 引用资源:在编辑器中标记为"引用",需要在运行时由应用提供
- CDN 资源:托管在 Rive CDN 上,由运行时自动加载
嵌入式资源
嵌入式资源直接包含在 Rive 文件中,无需额外处理即可使用。
CDN 图像
CDN 托管的资源可以自动加载,也可以通过自定义逻辑处理。
引用资源
引用资源在编辑器中标记为单独加载,需要应用在运行时提供实际的资源数据。
处理资源
当前运行时(推荐)
本节假设你已阅读了 Apple 概述。
资源 API 覆盖范围
使用拥有这些文件或使用它们的视图模型实例的同一个 Worker 来解码资源。
| 资源 | 解码输入 | 全局注册 | 数据绑定 |
|---|---|---|---|
| Image | decodeImage(from: Data) | addGlobalImageAsset(_:name:) | ImageProperty |
| Font | decodeFont(from: Data)、UIFont 或 NSFont | addGlobalFontAsset(_:name:) | FontProperty |
| Audio | decodeAudio(from: Data) | addGlobalAudioAsset(_:name:) | 无 Audio 属性 API |
| Blob | decodeBlob(from: Data) | 不支持 | BlobProperty |
全局注册会按 uniqueName 替换该 worker 上各文件中的匹配资源。数据绑定将资源赋给特定的视图模型属性,不需要全局注册。
Blobs 支持通过
BlobProperty解码和赋值,但不支持全局注册或移除。下面的示例会跳过.blob资源;请使用数据绑定来赋已解码的 blob。
发现并加载文件资源
使用 File.getAssets() 发现文件中的资源,然后在其 Worker 上解码并注册替换。这取代了旧版用于带外资源的 customLoader 回调。
全局资源会应用到该文件以及由同一 worker 加载的其他文件中的匹配资源,包括之后加载的文件。请使用 asset.uniqueName 进行注册。你可以在创建文件之后再注册资源,也可以用相同的唯一名称注册新资源来替换它。
每个 File.Asset 提供以下元数据:
| 属性 | 说明 |
|---|---|
name | 存储在文件中的作者命名资源名称。 |
uniqueName | 传给 worker 全局资源 API 的精确名称。 |
assetID | 存储在文件中的资源标识符。 |
type | .image、.font、.audio、.blob 或 .unknown(UInt16)。 |
fileExtension | 不含前导点的文件扩展名。 |
cdn | 可选的托管资源元数据,包含 baseURL 和 uuid。 |
getAssets() 包含嵌入式资源和引用资源。使用这些元数据来选择由应用提供哪些资源。
-
创建 Worker 和 File
在查询资源元数据之前先加载文件。请从主 actor 的 async 上下文中调用这些 API。
let worker = try await Worker()
let file = try await File(
source: .local("my_rive_file", .main),
worker: worker
) -
选择并加载资源
遍历文件的资源,并使用其元数据定位对应字节。此示例用每个资源的
uniqueName和fileExtension匹配打包资源,跳过未知资源类型以及未打包的资源。for asset in try await file.getAssets() {
if case .unknown = asset.type { continue }
guard let url = Bundle.main.url(
forResource: asset.uniqueName,
withExtension: asset.fileExtension
) else { continue }
let data = try await Task.detached {
try Data(contentsOf: url)
}.value
// 在此解码并注册资源,如下所示。
} -
注册全局资源
在循环内,用 worker 解码每个资源,并使用
asset.uniqueName注册。worker 会将其应用到使用该唯一名称的文件。switch asset.type {
case .image:
let image = try await worker.decodeImage(from: data)
worker.addGlobalImageAsset(image, name: asset.uniqueName)
case .font:
let font = try await worker.decodeFont(from: data)
worker.addGlobalFontAsset(font, name: asset.uniqueName)
case .audio:
let audio = try await worker.decodeAudio(from: data)
worker.addGlobalAudioAsset(audio, name: asset.uniqueName)
case .blob, .unknown:
break
}
完整示例
此示例加载文件名与每个资源的 uniqueName 加上 fileExtension 匹配的打包资源(例如 picture-47982.jpeg)。没有匹配打包资源的资源会被跳过并保持不变。
@MainActor
func loadFileWithAssets() async throws -> File {
let worker = try await Worker()
let file = try await File(
source: .local("my_rive_file", .main),
worker: worker
)
for asset in try await file.getAssets() {
if case .unknown = asset.type { continue }
guard let url = Bundle.main.url(
forResource: asset.uniqueName,
withExtension: asset.fileExtension
) else { continue }
let data = try await Task.detached {
try Data(contentsOf: url)
}.value
switch asset.type {
case .image:
let image = try await worker.decodeImage(from: data)
worker.addGlobalImageAsset(image, name: asset.uniqueName)
case .font:
let font = try await worker.decodeFont(from: data)
worker.addGlobalFontAsset(font, name: asset.uniqueName)
case .audio:
let audio = try await worker.decodeAudio(from: data)
worker.addGlobalAudioAsset(audio, name: asset.uniqueName)
case .blob, .unknown:
break
}
}
return file
}
从返回的文件创建你的 Rive 和视图。你也可以在显示文件之后再注册替换。使用相同的唯一名称会更新该 worker 上各文件中的匹配资源;当这些文件需要不同的替换时,请使用独立的 worker。
加载托管资源
新运行时不会自动获取托管资源。对于带有 cdn 元数据的资源,从其 baseURL 获取字节,并将 uuid 作为路径组件追加,然后使用相同的全局资源 API 解码并注册。
例如,在资源循环内,可按如下方式加载托管图像:
guard asset.type == .image,
let cdn = asset.cdn,
let baseURL = URL(string: cdn.baseURL)
else { continue }
let url = baseURL.appendingPathComponent(cdn.uuid)
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode)
else { throw URLError(.badServerResponse) }
let image = try await worker.decodeImage(from: data)
worker.addGlobalImageAsset(image, name: asset.uniqueName)
解码原生字体
除字体文件字节外,decodeFont(from:) 还接受平台字体:
#if canImport(UIKit)
let font = try await worker.decodeFont(from: UIFont.systemFont(ofSize: 12))
#elseif canImport(AppKit)
let font = try await worker.decodeFont(from: NSFont.systemFont(ofSize: 12))
#endif
worker.addGlobalFontAsset(font, name: fontAsset.uniqueName)
请为你所面向的平台导入 UIKit 或 AppKit。显示字号由 Rive 文本 run 控制。解码后的字体也可以赋给 FontProperty。
管理全局资源
worker 会保留已注册的资源,因此在注册期间你无需再单独保持强引用。要移除注册,请使用对应 API 并传入相同的唯一名称:
worker.removeGlobalImageAsset(name: imageAsset.uniqueName)
worker.removeGlobalFontAsset(fontAsset.uniqueName)
worker.removeGlobalAudioAsset(name: audioAsset.uniqueName)
旧版运行时
示例
使用资源处理器 API
实例化 RiveViewModel(或直接使用 RiveFile)时,将 customLoader 回调属性添加到参数列表中。此回调将在运行时从 .riv 文件加载时检测到的每个资源被调用,回调将负责处理运行时资源的加载,或者传递责任让运行时尝试加载。
你可能需要处理资源加载的一个场景是,如果文件中的资源标记为引用,你需要提供实际的资源来渲染图形,因为 Rive 没有将其嵌入 .riv 中,因此无法加载。
你可能希望让运行时尝试加载资源的一个场景是,如果文件中的资源标记为托管,你希望将加载责任传递给运行时(运行时将调用 Rive CDN 进行加载)。
RiveViewModel(fileName: "simple_assets", loadCdn: false, customLoader: { (asset: RiveFileAsset, data: Data, factory: RiveFactory) -> Bool in
// 对只有一个资源的 Rive 文件进行简单检查
if (asset is RiveImageAsset){
// picture-47982.jpeg 可以从 Rive 编辑器与 .riv 文件一起导出。
// 然后将其包含在项目的主 bundle 资源中
guard let url = (.main as Bundle).url(forResource: "picture-47982", withExtension: "jpeg") else {
fatalError("Failed to locate 'picture-47982' in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
}
(asset as! RiveImageAsset).renderImage(
factory.decodeImage(data)
)
return true;
}
return false;
}).view()
你提供的回调将收到 asset、data 和 factory。
-
asset- 对RiveFileAsset对象的引用。你将使用此引用为动态加载的内容设置新的 Rive 特定资源。如果你希望在视图的生命周期内动态替换给定的图像/字体,你可能需要缓存此对象。你可以从此对象获取多个属性,例如:name()- 不带唯一文件标识符的资源名称(即picture.webp而非picture-47982.webp)uniqueFilename()- 带唯一文件标识符的资源名称(即picture-47982.webp而非picture.webp)fileExtension()- 文件扩展名(即"png")cdnBaseUrl()- CDN 的基础 URL 名称cdnUuid()- Rive CDN 中资源的标识符。可用于查看是否有长度,以确定资源是否标记为从 Rive CDN 获取(在这种情况下,你可以让 Rive 运行时检索资源,而不是你的应用逻辑)
-
data- 资源的字节数组。这有助于确定资源是否已嵌入 Rive 文件中(即在编辑器中未标记为"引用") -
factory- 具有将资源字节转换为RiveRenderImage、RiveFont或RiveAudio方法的工具,asset对象通过.renderImage(your-rive-render-image)、.font(your-rive-font)或.audio(your-rive-audio)使用这些方法进行渲染。这些资源通过调用factory.decodeImage(data)、factory.decodeFont(data)或factory.decodeAudio(data)来创建
重要提示:请注意回调的返回值是 boolean,你需要返回:
true如果你打算自己处理和加载资源,或false如果你不想自己处理该资源的加载,并尝试让运行时加载该资源。
示例用法
import SwiftUI
import RiveRuntime
struct SimpleAssetReplacement: View {
@StateObject private var riveInstance = RiveViewModel(fileName: "simple_assets", autoPlay: false, loadCdn: false, customLoader: { (asset: RiveFileAsset, data: Data, factory: RiveFactory) -> Bool in
if (asset is RiveImageAsset) {
guard let url = (.main as Bundle).url(forResource: "picture-47982", withExtension: "jpeg") else {
fatalError("Failed to locate 'picture-47982' in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
}
(asset as! RiveImageAsset).renderImage(
factory.decodeImage(data)
)
return true;
} else if (asset is RiveFontAsset) {
guard let url = (.main as Bundle).url(forResource: "Inter-45562", withExtension: "ttf") else {
fatalError("Failed to locate 'Inter-45562' in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
}
(asset as! RiveFontAsset).font(
factory.decodeFont(data)
)
return true;
}
return false;
})
var body: some View {
riveInstance.view()
}
}
字体
使用自定义加载器时,引用字体可以通过两种方式加载:使用原始数据(来自文件,如上所示),或使用 UIFont / NSFont。
使用 UIFont / NSFont 时,提供的字体的尺寸、粗细和宽度将被忽略。字体将按文本运行中定义的方式使用,而不是被提供的字体的样式覆盖。
import SwiftUI
import RiveRuntime
struct SimpleFontReplacement: View {
@StateObject private var riveInstance = RiveViewModel(fileName: "simple_assets", autoPlay: false, loadCdn: false, customLoader: { (asset: RiveFileAsset, data: Data, factory: RiveFactory) -> Bool in
if (asset is RiveFontAsset) {
(asset as! RiveFontAsset).font(
factory.decodeFont(UIFont.systemFont(ofSize: 12))
)
return true;
}
return false;
})
var body: some View {
riveInstance.view()
}
}
图像
加载引用图像的资源时,你可能需要将本地资源缩放到 Rive 文件中定义的图像资源大小。使用自定义加载器时,你可以通过 RiveImageAsset 的 size 属性访问引用图像的大小。
import SwiftUI
import RiveRuntime
struct SimpleImageSizeReplacement: View {
@StateObject private var riveInstance = RiveViewModel(fileName: "simple_assets", autoPlay: false, loadCdn: false, customLoader: { (asset: RiveFileAsset, data: Data, factory: RiveFactory) -> Bool in
guard let imageAsset = asset as? RiveImageAsset else { return false }
let requestedSize = imageAsset.size
let image = UIImage(...)
let resizedImage = resize(image, to: requestedSize)
guard let pngData = resizedImage.pngData() else { return false }
imageAsset.renderImage(
factory.decodeImage(pngData)
)
return true
}
return false;
}
var body: some View {
riveInstance.view()
}
}