Skip to content

Commit 4fe9e12

Browse files
committed
feat: add getEnums() using experimental iOS API
Add RiveFile.getEnums() to retrieve enum definitions from Rive files. iOS uses the new experimental API (RiveRuntime 6.15.0 via SPM), Android uses existing File.enums property. Conditional compilation ensures it builds with both SPM and CocoaPods. Set USE_RIVE_SPM=1 with pod install to enable.
1 parent fd22bb3 commit 4fe9e12

25 files changed

+631
-40
lines changed

RNRive.podspec

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ if !rive_ios_version && package['runtimeVersions'] && package['runtimeVersions']
2424
rive_ios_version = package['runtimeVersions']['ios']
2525
end
2626

27-
if !rive_ios_version
27+
use_rive_spm = ENV['USE_RIVE_SPM'] == '1' || (defined?($UseRiveSPM) && $UseRiveSPM)
28+
29+
if !use_rive_spm && !rive_ios_version
2830
raise "Internal Error: Failed to determine Rive iOS SDK version. Please ensure package.json contains 'runtimeVersions.ios'"
2931
end
3032

31-
Pod::UI.puts "@rive-app/react-native: Rive iOS SDK #{rive_ios_version}"
33+
if use_rive_spm
34+
Pod::UI.puts "@rive-app/react-native: Using RiveRuntime via Swift Package Manager"
35+
else
36+
Pod::UI.puts "@rive-app/react-native: Rive iOS SDK #{rive_ios_version}"
37+
end
3238

3339
Pod::Spec.new do |s|
3440
s.name = "RNRive"
@@ -47,7 +53,19 @@ Pod::Spec.new do |s|
4753
load 'nitrogen/generated/ios/RNRive+autolinking.rb'
4854
add_nitrogen_files(s)
4955

50-
s.dependency "RiveRuntime", rive_ios_version
56+
if use_rive_spm
57+
spm_dependency(s,
58+
url: 'https://github.com/rive-app/rive-ios.git',
59+
requirement: {kind: 'upToNextMajorVersion', minimumVersion: '6.15.0'},
60+
products: ['RiveRuntime']
61+
)
62+
else
63+
s.dependency "RiveRuntime", rive_ios_version
64+
end
5165

5266
install_modules_dependencies(s)
67+
68+
if use_rive_spm
69+
s.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DRIVE_EXPERIMENTAL_API' }
70+
end
5371
end

android/src/main/java/com/margelo/nitro/rive/HybridRiveFile.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.margelo.nitro.rive
33
import androidx.annotation.Keep
44
import app.rive.runtime.kotlin.core.File
55
import com.facebook.proguard.annotations.DoNotStrip
6+
import com.margelo.nitro.core.Promise
67
import java.lang.ref.WeakReference
78
import kotlinx.coroutines.CoroutineScope
89
import kotlinx.coroutines.Dispatchers
@@ -96,6 +97,22 @@ class HybridRiveFile : HybridRiveFileSpec() {
9697
}
9798
}
9899

100+
override fun getEnums(): Promise<Array<RiveEnumDefinition>> {
101+
return Promise.async {
102+
val file = riveFile ?: return@async emptyArray()
103+
try {
104+
file.enums.map { enum ->
105+
RiveEnumDefinition(
106+
name = enum.name,
107+
values = enum.values.toTypedArray()
108+
)
109+
}.toTypedArray()
110+
} catch (e: NoSuchMethodError) {
111+
throw UnsupportedOperationException("getEnums requires rive-android SDK with enums support")
112+
}
113+
}
114+
}
115+
99116
override fun dispose() {
100117
scope.cancel()
101118
weakViews.clear()

example/__tests__/viewmodel-properties.harness.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { describe, it, expect } from 'react-native-harness';
2-
import type { ViewModelInstance } from '@rive-app/react-native';
2+
import type {
3+
ViewModelInstance,
4+
RiveEnumDefinition,
5+
} from '@rive-app/react-native';
36
import { RiveFileFactory } from '@rive-app/react-native';
47

58
const DATABINDING = require('../assets/rive/databinding.riv');
@@ -225,3 +228,18 @@ describe('Property Listeners', () => {
225228
expect(() => cleanup2()).not.toThrow();
226229
});
227230
});
231+
232+
describe('RiveFile Enums', () => {
233+
const expectedEnums: RiveEnumDefinition[] = [
234+
{ name: 'Pets', values: ['chipmunk', 'rat', 'frog', 'owl', 'cat', 'dog'] },
235+
];
236+
237+
it('getEnums returns enum definitions from file', async () => {
238+
const file = await RiveFileFactory.fromSource(DATABINDING, undefined);
239+
const enums = await file.getEnums();
240+
241+
expect(enums.length).toBe(expectedEnums.length);
242+
expect(enums[0]?.name).toBe(expectedEnums[0]?.name);
243+
expect(enums[0]?.values).toEqual(expectedEnums[0]?.values);
244+
});
245+
});

example/ios/Podfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
22

3+
$UseRiveSPM = ENV['USE_RIVE_SPM'] == '1'
4+
35
# Resolve react_native_pods.rb with node to allow for hoisting
46
require Pod::Executable.execute_command('node', ['-p',
57
'require.resolve(

example/ios/RiveExample.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ios/HybridRiveFile.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1+
import NitroModules
12
import RiveRuntime
3+
#if RIVE_EXPERIMENTAL_API
4+
@_spi(RiveExperimental) import RiveRuntime
5+
#endif
26

37
typealias ReferencedAssetCache = [String: RiveFileAsset]
48

9+
/// Source for creating experimental File instances
10+
enum ExperimentalFileSource {
11+
case data(Data)
12+
case resource(String)
13+
}
14+
515
class HybridRiveFile: HybridRiveFileSpec, RiveViewSource {
616
var riveFile: RiveFile?
717
var referencedAssetCache: ReferencedAssetCache?
818
var assetLoader: ReferencedAssetLoader?
919
var cachedFactory: RiveFactory?
1020
private var weakViews: [Weak<RiveReactNativeView>] = []
1121

22+
/// Source for experimental API - stored to create experimental File on demand
23+
var experimentalSource: ExperimentalFileSource?
24+
1225
public func setRiveFile(_ riveFile: RiveFile) {
1326
self.riveFile = riveFile
1427
}
@@ -118,6 +131,42 @@ class HybridRiveFile: HybridRiveFileSpec, RiveViewSource {
118131
}
119132
}
120133

134+
func getEnums() throws -> Promise<[RiveEnumDefinition]> {
135+
return Promise.async { [weak self] in
136+
#if RIVE_EXPERIMENTAL_API
137+
guard let source = self?.experimentalSource else {
138+
throw NSError(
139+
domain: "RiveError",
140+
code: 1,
141+
userInfo: [NSLocalizedDescriptionKey: "getEnums requires experimental API. Use USE_RIVE_SPM=1 with pod install."]
142+
)
143+
}
144+
145+
// Create worker and experimental file on demand
146+
let worker = await Worker()
147+
let experimentalSource: Source
148+
switch source {
149+
case .data(let data):
150+
experimentalSource = .data(data)
151+
case .resource(let name):
152+
experimentalSource = .local(name, nil)
153+
}
154+
155+
let file = try await File(source: experimentalSource, worker: worker)
156+
let viewModelEnums = try await file.getViewModelEnums()
157+
return viewModelEnums.map { vmEnum in
158+
RiveEnumDefinition(name: vmEnum.name, values: vmEnum.values)
159+
}
160+
#else
161+
throw NSError(
162+
domain: "RiveError",
163+
code: 1,
164+
userInfo: [NSLocalizedDescriptionKey: "getEnums requires RiveRuntime 6.15.0+ with experimental API. Use USE_RIVE_SPM=1 with pod install."]
165+
)
166+
#endif
167+
}
168+
}
169+
121170
func dispose() {
122171
weakViews.removeAll()
123172
referencedAssetCache = nil

ios/HybridRiveFileFactory.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,22 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl
1616
/// - fileWithCustomAssetLoader: Closure to load the file with a custom asset loader.
1717
/// - file: Closure to load the file without a custom asset loader.
1818
/// - referencedAssets: Optional referenced assets.
19+
/// - experimentalSource: Closure to extract the experimental source from the prepared result.
1920
/// - Returns: A promise resolving to a `HybridRiveFileSpec`.
2021
/// - Throws: Runtime errors if any step fails.
2122
func genericFrom<CheckResult, Prepared>(
2223
check: @escaping () throws -> CheckResult,
2324
prepare: @escaping (CheckResult) async throws -> Prepared,
2425
fileWithCustomAssetLoader: @escaping (Prepared, @escaping LoadAsset) throws -> RiveFile,
2526
file: @escaping (Prepared) throws -> RiveFile,
26-
referencedAssets: ReferencedAssetsType?
27+
referencedAssets: ReferencedAssetsType?,
28+
experimentalSource: @escaping (Prepared) -> ExperimentalFileSource?
2729
) throws -> Promise<(any HybridRiveFileSpec)> {
2830
return Promise.async {
2931
do {
3032
let checked = try check()
3133
let prepared = try await prepare(checked)
34+
let expSource = experimentalSource(prepared)
3235

3336
let result = try await withCheckedThrowingContinuation { continuation in
3437
DispatchQueue.global(qos: .userInitiated).async {
@@ -73,6 +76,7 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl
7376
hybridRiveFile.cachedFactory = factory
7477
}
7578
hybridRiveFile.assetLoader = result.loader
79+
hybridRiveFile.experimentalSource = expSource
7680
return hybridRiveFile
7781
} catch let error as NSError {
7882
throw RuntimeError.error(
@@ -98,7 +102,8 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl
98102
try RiveFile(data: data, loadCdn: loadCdn, customAssetLoader: loader)
99103
},
100104
file: { (data) in try RiveFile(data: data, loadCdn: loadCdn) },
101-
referencedAssets: referencedAssets
105+
referencedAssets: referencedAssets,
106+
experimentalSource: { data in .data(data) }
102107
)
103108
}
104109

@@ -119,7 +124,8 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl
119124
try RiveFile(data: data, loadCdn: loadCdn, customAssetLoader: loader)
120125
},
121126
file: { (data) in try RiveFile(data: data, loadCdn: loadCdn) },
122-
referencedAssets: referencedAssets
127+
referencedAssets: referencedAssets,
128+
experimentalSource: { data in .data(data) }
123129
)
124130
}
125131

@@ -137,7 +143,8 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl
137143
try RiveFile(resource: resource, loadCdn: loadCdn, customAssetLoader: loader)
138144
},
139145
file: { (resource) in try RiveFile(resource: resource, loadCdn: loadCdn) },
140-
referencedAssets: referencedAssets
146+
referencedAssets: referencedAssets,
147+
experimentalSource: { resource in .resource(resource) }
141148
)
142149
}
143150

@@ -157,7 +164,8 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl
157164
try RiveFile(data: data, loadCdn: loadCdn, customAssetLoader: loader)
158165
},
159166
file: { (data) in try RiveFile(data: data, loadCdn: loadCdn) },
160-
referencedAssets: referencedAssets
167+
referencedAssets: referencedAssets,
168+
experimentalSource: { data in .data(data) }
161169
)
162170
}
163171
}

nitrogen/generated/android/c++/JHybridRiveFileSpec.cpp

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nitrogen/generated/android/c++/JHybridRiveFileSpec.hpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)