mirror of
https://github.com/rive-app/rive-ios.git
synced 2026-01-18 17:11:28 +01:00
few bits to sort out - [x] make our mix of simulator/emulator consistent, settling on emulator - [x] passing the factory in works great for just in time asset decoding, but its not amazing when you want to decode ahead of time. - [x] couple of places left to pass this function signature through. (Question) is there a neater way to get this done, feels a bit like we are going back to parameter explosion a bit? - [x] should do a few examples, i think the complexity grows quite a bit in this one as you add caching, or callbacks - [x] should get the cached images/fonts to draw on init as well, either warming up cache, or jitting - [x] examples loading assets from the bundle (also there seem to be actual asset things too? should we use those?!) - [x] add test - [x] re-add "preview" project & rev the preview project once this has been deployed. (do this after new ios deploy) - [x] fix up race condition (see comment) https://github.com/rive-app/rive/assets/1216025/2c14330f-e8a4-481b-bc27-4807cabe3b82 (simple example, both swift ui and standard)  Diffs= fabb7f97f Ios out of band (#6232) Co-authored-by: Gordon Hayes <pggordonhayes@gmail.com> Co-authored-by: Maxwell Talbot <talbot.maxwell@gmail.com>
432 lines
14 KiB
Plaintext
432 lines
14 KiB
Plaintext
//
|
|
// RiveFile.m
|
|
// RiveRuntime
|
|
//
|
|
// Created by Maxwell Talbot on 5/14/21.
|
|
// Copyright © 2021 Rive. All rights reserved.
|
|
//
|
|
|
|
#import <Rive.h>
|
|
#import <RivePrivateHeaders.h>
|
|
#import <RenderContext.h>
|
|
#import <RenderContextManager.h>
|
|
#import <RiveFileAssetLoader.h>
|
|
#import <CDNFileAssetLoader.h>
|
|
|
|
#import <FileAssetLoaderAdapter.hpp>
|
|
|
|
/*
|
|
* RiveFile
|
|
*/
|
|
@implementation RiveFile
|
|
{
|
|
std::unique_ptr<rive::File> riveFile;
|
|
rive::FileAssetLoader* fileAssetLoader;
|
|
}
|
|
|
|
+ (uint)majorVersion
|
|
{
|
|
return UInt8(rive::File::majorVersion);
|
|
}
|
|
+ (uint)minorVersion
|
|
{
|
|
return UInt8(rive::File::minorVersion);
|
|
}
|
|
|
|
- (nullable instancetype)initWithByteArray:(NSArray*)array loadCdn:(bool)cdn error:(NSError**)error
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
UInt8* bytes;
|
|
@try
|
|
{
|
|
bytes = (UInt8*)calloc(array.count, sizeof(UInt64));
|
|
|
|
[array enumerateObjectsUsingBlock:^(NSNumber* number, NSUInteger index, BOOL* stop) {
|
|
bytes[index] = number.unsignedIntValue;
|
|
}];
|
|
BOOL ok = [self import:bytes byteLength:array.count loadCdn:cdn error:error];
|
|
if (!ok)
|
|
{
|
|
return nil;
|
|
}
|
|
self.isLoaded = true;
|
|
}
|
|
@finally
|
|
{
|
|
free(bytes);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (nullable instancetype)initWithByteArray:(NSArray*)array
|
|
loadCdn:(bool)cdn
|
|
customAssetLoader:(LoadAsset)customAssetLoader
|
|
error:(NSError**)error
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
UInt8* bytes;
|
|
@try
|
|
{
|
|
bytes = (UInt8*)calloc(array.count, sizeof(UInt64));
|
|
|
|
[array enumerateObjectsUsingBlock:^(NSNumber* number, NSUInteger index, BOOL* stop) {
|
|
bytes[index] = number.unsignedIntValue;
|
|
}];
|
|
BOOL ok = [self import:bytes
|
|
byteLength:array.count
|
|
loadCdn:cdn
|
|
customAssetLoader:customAssetLoader
|
|
error:error];
|
|
if (!ok)
|
|
{
|
|
return nil;
|
|
}
|
|
self.isLoaded = true;
|
|
}
|
|
@finally
|
|
{
|
|
free(bytes);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// QUESTION: deprecate? init with NSData feels like its all we need?
|
|
- (nullable instancetype)initWithBytes:(UInt8*)bytes
|
|
byteLength:(UInt64)length
|
|
loadCdn:(bool)cdn
|
|
error:(NSError**)error
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
BOOL ok = [self import:bytes byteLength:length loadCdn:cdn error:error];
|
|
if (!ok)
|
|
{
|
|
return nil;
|
|
}
|
|
self.isLoaded = true;
|
|
return self;
|
|
}
|
|
return nil;
|
|
}
|
|
- (nullable instancetype)initWithBytes:(UInt8*)bytes
|
|
byteLength:(UInt64)length
|
|
loadCdn:(bool)cdn
|
|
customAssetLoader:(LoadAsset)customAssetLoader
|
|
error:(NSError**)error
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
BOOL ok = [self import:bytes
|
|
byteLength:length
|
|
loadCdn:cdn
|
|
customAssetLoader:customAssetLoader
|
|
error:error];
|
|
if (!ok)
|
|
{
|
|
return nil;
|
|
}
|
|
self.isLoaded = true;
|
|
return self;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (nullable instancetype)initWithData:(NSData*)data loadCdn:(bool)cdn error:(NSError**)error
|
|
{
|
|
UInt8* bytes = (UInt8*)[data bytes];
|
|
return [self initWithBytes:bytes byteLength:data.length loadCdn:cdn error:error];
|
|
}
|
|
- (nullable instancetype)initWithData:(NSData*)data
|
|
loadCdn:(bool)cdn
|
|
customAssetLoader:(LoadAsset)customAssetLoader
|
|
error:(NSError**)error
|
|
{
|
|
UInt8* bytes = (UInt8*)[data bytes];
|
|
return [self initWithBytes:bytes
|
|
byteLength:data.length
|
|
loadCdn:cdn
|
|
customAssetLoader:customAssetLoader
|
|
error:error];
|
|
}
|
|
|
|
/*
|
|
* Creates a RiveFile from a binary resource
|
|
*/
|
|
- (nullable instancetype)initWithResource:(NSString*)resourceName
|
|
withExtension:(NSString*)extension
|
|
loadCdn:(bool)cdn
|
|
error:(NSError**)error
|
|
{
|
|
// QUESTION: good ideas on how we can combine a few of these into following the same path
|
|
// better?
|
|
// there's a lot of copy pasta here.
|
|
NSString* filepath = [[NSBundle mainBundle] pathForResource:resourceName ofType:extension];
|
|
NSURL* fileUrl = [NSURL fileURLWithPath:filepath];
|
|
NSData* fileData = [NSData dataWithContentsOfURL:fileUrl];
|
|
|
|
return [self initWithData:fileData loadCdn:cdn error:error];
|
|
}
|
|
|
|
/*
|
|
* Creates a RiveFile from a binary resource, and assumes the resource extension is '.riv'
|
|
*/
|
|
- (nullable instancetype)initWithResource:(NSString*)resourceName
|
|
loadCdn:(bool)cdn
|
|
error:(NSError**)error
|
|
{
|
|
return [self initWithResource:resourceName withExtension:@"riv" loadCdn:cdn error:error];
|
|
}
|
|
|
|
- (nullable instancetype)initWithResource:(nonnull NSString*)resourceName
|
|
loadCdn:(bool)cdn
|
|
customAssetLoader:(nonnull LoadAsset)customAssetLoader
|
|
error:(NSError* __autoreleasing _Nullable* _Nullable)error
|
|
{
|
|
return [self initWithResource:resourceName
|
|
withExtension:@"riv"
|
|
loadCdn:cdn
|
|
customAssetLoader:customAssetLoader
|
|
error:error];
|
|
}
|
|
|
|
- (nullable instancetype)initWithResource:(nonnull NSString*)resourceName
|
|
withExtension:(nonnull NSString*)extension
|
|
loadCdn:(bool)cdn
|
|
customAssetLoader:(nonnull LoadAsset)customAssetLoader
|
|
error:(NSError* __autoreleasing _Nullable* _Nullable)error
|
|
{
|
|
NSString* filepath = [[NSBundle mainBundle] pathForResource:resourceName ofType:extension];
|
|
NSURL* fileUrl = [NSURL fileURLWithPath:filepath];
|
|
NSData* fileData = [NSData dataWithContentsOfURL:fileUrl];
|
|
return [self initWithData:fileData loadCdn:cdn customAssetLoader:customAssetLoader error:error];
|
|
}
|
|
|
|
/*
|
|
* Creates a RiveFile from an HTTP url
|
|
*/
|
|
- (nullable instancetype)initWithHttpUrl:(NSString*)url
|
|
loadCdn:(bool)loadCdn
|
|
withDelegate:(id<RiveFileDelegate>)delegate
|
|
{
|
|
return [self initWithHttpUrl:url
|
|
loadCdn:loadCdn
|
|
customAssetLoader:^bool(RiveFileAsset* asset, NSData* data, RiveFactory* factory) {
|
|
return false;
|
|
}
|
|
withDelegate:delegate];
|
|
}
|
|
|
|
- (nullable instancetype)initWithHttpUrl:(nonnull NSString*)url
|
|
loadCdn:(bool)cdn
|
|
customAssetLoader:(nonnull LoadAsset)customAssetLoader
|
|
withDelegate:(nonnull id<RiveFileDelegate>)delegate
|
|
{
|
|
self.isLoaded = false;
|
|
if (self = [super init])
|
|
{
|
|
self.delegate = delegate;
|
|
// Set up the http download task
|
|
NSURL* URL = [NSURL URLWithString:url];
|
|
|
|
// TODO: we are still adding 8MB of memory when we load our first http url.
|
|
NSURLSessionTask* task = [[NSURLSession sharedSession]
|
|
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];
|
|
// TODO: Do something with this error the proper way with delegates.
|
|
NSError* error = nil;
|
|
[self import:bytes
|
|
byteLength:[data length]
|
|
loadCdn:true
|
|
customAssetLoader:customAssetLoader
|
|
error:&error];
|
|
self.isLoaded = true;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([[NSThread currentThread] isMainThread])
|
|
{
|
|
if ([self.delegate respondsToSelector:@selector(riveFileDidLoad:error:)])
|
|
{
|
|
NSError* error = nil;
|
|
[self.delegate riveFileDidLoad:self error:&error];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}];
|
|
|
|
// Kick off the http download
|
|
[task resume];
|
|
return self;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (BOOL)import:(UInt8*)bytes byteLength:(UInt64)length loadCdn:(bool)loadCdn error:(NSError**)error
|
|
{
|
|
return [self import:bytes
|
|
byteLength:length
|
|
loadCdn:loadCdn
|
|
customAssetLoader:^bool(RiveFileAsset* asset, NSData* data, RiveFactory* factory) {
|
|
return false;
|
|
}
|
|
error:error];
|
|
}
|
|
- (BOOL)import:(UInt8*)bytes
|
|
byteLength:(UInt64)length
|
|
loadCdn:(bool)loadCdn
|
|
customAssetLoader:(LoadAsset)custom
|
|
error:(NSError**)error
|
|
{
|
|
rive::ImportResult result;
|
|
RenderContext* renderContext = [[RenderContextManager shared] getDefaultContext];
|
|
assert(renderContext);
|
|
rive::Factory* factory = [renderContext factory];
|
|
|
|
FallbackFileAssetLoader* fallbackLoader = [[FallbackFileAssetLoader alloc] init];
|
|
|
|
CustomFileAssetLoader* customAssetLoader =
|
|
[[CustomFileAssetLoader alloc] initWithLoader:custom];
|
|
[fallbackLoader addLoader:customAssetLoader];
|
|
|
|
if (loadCdn)
|
|
{
|
|
CDNFileAssetLoader* cdnLoader = [[CDNFileAssetLoader alloc] init];
|
|
[fallbackLoader addLoader:cdnLoader];
|
|
}
|
|
|
|
fileAssetLoader = new rive::FileAssetLoaderAdapter(fallbackLoader);
|
|
|
|
auto file = rive::File::import(rive::Span(bytes, length), factory, &result, fileAssetLoader);
|
|
if (result == rive::ImportResult::success)
|
|
{
|
|
riveFile = std::move(file);
|
|
return true;
|
|
}
|
|
|
|
switch (result)
|
|
{
|
|
case rive::ImportResult::unsupportedVersion:
|
|
*error =
|
|
[NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveUnsupportedVersion
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : @"Unsupported Rive File Version",
|
|
@"name" : @"UnsupportedVersion"
|
|
}];
|
|
break;
|
|
case rive::ImportResult::malformed:
|
|
*error = [NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveMalformedFile
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : @"Malformed Rive File.",
|
|
@"name" : @"Malformed"
|
|
}];
|
|
break;
|
|
default:
|
|
*error = [NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveUnknownError
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : @"Unknown error loading file.",
|
|
@"name" : @"Unknown"
|
|
}];
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
- (RiveArtboard*)artboard:(NSError**)error
|
|
{
|
|
auto artboard = riveFile->artboardDefault();
|
|
if (artboard == nullptr)
|
|
{
|
|
*error = [NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveNoArtboardsFound
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : @"No Artboards Found.",
|
|
@"name" : @"NoArtboardsFound"
|
|
}];
|
|
return nil;
|
|
}
|
|
else
|
|
{
|
|
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
|
|
}
|
|
}
|
|
|
|
- (NSInteger)artboardCount
|
|
{
|
|
return riveFile->artboardCount();
|
|
}
|
|
|
|
- (RiveArtboard*)artboardFromIndex:(NSInteger)index error:(NSError**)error
|
|
{
|
|
auto artboard = riveFile->artboardAt(index);
|
|
if (artboard == nullptr)
|
|
{
|
|
*error = [NSError
|
|
errorWithDomain:RiveErrorDomain
|
|
code:RiveNoArtboardFound
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : [NSString
|
|
stringWithFormat:@"No Artboard Found at index %ld.", (long)index],
|
|
@"name" : @"NoArtboardFound"
|
|
}];
|
|
return nil;
|
|
}
|
|
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
|
|
}
|
|
|
|
- (RiveArtboard*)artboardFromName:(NSString*)name error:(NSError**)error
|
|
{
|
|
std::string stdName = std::string([name UTF8String]);
|
|
auto artboard = riveFile->artboardNamed(stdName);
|
|
if (artboard == nullptr)
|
|
{
|
|
*error = [NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveNoArtboardFound
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : [NSString
|
|
stringWithFormat:@"No Artboard Found with name %@.", name],
|
|
@"name" : @"NoArtboardFound"
|
|
}];
|
|
return nil;
|
|
}
|
|
else
|
|
{
|
|
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
|
|
}
|
|
}
|
|
|
|
- (NSArray*)artboardNames
|
|
{
|
|
NSMutableArray* artboardNames = [NSMutableArray array];
|
|
|
|
for (NSUInteger i = 0; i < [self artboardCount]; i++)
|
|
{
|
|
[artboardNames addObject:[[self artboardFromIndex:i error:nil] name]];
|
|
}
|
|
return artboardNames;
|
|
}
|
|
|
|
/// Clean up rive file
|
|
- (void)dealloc
|
|
{
|
|
riveFile.reset(nullptr);
|
|
delete fileAssetLoader;
|
|
}
|
|
|
|
@end
|