From d61c7044a6f07226cd0e9fdb4776aed712d53319 Mon Sep 17 00:00:00 2001 From: matt Sullivan Date: Tue, 1 Jun 2021 12:59:35 -0700 Subject: [PATCH 1/2] Adds initWithResource contructor to RiveFile --- .../Source/UIkit/SimpleAnimation.swift | 15 +----- Source/Renderer/RiveFile.mm | 53 +++++++++++++------ Source/Renderer/include/RiveFile.h | 2 + 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/Example-iOS/Source/UIkit/SimpleAnimation.swift b/Example-iOS/Source/UIkit/SimpleAnimation.swift index a9ffdf92..ab18b3a4 100644 --- a/Example-iOS/Source/UIkit/SimpleAnimation.swift +++ b/Example-iOS/Source/UIkit/SimpleAnimation.swift @@ -9,19 +9,6 @@ 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" @@ -29,7 +16,7 @@ class SimpleAnimationViewController: UIViewController { 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) diff --git a/Source/Renderer/RiveFile.mm b/Source/Renderer/RiveFile.mm index 2ea61c1c..79cecbe7 100644 --- a/Source/Renderer/RiveFile.mm +++ b/Source/Renderer/RiveFile.mm @@ -32,23 +32,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; @@ -79,6 +62,42 @@ - (nullable instancetype)initWithBytes:(UInt8 *)bytes byteLength:(UInt64)length 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"]; +} + +- (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) { diff --git a/Source/Renderer/include/RiveFile.h b/Source/Renderer/include/RiveFile.h index 342e9323..bb582782 100644 --- a/Source/Renderer/include/RiveFile.h +++ b/Source/Renderer/include/RiveFile.h @@ -26,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN - (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; // Returns a reference to the default artboard - (RiveArtboard *)artboard; From 3f2dbb43e066fe1e64c9060884c03cd4bf44b08a Mon Sep 17 00:00:00 2001 From: matt Sullivan Date: Tue, 1 Jun 2021 17:04:18 -0700 Subject: [PATCH 2/2] Adds loading RiveFiles over HTTP --- .../Source/UIkit/SimpleAnimation.swift | 20 ++++++++- Source/Renderer/RiveFile.mm | 43 +++++++++++++++++- Source/Renderer/include/RiveFile.h | 14 ++++++ Source/Views/RiveView.swift | 45 ++++++++++++++++--- 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/Example-iOS/Source/UIkit/SimpleAnimation.swift b/Example-iOS/Source/UIkit/SimpleAnimation.swift index ab18b3a4..207f704f 100644 --- a/Example-iOS/Source/UIkit/SimpleAnimation.swift +++ b/Example-iOS/Source/UIkit/SimpleAnimation.swift @@ -19,8 +19,26 @@ class SimpleAnimationViewController: UIViewController { 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 + } + } + */ diff --git a/Source/Renderer/RiveFile.mm b/Source/Renderer/RiveFile.mm index 79cecbe7..779a45a5 100644 --- a/Source/Renderer/RiveFile.mm +++ b/Source/Renderer/RiveFile.mm @@ -6,7 +6,6 @@ // Copyright © 2021 Rive. All rights reserved. // - #import #import @@ -43,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); @@ -57,6 +57,7 @@ - (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; @@ -81,6 +82,46 @@ - (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)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) { diff --git a/Source/Renderer/include/RiveFile.h b/Source/Renderer/include/RiveFile.h index bb582782..c8be8a1f 100644 --- a/Source/Renderer/include/RiveFile.h +++ b/Source/Renderer/include/RiveFile.h @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @class RiveArtboard; +@protocol RiveFileDelegate; /* * RiveFile @@ -24,10 +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)delegate; // Returns a reference to the default artboard - (RiveArtboard *)artboard; @@ -44,7 +52,13 @@ NS_ASSUME_NONNULL_BEGIN // Returns the names of all artboards in the file. - (NSArray *)artboardNames; +@end +/* + * Delegate to inform when a rive file is loaded + */ +@protocol RiveFileDelegate +- (void)riveFileDidLoad:(RiveFile *)riveFile; @end NS_ASSUME_NONNULL_END diff --git a/Source/Views/RiveView.swift b/Source/Views/RiveView.swift index 1b90a54c..79a3e0dd 100644 --- a/Source/Views/RiveView.swift +++ b/Source/Views/RiveView.swift @@ -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? @@ -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() @@ -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. @@ -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. @@ -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), @@ -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() @@ -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.