Skip to content

Commit 3f2dbb4

Browse files
committed
Adds loading RiveFiles over HTTP
1 parent d61c704 commit 3f2dbb4

File tree

4 files changed

+114
-8
lines changed

4 files changed

+114
-8
lines changed

Example-iOS/Source/UIkit/SimpleAnimation.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,26 @@ class SimpleAnimationViewController: UIViewController {
1919
guard let riveFile = RiveFile(resource: resourceName) else {
2020
fatalError("Failed to load RiveFile")
2121
}
22+
2223
view.configure(riveFile)
23-
2424
self.view = view
2525
}
2626
}
27+
28+
/*
29+
class SimpleAnimationViewController: UIViewController {
30+
let url = "https://cdn.rive.app/animations/truck.riv"
31+
32+
override public func loadView() {
33+
super.loadView()
34+
35+
let view = RiveView()
36+
guard let riveFile = RiveFile(httpUrl: url, with: view) else {
37+
fatalError("Unable to load RiveFile")
38+
}
39+
40+
view.configure(riveFile)
41+
self.view = view
42+
}
43+
}
44+
*/

Source/Renderer/RiveFile.mm

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
// Copyright © 2021 Rive. All rights reserved.
77
//
88

9-
109
#import <Rive.h>
1110
#import <RivePrivateHeaders.h>
1211

@@ -43,6 +42,7 @@ - (nullable instancetype)initWithByteArray:(NSArray *)array {
4342
}];
4443
rive::BinaryReader reader = [self getReader:bytes byteLength:array.count];
4544
[self import:reader];
45+
self.isLoaded = true;
4646
}
4747
@finally {
4848
free(bytes);
@@ -57,6 +57,7 @@ - (nullable instancetype)initWithBytes:(UInt8 *)bytes byteLength:(UInt64)length
5757
if (self = [super init]) {
5858
rive::BinaryReader reader = [self getReader:bytes byteLength:length];
5959
[self import:reader];
60+
self.isLoaded = true;
6061
return self;
6162
}
6263
return nil;
@@ -81,6 +82,46 @@ - (nullable instancetype)initWithResource:(NSString *)resourceName {
8182
return [[RiveFile alloc] initWithResource:resourceName withExtension:@"riv"];
8283
}
8384

85+
/*
86+
* Creates a RiveFile from an HTTP url
87+
*/
88+
- (nullable instancetype)initWithHttpUrl:(NSString *)url withDelegate:(id<RiveFileDelegate>)delegate {
89+
self.isLoaded = false;
90+
if (self = [super init]) {
91+
self.delegate = delegate;
92+
// Set up the http download task
93+
NSURL *URL = [NSURL URLWithString:url];
94+
NSURLSession *session = [NSURLSession sessionWithConfiguration:
95+
[NSURLSessionConfiguration defaultSessionConfiguration]];
96+
NSURLSessionTask *task = [session downloadTaskWithURL:URL
97+
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
98+
if (!error) {
99+
// Load the data into the reader
100+
NSData *data = [NSData dataWithContentsOfURL: location];
101+
UInt8 *bytes = (UInt8 *)[data bytes];
102+
rive::BinaryReader reader = [self getReader:bytes byteLength:[data length]];
103+
[self import:reader];
104+
self.isLoaded = true;
105+
dispatch_async(dispatch_get_main_queue(), ^{
106+
if ([[NSThread currentThread] isMainThread]) {
107+
if ([self.delegate respondsToSelector:@selector(riveFileDidLoad:)]) {
108+
[self.delegate riveFileDidLoad:self];
109+
}
110+
}
111+
});
112+
}
113+
}];
114+
115+
// Kick off the http download
116+
[task resume];
117+
118+
// Return the as yet uninitialized RiveFile
119+
return self;
120+
}
121+
122+
return nil;
123+
}
124+
84125
- (void) import:(rive::BinaryReader)reader {
85126
rive::ImportResult result = rive::File::import(reader, &riveFile);
86127
if (result == rive::ImportResult::success) {

Source/Renderer/include/RiveFile.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
NS_ASSUME_NONNULL_BEGIN
1616

1717
@class RiveArtboard;
18+
@protocol RiveFileDelegate;
1819

1920
/*
2021
* RiveFile
@@ -24,10 +25,17 @@ NS_ASSUME_NONNULL_BEGIN
2425
@property (class, readonly) uint majorVersion;
2526
@property (class, readonly) uint minorVersion;
2627

28+
// Is the Rive file loaded and ready for use?
29+
@property bool isLoaded;
30+
31+
// Delegate for calling when a file has finished loading
32+
@property id delegate;
33+
2734
- (nullable instancetype)initWithByteArray:(NSArray *)bytes;
2835
- (nullable instancetype)initWithBytes:(UInt8 *)bytes byteLength:(UInt64)length;
2936
- (nullable instancetype)initWithResource:(NSString *)resourceName withExtension:(NSString *)extension;
3037
- (nullable instancetype)initWithResource:(NSString *)resourceName;
38+
- (nullable instancetype)initWithHttpUrl:(NSString *)url withDelegate:(id<RiveFileDelegate>)delegate;
3139

3240
// Returns a reference to the default artboard
3341
- (RiveArtboard *)artboard;
@@ -44,7 +52,13 @@ NS_ASSUME_NONNULL_BEGIN
4452
// Returns the names of all artboards in the file.
4553
- (NSArray<NSString *> *)artboardNames;
4654

55+
@end
4756

57+
/*
58+
* Delegate to inform when a rive file is loaded
59+
*/
60+
@protocol RiveFileDelegate <NSObject>
61+
- (void)riveFileDidLoad:(RiveFile *)riveFile;
4862
@end
4963

5064
NS_ASSUME_NONNULL_END

Source/Views/RiveView.swift

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ class EventQueue {
8484
}
8585
}
8686

87+
/// Stores config options for a RiveFile when rive files load async
88+
struct ConfigOptions {
89+
let riveFile: RiveFile
90+
var artboard: String? = nil
91+
var animation: String? = nil
92+
var stateMachine: String?
93+
var autoPlay: Bool = true
94+
}
95+
8796
public class RiveView: UIView {
8897
// Configuration
8998
private var riveFile: RiveFile?
@@ -108,6 +117,9 @@ public class RiveView: UIView {
108117
public weak var inputsDelegate: InputsDelegate?
109118
public weak var stateChangeDelegate: StateChangeDelegate?
110119

120+
// Tracks config options when rive files load asynchronously
121+
private var configOptions: ConfigOptions?
122+
111123
// Queue of events that need to be done outside view updates
112124
private var eventQueue = EventQueue()
113125

@@ -150,7 +162,7 @@ public class RiveView: UIView {
150162
self.stopDelegate = stopDelegate
151163
self.inputsDelegate = inputsDelegate
152164
self.stateChangeDelegate = stateChangeDelegate
153-
self.configure(riveFile, andArtboard: artboard, andAnimation:animation, andStateMachine: stateMachine, andAutoPlay: autoplay)
165+
self.configure(riveFile, andArtboard: artboard, andAnimation: animation, andStateMachine: stateMachine, andAutoPlay: autoplay)
154166
}
155167

156168
/// Minimalist constructor, call `.configure` to customize the `RiveView` later.
@@ -163,6 +175,13 @@ public class RiveView: UIView {
163175
}
164176
}
165177

178+
// Handle when a Rive file is asynchronously loaded
179+
extension RiveView: RiveFileDelegate {
180+
public func riveFileDidLoad(_ riveFile: RiveFile) {
181+
self.configure(riveFile);
182+
}
183+
}
184+
166185
// MARK:- Configure
167186
extension RiveView {
168187
/// Configure fit to specify how and if the animation should be resized to fit its container.
@@ -206,6 +225,18 @@ extension RiveView {
206225
andStateMachine stateMachine: String?=nil,
207226
andAutoPlay autoPlay: Bool=true
208227
) {
228+
if !riveFile.isLoaded {
229+
// Save the config details for async call
230+
self.configOptions = ConfigOptions(
231+
riveFile: riveFile,
232+
artboard: artboard,
233+
animation: animation,
234+
stateMachine: stateMachine,
235+
autoPlay: autoPlay
236+
);
237+
return;
238+
}
239+
209240
clear()
210241
// Testing stuff
211242
NotificationCenter.default.addObserver(self, selector: #selector(animationWillEnterForeground),
@@ -217,9 +248,9 @@ extension RiveView {
217248
self.isOpaque = false
218249

219250
self.riveFile = riveFile
220-
self.autoPlay = autoPlay
251+
self.autoPlay = configOptions?.autoPlay ?? autoPlay
221252

222-
if let artboardName = artboard {
253+
if let artboardName = configOptions?.artboard ?? artboard {
223254
self._artboard = riveFile.artboard(fromName:artboardName)
224255
}else {
225256
self._artboard = riveFile.artboard()
@@ -239,16 +270,18 @@ extension RiveView {
239270

240271
// Start the animation loop
241272
if autoPlay {
242-
if let animationName = animation {
273+
if let animationName = configOptions?.animation ?? animation {
243274
play(animationName: animationName)
244-
}else if let stateMachineName = stateMachine {
275+
}else if let stateMachineName = configOptions?.stateMachine ?? stateMachine {
245276
play(animationName: stateMachineName, isStateMachine: true)
246277
}else {
247278
play()
248279
}
249-
}else {
280+
} else {
250281
advance(delta: 0)
251282
}
283+
// Clear out any config options
284+
self.configOptions = nil
252285
}
253286

254287
/// Stop playback, clear any created animation or state machine instances.

0 commit comments

Comments
 (0)