跳到主要内容

加载资源

概览​

Rive 资源(如图像、字体和音频)可以通过多种方式加载。本指南介绍如何在运行时动态加载和替换资源。

资源加载方法​

Rive 支持三种类型的资源:

  • 嵌入式资源:直接编码在 .riv 文件中
  • 引用资源:在编辑器中标记为"引用",需要在运行时由应用提供
  • CDN 资源:托管在 Rive CDN 上,由运行时自动加载

嵌入式资源​

嵌入式资源直接包含在 Rive 文件中,无需额外处理即可使用。

CDN 图像​

CDN 托管的资源可以自动加载,也可以通过自定义逻辑处理。

引用资源​

引用资源在编辑器中标记为单独加载,需要应用在运行时提供实际的资源数据。

处理资源​

当前运行时(推荐)​

本节假设你已阅读了 Apple 概述。

资源 API 覆盖范围​

使用拥有这些文件或使用它们的视图模型实例的同一个 Worker 来解码资源。

资源解码输入全局注册数据绑定
ImagedecodeImage(from: Data)addGlobalImageAsset(_:name:)ImageProperty
FontdecodeFont(from: Data)、UIFont 或 NSFontaddGlobalFontAsset(_:name:)FontProperty
AudiodecodeAudio(from: Data)addGlobalAudioAsset(_:name:)无 Audio 属性 API
BlobdecodeBlob(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() 包含嵌入式资源和引用资源。使用这些元数据来选择由应用提供哪些资源。

  1. 创建 Worker 和 File

    在查询资源元数据之前先加载文件。请从主 actor 的 async 上下文中调用这些 API。

    let worker = try await Worker()
    let file = try await File(
    source: .local("my_rive_file", .main),
    worker: worker
    )
  2. 选择并加载资源

    遍历文件的资源,并使用其元数据定位对应字节。此示例用每个资源的 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

    // 在此解码并注册资源,如下所示。
    }
  3. 注册全局资源

    在循环内,用 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()
}
}

资源​

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