加载资源
概览
Rive 资源(如图像、字体和音频)可以通过多种方式加载。本指南介绍如何在运行时动态加载和替换资源。
资源加载方法
Rive 支持三种类型的资源:
- 嵌入式资源:直接编码在
.riv文件中 - 引用资源:在编辑器中标记为"引用",需要在运行时由应用提供
- CDN 资源:托管在 Rive CDN 上,由运行时自动加载
嵌入式资源
嵌入式资源直接包含在 Rive 文件中,无需额外处理即可使用。
CDN 图像
CDN 托管的资源可以自动加载,也可以通过自定义逻辑处理。
引用资源
引用资源在编辑器中标记为单独加载,需要应用在运行时提供实际的资源数据。
处理资源
当前运行时(推荐)
本节假设你已阅读了 Apple 概述。
发现并加载文件资源
使用 File.getAssets() 发现文件中的资源,然后在其 Worker 上解码并注册替换。这取代了旧版用于带外资源的 customLoader 回调。
全局资源会应用到该文件以及由同一 worker 加载的其他文件中的匹配资源,包括之后加载的文件。请使用 asset.uniqueName 进行注册。你可以在创建文件之后再注册资源,也可以用相同的唯一名称注册新资源来替换它。
每个 File.Asset 提供以下元数据:
| 属性 | 说明 |
|---|---|
name | 存储在文件中的作者命名资源名称。 |
uniqueName | 传给 worker 全局资源 API 的精确名称。 |
assetID | 存储在文件中的资源标识符。 |
type | .image、.font、.audio 或 .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 .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 .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)
管理全局资源
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()
}
}