mirror of
https://github.com/rive-app/rive-ios.git
synced 2026-01-18 17:11:28 +01:00
feat(ios): add support for data binding images (#9664) 8f30ede7be
Co-authored-by: David Skuza <david@rive.app>
This commit is contained in:
@@ -1 +1 @@
|
||||
052a4984ef8526de04a111ea4e7b725271e53d38
|
||||
8f30ede7be2d34dd642dc6f53575f1ab9bd3525e
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:^(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
26
Source/Extensions/RiveRenderImage+Extensions.swift
Normal file
26
Source/Extensions/RiveRenderImage+Extensions.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RiveRenderImage : NSObject
|
||||
- (nullable instancetype)initWithData:(NSData*)data;
|
||||
@end
|
||||
|
||||
@interface RiveAudio : NSObject
|
||||
|
||||
@@ -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
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
BIN
Tests/Assets/1x1_png.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 B |
BIN
Tests/Assets/data_binding_image_test.riv
Normal file
BIN
Tests/Assets/data_binding_image_test.riv
Normal file
Binary file not shown.
@@ -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 {
|
||||
|
||||
58
Tests/RiveRenderImageTests.swift
Normal file
58
Tests/RiveRenderImageTests.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user