feat(ios): add support for data binding images (#9664) 8f30ede7be

Co-authored-by: David Skuza <david@rive.app>
This commit is contained in:
dskuza
2025-06-04 15:23:01 +00:00
parent 15320ede5f
commit f0000132fe
18 changed files with 303 additions and 2 deletions

View File

@@ -1 +1 @@
052a4984ef8526de04a111ea4e7b725271e53d38
8f30ede7be2d34dd642dc6f53575f1ab9bd3525e

View File

@@ -43,6 +43,10 @@ private class DataBindingViewModel: RiveViewModel {
return dataBindingInstance?.viewModelInstanceProperty(fromPath: "Nested")
}
var imageProperty: RiveDataBindingViewModel.Instance.ImageProperty? {
return dataBindingInstance?.imageProperty(fromPath: "Image")
}
init(fileName: String) {
super.init(fileName: fileName)
@@ -91,6 +95,7 @@ struct DataBindingView: DismissableView {
updateEnum()
updateNestedViewModel()
updateTrigger()
updateImage()
// Manually advance the Rive view since it is not playing.
// When a Rive view is playing, this is handled for you.
@@ -175,5 +180,18 @@ struct DataBindingView: DismissableView {
guard let property = riveViewModel.triggerProperty(name: trigger) else { return }
property.trigger()
}
private func updateImage() {
let images = [
UIImage(systemName: "square.and.arrow.down")!,
UIImage(systemName: "paperplane")!,
UIImage(systemName: "externaldrive")!,
// or any other UIImage initializer
]
guard let property = riveViewModel.imageProperty else { return }
let image = images.randomElement()!
guard let renderImage = RiveRenderImage(image: image, format: .png) else { return }
property.setValue(renderImage)
}
}

View File

@@ -93,12 +93,17 @@
E5964A982A9697B600140479 /* RiveEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5964A972A9697B600140479 /* RiveEvent.mm */; };
E599DCF92AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; };
E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; };
F21C3D1B2DDFCD93005F82F4 /* RiveRenderImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21C3D1A2DDFCD93005F82F4 /* RiveRenderImage+Extensions.swift */; };
F21F08142C66526D00FFA205 /* RiveFallbackFontDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */; };
F22CF1B12D380E3700D35779 /* data_binding_test.riv in Resources */ = {isa = PBXBuildFile; fileRef = F22CF1B02D380E3700D35779 /* data_binding_test.riv */; };
F22CF1B32D380E6900D35779 /* DataBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22CF1B22D380E6900D35779 /* DataBindingTests.swift */; };
F22E12112D67D47C00F2E8C3 /* RiveDisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23B2B7F2D2DCD70007AA53E /* RiveDisplayLink.swift */; };
F23626AA2C8F90FA00727D9A /* nested_text_run.riv in Resources */ = {isa = PBXBuildFile; fileRef = F23626A92C8F90FA00727D9A /* nested_text_run.riv */; };
F23992E72CB9C1C60021EF61 /* RenderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F23992E62CB9C1C60021EF61 /* RenderContextTests.m */; };
F24FC6452DD3C83700DEE8C5 /* RiveRenderImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24FC6442DD3C83700DEE8C5 /* RiveRenderImageTests.swift */; };
F24FC6492DD3C85E00DEE8C5 /* 1x1_jpg.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F24FC6472DD3C85E00DEE8C5 /* 1x1_jpg.jpg */; };
F24FC64A2DD3C85E00DEE8C5 /* 1x1_png.png in Resources */ = {isa = PBXBuildFile; fileRef = F24FC6482DD3C85E00DEE8C5 /* 1x1_png.png */; };
F24FC6572DD3D7AD00DEE8C5 /* data_binding_image_test.riv in Resources */ = {isa = PBXBuildFile; fileRef = F24FC6562DD3D7AD00DEE8C5 /* data_binding_image_test.riv */; };
F259E5872D35B91700B78FEF /* RiveDataBindingViewModel.mm in Sources */ = {isa = PBXBuildFile; fileRef = F259E5862D35B91700B78FEF /* RiveDataBindingViewModel.mm */; };
F259E5882D35B91700B78FEF /* RiveDataBindingViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = F259E5852D35B91700B78FEF /* RiveDataBindingViewModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
F25C58A82D5A60EC00186C91 /* RiveLogger+DataBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F25C58A72D5A60EC00186C91 /* RiveLogger+DataBinding.swift */; };
@@ -228,12 +233,17 @@
E5964A952A965A9300140479 /* RiveEvent.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = RiveEvent.h; sourceTree = "<group>"; };
E5964A972A9697B600140479 /* RiveEvent.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveEvent.mm; sourceTree = "<group>"; };
E599DCF82AAFA06100D1E49A /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = rating_animation.riv; path = "Example-iOS/Assets/rating_animation.riv"; sourceTree = SOURCE_ROOT; };
F21C3D1A2DDFCD93005F82F4 /* RiveRenderImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveRenderImage+Extensions.swift"; sourceTree = "<group>"; };
F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFallbackFontDescriptor.swift; sourceTree = "<group>"; };
F22CF1B02D380E3700D35779 /* data_binding_test.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = data_binding_test.riv; sourceTree = "<group>"; };
F22CF1B22D380E6900D35779 /* DataBindingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBindingTests.swift; sourceTree = "<group>"; };
F23626A92C8F90FA00727D9A /* nested_text_run.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = nested_text_run.riv; sourceTree = "<group>"; };
F23992E62CB9C1C60021EF61 /* RenderContextTests.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RenderContextTests.m; sourceTree = "<group>"; };
F23B2B7F2D2DCD70007AA53E /* RiveDisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveDisplayLink.swift; sourceTree = "<group>"; };
F24FC6442DD3C83700DEE8C5 /* RiveRenderImageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveRenderImageTests.swift; sourceTree = "<group>"; };
F24FC6472DD3C85E00DEE8C5 /* 1x1_jpg.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 1x1_jpg.jpg; sourceTree = "<group>"; };
F24FC6482DD3C85E00DEE8C5 /* 1x1_png.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1x1_png.png; sourceTree = "<group>"; };
F24FC6562DD3D7AD00DEE8C5 /* data_binding_image_test.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = data_binding_image_test.riv; sourceTree = "<group>"; };
F259E5852D35B91700B78FEF /* RiveDataBindingViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModel.h; sourceTree = "<group>"; };
F259E5862D35B91700B78FEF /* RiveDataBindingViewModel.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveDataBindingViewModel.mm; sourceTree = "<group>"; };
F25C58A72D5A60EC00186C91 /* RiveLogger+DataBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+DataBinding.swift"; sourceTree = "<group>"; };
@@ -321,6 +331,9 @@
04BE53F726493FE600427B39 /* Assets */ = {
isa = PBXGroup;
children = (
F24FC6562DD3D7AD00DEE8C5 /* data_binding_image_test.riv */,
F24FC6472DD3C85E00DEE8C5 /* 1x1_jpg.jpg */,
F24FC6482DD3C85E00DEE8C5 /* 1x1_png.png */,
F22CF1B02D380E3700D35779 /* data_binding_test.riv */,
F2CCA9782C9B2799007DC0D2 /* referenced_image_asset.riv */,
F23626A92C8F90FA00727D9A /* nested_text_run.riv */,
@@ -429,6 +442,7 @@
C9C73ED324FC478800EF9516 /* Source */ = {
isa = PBXGroup;
children = (
F24FC6502DD3CF6700DEE8C5 /* Extensions */,
F259E5842D35B87800B78FEF /* DataBinding */,
F2CCA9C02C9E13B2007DC0D2 /* Logging */,
C3468E5727EB9887008652FD /* RiveView.swift */,
@@ -466,6 +480,7 @@
F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */,
F23992E62CB9C1C60021EF61 /* RenderContextTests.m */,
F22CF1B22D380E6900D35779 /* DataBindingTests.swift */,
F24FC6442DD3C83700DEE8C5 /* RiveRenderImageTests.swift */,
);
path = Tests;
sourceTree = "<group>";
@@ -484,6 +499,14 @@
path = Fonts;
sourceTree = "<group>";
};
F24FC6502DD3CF6700DEE8C5 /* Extensions */ = {
isa = PBXGroup;
children = (
F21C3D1A2DDFCD93005F82F4 /* RiveRenderImage+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
F259E5842D35B87800B78FEF /* DataBinding */ = {
isa = PBXGroup;
children = (
@@ -673,6 +696,7 @@
0424A8742BD592F2000A9A1C /* audio_test.riv in Resources */,
0412652A2B0CCB8E009400EC /* embedded_assets.riv in Resources */,
04BE540B2649403600427B39 /* multiple_animations.riv in Resources */,
F24FC6572DD3D7AD00DEE8C5 /* data_binding_image_test.riv in Resources */,
041265282B0CC387009400EC /* hosted_assets.riv in Resources */,
E57798AB2A7310B700FF25C3 /* testtext.riv in Resources */,
04ED72F3299C115100E8DE53 /* empty_animation_state.riv in Resources */,
@@ -684,6 +708,8 @@
04BE54092649403600427B39 /* state_machine_configurations.riv in Resources */,
04BE54072649403600427B39 /* what_a_state.riv in Resources */,
04E222402BE3C85100D82668 /* ball_test.riv in Resources */,
F24FC6492DD3C85E00DEE8C5 /* 1x1_jpg.jpg in Resources */,
F24FC64A2DD3C85E00DEE8C5 /* 1x1_png.png in Resources */,
0424A8762BD59435000A9A1C /* img_test.riv in Resources */,
04BE541A264A823000427B39 /* animationconfigurations.riv in Resources */,
04BE540E2649403600427B39 /* multiple_state_machines.riv in Resources */,
@@ -733,6 +759,7 @@
046FB7F8264EAA60000129B1 /* RiveStateMachineInstance.mm in Sources */,
C3468E5C27ED4C41008652FD /* RiveModel.swift in Sources */,
F259E5872D35B91700B78FEF /* RiveDataBindingViewModel.mm in Sources */,
F21C3D1B2DDFCD93005F82F4 /* RiveRenderImage+Extensions.swift in Sources */,
046FB7FF264EAA61000129B1 /* RiveFile.mm in Sources */,
F2FD94082CC94B1A00C1FC85 /* RiveFallbackFontCache.m in Sources */,
046FB7F2264EAA60000129B1 /* RiveArtboard.mm in Sources */,
@@ -763,6 +790,7 @@
F22CF1B32D380E6900D35779 /* DataBindingTests.swift in Sources */,
04BE5418264A818F00427B39 /* RiveAnimationConfigurationsTest.mm in Sources */,
04BE5427264C02AA00427B39 /* StateMachineInstanceTest.mm in Sources */,
F24FC6452DD3C83700DEE8C5 /* RiveRenderImageTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
@class RiveDataBindingViewModelInstanceColorProperty;
@class RiveDataBindingViewModelInstanceEnumProperty;
@class RiveDataBindingViewModelInstanceTriggerProperty;
@class RiveDataBindingViewModelInstanceImageProperty;
@class RiveDataBindingViewModelInstancePropertyData;
/// An object that represents an instance of a view model, used to update
@@ -159,6 +160,19 @@ NS_SWIFT_NAME(RiveDataBindingViewModel.Instance)
- (nullable RiveDataBindingViewModelInstanceTriggerProperty*)
triggerPropertyFromPath:(NSString*)path;
/// Gets an image property in the view model instance.
///
/// - Note: Unlike a `RiveViewModel.Instance`, a strong reference to this type
/// does not have to be made. If the property exists, the underlying property
/// will be cached, and calling this function again with the same path is
/// guaranteed to return the same object.
///
/// - Parameter path: The path to the number property.
///
/// - Returns: The property if it exists at the supplied path, otherwise nil.
- (nullable RiveDataBindingViewModelInstanceImageProperty*)
imagePropertyFromPath:(NSString*)path;
/// Calls all registered property listeners for the properties of the view model
/// instance.
- (void)updateListeners;

View File

@@ -312,6 +312,41 @@
return triggerProperty;
}
- (RiveDataBindingViewModelInstanceImageProperty*)imagePropertyFromPath:
(NSString*)path
{
RiveDataBindingViewModelInstanceImageProperty* cached;
if ((cached = [self
cachedPropertyFromPath:path
asClass:
[RiveDataBindingViewModelInstanceImageProperty
class]]))
{
return cached;
}
auto image = _instance->propertyImage(std::string([path UTF8String]));
if (image == nullptr)
{
[RiveLogger logWithViewModelInstance:self
imagePropertyAtPath:path
found:NO];
return nil;
}
[RiveLogger logWithViewModelInstance:self
imagePropertyAtPath:path
found:YES];
RiveDataBindingViewModelInstanceImageProperty* imageProperty =
[[RiveDataBindingViewModelInstanceImageProperty alloc]
initWithImage:image];
imageProperty.valueDelegate = self;
[self cacheProperty:imageProperty withPath:path];
return imageProperty;
}
- (void)updateListeners
{
[_properties enumerateKeysAndObjectsUsingBlock:^(

View File

@@ -262,4 +262,33 @@ NS_SWIFT_NAME(RiveDataBindingViewModelInstance.TriggerProperty)
@end
#pragma mark - Image
typedef void (^RiveDataBindingViewModelInstanceImagePropertyListener)(void)
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceImageProperty.Listener);
/// An object that represents a trigger property of a view model instance.
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.ImageProperty)
@interface RiveDataBindingViewModelInstanceImageProperty
: RiveDataBindingViewModelInstanceProperty
- (instancetype)init NS_UNAVAILABLE;
- (void)setValue:(nonnull RiveRenderImage*)image;
/// Adds a block as a listener, called when the property is triggered.
///
/// - Note: The property can be triggered either explicitly by the developer,
/// or as a result of a change in a state machine.
///
/// - Parameter listener: The block that will be called when the property's
/// value changes.
///
/// - Returns: A UUID for the listener, used in conjunction with
/// `removeListener`.
- (NSUUID*)addListener:
(RiveDataBindingViewModelInstanceImagePropertyListener)listener;
@end
NS_ASSUME_NONNULL_END

View File

@@ -536,3 +536,42 @@
}
@end
#pragma mark - Image
@implementation RiveDataBindingViewModelInstanceImageProperty
{
rive::ViewModelInstanceAssetImageRuntime* _image;
}
- (instancetype)initWithImage:(rive::ViewModelInstanceAssetImageRuntime*)image
{
if (self = [super initWithValue:image])
{
_image = image;
}
return self;
}
- (void)setValue:(RiveRenderImage*)renderImage
{
[RiveLogger logPropertyUpdated:self value:@"new image"];
_image->value([renderImage instance].get());
}
- (NSUUID*)addListener:
(RiveDataBindingViewModelInstanceTriggerPropertyListener)listener
{
return [super addListener:listener];
}
- (void)handleListeners
{
for (RiveDataBindingViewModelInstanceImagePropertyListener listener in self
.listeners.allValues)
{
listener();
}
}
@end

View File

@@ -0,0 +1,26 @@
//
// RiveRenderImage+Extensions.swift
// RiveRuntime
//
// Created by David Skuza on 5/22/25.
// Copyright © 2025 Rive. All rights reserved.
//
public extension RiveRenderImage {
enum Format {
case jpeg(compressionQuality: CGFloat)
case png
}
convenience init?(image: UIImage, format: Format) {
let data: Data?
switch format {
case .jpeg(let compressionQuality):
data = image.jpegData(compressionQuality: compressionQuality)
case .png:
data = image.pngData()
}
guard let data else { return nil }
self.init(data: data)
}
}

View File

@@ -27,6 +27,7 @@ enum RiveLoggerDataBindingEvent {
case enumProperty(String, Bool)
case viewModelProperty(String, Bool)
case triggerProperty(String, Bool)
case imageProperty(String, Bool)
}
enum Property: DataBindingEvent {
case propertyUpdated(String, String)
@@ -106,6 +107,11 @@ extension RiveLogger {
let message = message(instance: instance, for: "trigger", path: path, found: found)
dataBinding.debug("\(message)")
}
case .imageProperty(let path, let found):
_log(event: event, level: .debug) {
let message = message(instance: instance, for: "image", path: path, found: found)
dataBinding.debug("\(message)")
}
}
}
@@ -174,6 +180,10 @@ extension RiveLogger {
Self.log(viewModelInstance: instance, event: .triggerProperty(path, found))
}
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, imagePropertyAtPath path: String, found: Bool) {
Self.log(viewModelInstance: instance, event: .imageProperty(path, found))
}
// MARK: - Properties
@objc(logPropertyUpdated:value:) static func log(propertyUpdated property: RiveDataBindingViewModel.Instance.Property, value: String) {

View File

@@ -12,6 +12,7 @@
#import <RiveRuntime/RiveRuntime-Swift.h>
#import <CoreText/CTFont.h>
#import <rive/text/font_hb.hpp>
#import <RenderContext.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIFont.h>
@@ -41,6 +42,7 @@ static rive::rcp<rive::Font> riveFontFromNativeFont(id font,
rive::rcp<rive::RenderImage>
instance; // note: we do NOT own this, so don't delete it
}
- (instancetype)initWithImage:(rive::rcp<rive::RenderImage>)image
{
if (self = [super init])
@@ -53,6 +55,21 @@ static rive::rcp<rive::Font> riveFontFromNativeFont(id font,
return nil;
}
}
- (instancetype)initWithData:(NSData*)data
{
RenderContext* context = [[RenderContextManager shared] newDefaultContext];
RiveFactory* factory =
[[RiveFactory alloc] initWithFactory:[context factory]];
auto renderImage = [factory decodeImage:data];
auto image = [renderImage instance];
if (image == nullptr || image.get() == nullptr)
{
return nil;
}
return [[RiveRenderImage alloc] initWithImage:image];
}
- (rive::rcp<rive::RenderImage>)instance
{
return instance;

View File

@@ -22,6 +22,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface RiveRenderImage : NSObject
- (nullable instancetype)initWithData:(NSData*)data;
@end
@interface RiveAudio : NSObject

View File

@@ -232,4 +232,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithData:(rive::PropertyData)data;
@end
@interface RiveDataBindingViewModelInstanceImageProperty ()
- (instancetype)initWithImage:(rive::ViewModelInstanceAssetImageRuntime*)image;
@end
NS_ASSUME_NONNULL_END

BIN
Tests/Assets/1x1_jpg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

BIN
Tests/Assets/1x1_png.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

View File

@@ -10,7 +10,7 @@ import XCTest
@testable import RiveRuntime
class DataBindingTests: XCTestCase {
let file = try! RiveFile(testfileName: "data_binding_test")
let file: RiveFile = try! RiveFile(testfileName: "data_binding_test")
// MARK: - RiveFile
@@ -442,6 +442,28 @@ class DataBindingTests: XCTestCase {
XCTAssertNil(instance.triggerProperty(fromPath: "404"))
}
// MARK: Image
func test_viewModelInstance_imageProperty_returnsPropertyOrNil() throws {
let file = try RiveFile(testfileName: "data_binding_image_test")
let instance = file.viewModelNamed("vm")!.createDefaultInstance()!
XCTAssertNotNil(instance.imageProperty(fromPath: "img"))
XCTAssertNil(instance.imageProperty(fromPath: "404"))
}
func test_viewModelInstance_imageProperty_canSetValue() throws {
let file = try RiveFile(testfileName: "data_binding_image_test")
let instance = file.viewModelNamed("vm")!.createDefaultInstance()!
let property = instance.imageProperty(fromPath: "img")!
let bundle = Bundle(for: type(of: self))
let fileURL = bundle.url(forResource: "1x1_jpg", withExtension: "jpg")!
let data = try Data(contentsOf: fileURL)
let renderImage = RiveRenderImage(data: data)!
property.setValue(renderImage)
XCTAssertTrue(property.hasChanged)
}
// MARK: Binding
func test_binding_artboard_stringProperty_updatesTextRun() throws {

View File

@@ -0,0 +1,58 @@
//
// RiveRenderImageTests.swift
// RiveRuntimeTests
//
// Created by David Skuza on 5/13/25.
// Copyright © 2025 Rive. All rights reserved.
//
import XCTest
import RiveRuntime
class RiveRenderImageTests: XCTestCase {
func test_imageFromData_withEmptyData_returnsNil() {
// Data comprised of 0 bytes
XCTAssertNil(RiveRenderImage(data: Data()))
}
func test_imageFromData_withIncompatibleData_returnsNil() throws {
// Data comprised of 8 bytes that do _not_ create an image
XCTAssertNil(RiveRenderImage(data: Data([1, 2, 3, 4, 5, 6, 7, 8])))
}
func test_imageFromData_withCompatibleData_returnsImage() throws {
let bundle = Bundle(for: type(of: self))
// We know JPG to be a valid Rive image asset format
var fileURL = bundle.url(forResource: "1x1_jpg", withExtension: "jpg")!
var data = try Data(contentsOf: fileURL)
XCTAssertNotNil(RiveRenderImage(data: data))
// We know PNG to be a valid Rive image asset format
fileURL = bundle.url(forResource: "1x1_png", withExtension: "png")!
data = try Data(contentsOf: fileURL)
XCTAssertNotNil(RiveRenderImage(data: data))
}
// MARK: - Extensions
func test_imageFromUIImage_withIncorrectFormat_returnsNil() throws {
XCTAssertNil(RiveRenderImage(image: UIImage(), format: .png))
XCTAssertNil(RiveRenderImage(image: UIImage(), format: .jpeg(compressionQuality: 80)))
}
func test_imageFromUIImage_withCorrectFormat_returnsImage() throws {
let bundle = Bundle(for: type(of: self))
// We know JPG to be a valid Rive image asset format
var fileURL = bundle.url(forResource: "1x1_jpg", withExtension: "jpg")!
var data = try Data(contentsOf: fileURL)
var image = UIImage(data: data)!
XCTAssertNotNil(RiveRenderImage(image: image, format: .jpeg(compressionQuality: 80)))
fileURL = bundle.url(forResource: "1x1_png", withExtension: "png")!
data = try Data(contentsOf: fileURL)
image = UIImage(data: data)!
XCTAssertNotNil(RiveRenderImage(image: image, format: .png))
}
}