Skip to content

Commit

Permalink
Merge pull request #60 from rive-app/networking
Browse files Browse the repository at this point in the history
New constructors for RiveFile
  • Loading branch information
mjtalbot authored Jun 2, 2021
2 parents f8c7f23 + 3f2dbb4 commit a0f9cec
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 39 deletions.
35 changes: 20 additions & 15 deletions Example-iOS/Source/UIkit/SimpleAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,36 @@
import UIKit
import RiveRuntime

func getResourceBytes(resourceName: String, resourceExt: String=".riv") -> [UInt8] {
guard let url = Bundle.main.url(forResource: resourceName, withExtension: resourceExt) else {
fatalError("Failed to locate \(resourceName) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
}

// Import the data into a RiveFile
return [UInt8](data)
}


class SimpleAnimationViewController: UIViewController {
let resourceName = "truck_v7"

override public func loadView() {
super.loadView()

let view = RiveView()
guard let riveFile = RiveFile(byteArray: getResourceBytes(resourceName: resourceName)) else {
guard let riveFile = RiveFile(resource: resourceName) else {
fatalError("Failed to load RiveFile")
}

view.configure(riveFile)

self.view = view
}
}

/*
class SimpleAnimationViewController: UIViewController {
let url = "https://cdn.rive.app/animations/truck.riv"

override public func loadView() {
super.loadView()

let view = RiveView()
guard let riveFile = RiveFile(httpUrl: url, with: view) else {
fatalError("Unable to load RiveFile")
}

view.configure(riveFile)
self.view = view
}
}
*/
96 changes: 78 additions & 18 deletions Source/Renderer/RiveFile.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// Copyright © 2021 Rive. All rights reserved.
//


#import <Rive.h>
#import <RivePrivateHeaders.h>

Expand All @@ -32,23 +31,6 @@ @implementation RiveFile {
+ (uint)majorVersion { return UInt8(rive::File::majorVersion); }
+ (uint)minorVersion { return UInt8(rive::File::minorVersion); }

- (void) import:(rive::BinaryReader)reader {
rive::ImportResult result = rive::File::import(reader, &riveFile);
if (result == rive::ImportResult::success) {
return;
}
else if(result == rive::ImportResult::unsupportedVersion){
@throw [[RiveException alloc] initWithName:@"UnsupportedVersion" reason:@"Unsupported Rive File Version." userInfo:nil];

}
else if(result == rive::ImportResult::malformed){
@throw [[RiveException alloc] initWithName:@"Malformed" reason:@"Malformed Rive File." userInfo:nil];
}
else {
@throw [[RiveException alloc] initWithName:@"Unknown" reason:@"Unknown error loading file." userInfo:nil];
}
}

- (nullable instancetype)initWithByteArray:(NSArray *)array {
if (self = [super init]) {
UInt8* bytes;
Expand All @@ -60,6 +42,7 @@ - (nullable instancetype)initWithByteArray:(NSArray *)array {
}];
rive::BinaryReader reader = [self getReader:bytes byteLength:array.count];
[self import:reader];
self.isLoaded = true;
}
@finally {
free(bytes);
Expand All @@ -74,11 +57,88 @@ - (nullable instancetype)initWithBytes:(UInt8 *)bytes byteLength:(UInt64)length
if (self = [super init]) {
rive::BinaryReader reader = [self getReader:bytes byteLength:length];
[self import:reader];
self.isLoaded = true;
return self;
}
return nil;
}

/*
* Creates a RiveFile from a binary resource
*/
- (nullable instancetype)initWithResource:(NSString *)resourceName withExtension:(NSString *)extension {
NSString *filepath = [[NSBundle mainBundle] pathForResource:resourceName ofType:extension];
NSURL *fileUrl = [NSURL fileURLWithPath:filepath];
NSData *fileData = [NSData dataWithContentsOfURL:fileUrl];
UInt8 *bytePtr = (UInt8 *)[fileData bytes];

return [[RiveFile alloc] initWithBytes:bytePtr byteLength:fileData.length];
}

/*
* Creates a RiveFile from a binary resource, and assumes the resource extension is '.riv'
*/
- (nullable instancetype)initWithResource:(NSString *)resourceName {
return [[RiveFile alloc] initWithResource:resourceName withExtension:@"riv"];
}

/*
* Creates a RiveFile from an HTTP url
*/
- (nullable instancetype)initWithHttpUrl:(NSString *)url withDelegate:(id<RiveFileDelegate>)delegate {
self.isLoaded = false;
if (self = [super init]) {
self.delegate = delegate;
// Set up the http download task
NSURL *URL = [NSURL URLWithString:url];
NSURLSession *session = [NSURLSession sessionWithConfiguration:
[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionTask *task = [session downloadTaskWithURL:URL
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (!error) {
// Load the data into the reader
NSData *data = [NSData dataWithContentsOfURL: location];
UInt8 *bytes = (UInt8 *)[data bytes];
rive::BinaryReader reader = [self getReader:bytes byteLength:[data length]];
[self import:reader];
self.isLoaded = true;
dispatch_async(dispatch_get_main_queue(), ^{
if ([[NSThread currentThread] isMainThread]) {
if ([self.delegate respondsToSelector:@selector(riveFileDidLoad:)]) {
[self.delegate riveFileDidLoad:self];
}
}
});
}
}];

// Kick off the http download
[task resume];

// Return the as yet uninitialized RiveFile
return self;
}

return nil;
}

- (void) import:(rive::BinaryReader)reader {
rive::ImportResult result = rive::File::import(reader, &riveFile);
if (result == rive::ImportResult::success) {
return;
}
else if(result == rive::ImportResult::unsupportedVersion){
@throw [[RiveException alloc] initWithName:@"UnsupportedVersion" reason:@"Unsupported Rive File Version." userInfo:nil];

}
else if(result == rive::ImportResult::malformed){
@throw [[RiveException alloc] initWithName:@"Malformed" reason:@"Malformed Rive File." userInfo:nil];
}
else {
@throw [[RiveException alloc] initWithName:@"Unknown" reason:@"Unknown error loading file." userInfo:nil];
}
}

- (RiveArtboard *)artboard {
rive::Artboard *artboard = riveFile->artboard();
if (artboard == nullptr) {
Expand Down
16 changes: 16 additions & 0 deletions Source/Renderer/include/RiveFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
NS_ASSUME_NONNULL_BEGIN

@class RiveArtboard;
@protocol RiveFileDelegate;

/*
* RiveFile
Expand All @@ -24,8 +25,17 @@ NS_ASSUME_NONNULL_BEGIN
@property (class, readonly) uint majorVersion;
@property (class, readonly) uint minorVersion;

// Is the Rive file loaded and ready for use?
@property bool isLoaded;

// Delegate for calling when a file has finished loading
@property id delegate;

- (nullable instancetype)initWithByteArray:(NSArray *)bytes;
- (nullable instancetype)initWithBytes:(UInt8 *)bytes byteLength:(UInt64)length;
- (nullable instancetype)initWithResource:(NSString *)resourceName withExtension:(NSString *)extension;
- (nullable instancetype)initWithResource:(NSString *)resourceName;
- (nullable instancetype)initWithHttpUrl:(NSString *)url withDelegate:(id<RiveFileDelegate>)delegate;

// Returns a reference to the default artboard
- (RiveArtboard *)artboard;
Expand All @@ -42,7 +52,13 @@ NS_ASSUME_NONNULL_BEGIN
// Returns the names of all artboards in the file.
- (NSArray<NSString *> *)artboardNames;

@end

/*
* Delegate to inform when a rive file is loaded
*/
@protocol RiveFileDelegate <NSObject>
- (void)riveFileDidLoad:(RiveFile *)riveFile;
@end

NS_ASSUME_NONNULL_END
Expand Down
45 changes: 39 additions & 6 deletions Source/Views/RiveView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ class EventQueue {
}
}

/// Stores config options for a RiveFile when rive files load async
struct ConfigOptions {
let riveFile: RiveFile
var artboard: String? = nil
var animation: String? = nil
var stateMachine: String?
var autoPlay: Bool = true
}

public class RiveView: UIView {
// Configuration
private var riveFile: RiveFile?
Expand All @@ -108,6 +117,9 @@ public class RiveView: UIView {
public weak var inputsDelegate: InputsDelegate?
public weak var stateChangeDelegate: StateChangeDelegate?

// Tracks config options when rive files load asynchronously
private var configOptions: ConfigOptions?

// Queue of events that need to be done outside view updates
private var eventQueue = EventQueue()

Expand Down Expand Up @@ -150,7 +162,7 @@ public class RiveView: UIView {
self.stopDelegate = stopDelegate
self.inputsDelegate = inputsDelegate
self.stateChangeDelegate = stateChangeDelegate
self.configure(riveFile, andArtboard: artboard, andAnimation:animation, andStateMachine: stateMachine, andAutoPlay: autoplay)
self.configure(riveFile, andArtboard: artboard, andAnimation: animation, andStateMachine: stateMachine, andAutoPlay: autoplay)
}

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

// Handle when a Rive file is asynchronously loaded
extension RiveView: RiveFileDelegate {
public func riveFileDidLoad(_ riveFile: RiveFile) {
self.configure(riveFile);
}
}

// MARK:- Configure
extension RiveView {
/// Configure fit to specify how and if the animation should be resized to fit its container.
Expand Down Expand Up @@ -206,6 +225,18 @@ extension RiveView {
andStateMachine stateMachine: String?=nil,
andAutoPlay autoPlay: Bool=true
) {
if !riveFile.isLoaded {
// Save the config details for async call
self.configOptions = ConfigOptions(
riveFile: riveFile,
artboard: artboard,
animation: animation,
stateMachine: stateMachine,
autoPlay: autoPlay
);
return;
}

clear()
// Testing stuff
NotificationCenter.default.addObserver(self, selector: #selector(animationWillEnterForeground),
Expand All @@ -217,9 +248,9 @@ extension RiveView {
self.isOpaque = false

self.riveFile = riveFile
self.autoPlay = autoPlay
self.autoPlay = configOptions?.autoPlay ?? autoPlay

if let artboardName = artboard {
if let artboardName = configOptions?.artboard ?? artboard {
self._artboard = riveFile.artboard(fromName:artboardName)
}else {
self._artboard = riveFile.artboard()
Expand All @@ -239,16 +270,18 @@ extension RiveView {

// Start the animation loop
if autoPlay {
if let animationName = animation {
if let animationName = configOptions?.animation ?? animation {
play(animationName: animationName)
}else if let stateMachineName = stateMachine {
}else if let stateMachineName = configOptions?.stateMachine ?? stateMachine {
play(animationName: stateMachineName, isStateMachine: true)
}else {
play()
}
}else {
} else {
advance(delta: 0)
}
// Clear out any config options
self.configOptions = nil
}

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

0 comments on commit a0f9cec

Please sign in to comment.