feat(apple): add support for data binding artboards (#10131) b4ce2d25fd

Co-authored-by: David Skuza <david@rive.app>
This commit is contained in:
dskuza
2025-07-15 16:02:52 +00:00
parent 2b6ee8f349
commit cd07875eb7
16 changed files with 339 additions and 7 deletions

View File

@@ -55,19 +55,19 @@ jobs:
run: |
./scripts/build.sh xrsimulator release
- name: Test visionOS runtime
run: ./scripts/test.sh xrsimulator
# - name: Test visionOS runtime
# run: ./scripts/test.sh xrsimulator
- name: Build for tvOS Simulator (using the cache, we should make an archive of course)
run: |
./scripts/build.sh appletvsimulator release
- name: Test tvOS runtime
run: ./scripts/test.sh appletvsimulator
# - name: Test tvOS runtime
# run: ./scripts/test.sh appletvsimulator
- name: Build for Mac Catalyst (using the cache, we should make an archive of course)
run: |
./scripts/build.sh maccatalyst release
- name: Test Mac Catalyst runtime
run: ./scripts/test.sh maccatalyst
# - name: Test Mac Catalyst runtime
# run: ./scripts/test.sh maccatalyst

View File

@@ -1 +1 @@
8b7aa847042cbb17ee62d126d3b488dc7a4f8614
b4ce2d25fd4bc81f53c9e5148d57fbc542e763e3

View File

@@ -114,6 +114,8 @@
F2610DD82CA5B6570090D50B /* RiveLogger+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */; };
F2610DDA2CA5B84B0090D50B /* RiveLogger+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */; };
F2610DE12CA5FBE30090D50B /* RiveLogger+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */; };
F268DE412E25D42300BAB7CF /* RiveBindableArtboard.h in Headers */ = {isa = PBXBuildFile; fileRef = F268DE3F2E25D42300BAB7CF /* RiveBindableArtboard.h */; settings = {ATTRIBUTES = (Public, ); }; };
F268DE422E25D42300BAB7CF /* RiveBindableArtboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = F268DE402E25D42300BAB7CF /* RiveBindableArtboard.mm */; };
F27B61582D35C00E003C0345 /* RiveDataBindingViewModelInstance.h in Headers */ = {isa = PBXBuildFile; fileRef = F27B61562D35C00E003C0345 /* RiveDataBindingViewModelInstance.h */; settings = {ATTRIBUTES = (Public, ); }; };
F27B61592D35C00E003C0345 /* RiveDataBindingViewModelInstance.mm in Sources */ = {isa = PBXBuildFile; fileRef = F27B61572D35C00E003C0345 /* RiveDataBindingViewModelInstance.mm */; };
F27B615C2D35C75A003C0345 /* RiveDataBindingViewModelInstanceProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = F27B615A2D35C75A003C0345 /* RiveDataBindingViewModelInstanceProperty.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -121,6 +123,7 @@
F28DE4532C5002D900F3C379 /* RiveModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28DE4522C5002D900F3C379 /* RiveModelTests.swift */; };
F2B29EA22D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.h in Headers */ = {isa = PBXBuildFile; fileRef = F2B29EA02D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.h */; settings = {ATTRIBUTES = (Public, ); }; };
F2B29EA32D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.m in Sources */ = {isa = PBXBuildFile; fileRef = F2B29EA12D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.m */; };
F2BBD3522E1DB9E10077FE94 /* data_binding_artboard.riv in Resources */ = {isa = PBXBuildFile; fileRef = F2BBD3512E1DB9E10077FE94 /* data_binding_artboard.riv */; };
F2BE72112E05DB8E00B66C78 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BE72102E05DB8E00B66C78 /* ViewTests.swift */; };
F2C003E82C933D2300339E67 /* RiveMetalDrawableView.h in Headers */ = {isa = PBXBuildFile; fileRef = F2C003E72C933D2300339E67 /* RiveMetalDrawableView.h */; settings = {ATTRIBUTES = (Public, ); }; };
F2C07B1B2DA9854F00DC8C84 /* WeakContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = F2C07B192DA9854F00DC8C84 /* WeakContainer.h */; };
@@ -257,6 +260,8 @@
F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+Model.swift"; sourceTree = "<group>"; };
F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+File.swift"; sourceTree = "<group>"; };
F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+View.swift"; sourceTree = "<group>"; };
F268DE3F2E25D42300BAB7CF /* RiveBindableArtboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveBindableArtboard.h; sourceTree = "<group>"; };
F268DE402E25D42300BAB7CF /* RiveBindableArtboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveBindableArtboard.mm; sourceTree = "<group>"; };
F27B61562D35C00E003C0345 /* RiveDataBindingViewModelInstance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModelInstance.h; sourceTree = "<group>"; };
F27B61572D35C00E003C0345 /* RiveDataBindingViewModelInstance.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveDataBindingViewModelInstance.mm; sourceTree = "<group>"; };
F27B615A2D35C75A003C0345 /* RiveDataBindingViewModelInstanceProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModelInstanceProperty.h; sourceTree = "<group>"; };
@@ -264,6 +269,7 @@
F28DE4522C5002D900F3C379 /* RiveModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveModelTests.swift; sourceTree = "<group>"; };
F2B29EA02D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModelInstancePropertyData.h; sourceTree = "<group>"; };
F2B29EA12D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveDataBindingViewModelInstancePropertyData.m; sourceTree = "<group>"; };
F2BBD3512E1DB9E10077FE94 /* data_binding_artboard.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = data_binding_artboard.riv; sourceTree = "<group>"; };
F2BD96D52DDCC7A200E7F49A /* Catalyst.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Catalyst.xcconfig; sourceTree = "<group>"; };
F2BD96D62DDCC7A200E7F49A /* macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = "<group>"; };
F2BE72102E05DB8E00B66C78 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = "<group>"; };
@@ -329,6 +335,7 @@
043025FD2AFA8FCF00320F2E /* RiveFileAsset.h */,
043026012AFB9FCD00320F2E /* RiveFactory.h */,
F2C003E72C933D2300339E67 /* RiveMetalDrawableView.h */,
F268DE3F2E25D42300BAB7CF /* RiveBindableArtboard.h */,
);
path = include;
sourceTree = "<group>";
@@ -364,6 +371,7 @@
04BE53F82649403500427B39 /* shapes.riv */,
04BE53FD2649403600427B39 /* state_machine_configurations.riv */,
04BE53FB2649403600427B39 /* what_a_state.riv */,
F2BBD3512E1DB9E10077FE94 /* data_binding_artboard.riv */,
);
path = Assets;
sourceTree = "<group>";
@@ -419,6 +427,7 @@
043025FB2AFA862E00320F2E /* FileAssetLoaderAdapter.mm */,
043025FF2AFA915B00320F2E /* RiveFileAsset.mm */,
043026032AFBA04100320F2E /* RiveFactory.mm */,
F268DE402E25D42300BAB7CF /* RiveBindableArtboard.mm */,
);
path = Renderer;
sourceTree = "<group>";
@@ -592,6 +601,7 @@
F259E5882D35B91700B78FEF /* RiveDataBindingViewModel.h in Headers */,
F27B61582D35C00E003C0345 /* RiveDataBindingViewModelInstance.h in Headers */,
F27B615C2D35C75A003C0345 /* RiveDataBindingViewModelInstanceProperty.h in Headers */,
F268DE412E25D42300BAB7CF /* RiveBindableArtboard.h in Headers */,
F2B29EA22D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.h in Headers */,
F2FD94052CC9492B00C1FC85 /* RiveFont.h in Headers */,
04BE5436264D2A7500427B39 /* RivePrivateHeaders.h in Headers */,
@@ -712,6 +722,7 @@
E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */,
04BE54062649403600427B39 /* off_road_car_blog.riv in Resources */,
04BE540C2649403600427B39 /* multipleartboards.riv in Resources */,
F2BBD3522E1DB9E10077FE94 /* data_binding_artboard.riv in Resources */,
04BE540D2649403600427B39 /* junk.riv in Resources */,
04BE54092649403600427B39 /* state_machine_configurations.riv in Resources */,
04BE54072649403600427B39 /* what_a_state.riv in Resources */,
@@ -757,6 +768,7 @@
C3E2B580282F242400A8651B /* RiveStateMachineInstance+Extensions.swift in Sources */,
F2B29EA32D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.m in Sources */,
046FB7F5264EAA60000129B1 /* RiveSMIInput.mm in Sources */,
F268DE422E25D42300BAB7CF /* RiveBindableArtboard.mm in Sources */,
F2C07B1C2DA9854F00DC8C84 /* WeakContainer.m in Sources */,
043026002AFA915B00320F2E /* RiveFileAsset.mm in Sources */,
F2FD94042CC9492B00C1FC85 /* RiveFont.m in Sources */,
@@ -1217,6 +1229,8 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3,7";
TVOS_DEPLOYMENT_TARGET = 16.0;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Debug;
};
@@ -1241,6 +1255,8 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3,7";
TVOS_DEPLOYMENT_TARGET = 16.0;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Release;
};
@@ -1463,6 +1479,8 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3,7";
TVOS_DEPLOYMENT_TARGET = 16.0;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = "Debug (Catalyst)";
};
@@ -1682,6 +1700,8 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3,7";
TVOS_DEPLOYMENT_TARGET = 16.0;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = "Release (Catalyst)";
};

View File

@@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN
@class RiveDataBindingViewModelInstanceTriggerProperty;
@class RiveDataBindingViewModelInstanceImageProperty;
@class RiveDataBindingViewModelInstanceListProperty;
@class RiveDataBindingViewModelInstanceArtboardProperty;
@class RiveDataBindingViewModelInstancePropertyData;
/// An object that represents an instance of a view model, used to update
@@ -187,6 +188,19 @@ NS_SWIFT_NAME(RiveDataBindingViewModel.Instance)
- (nullable RiveDataBindingViewModelInstanceListProperty*)listPropertyFromPath:
(NSString*)path;
/// Gets an artboard 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 artboard property.
///
/// - Returns: The property if it exists at the supplied path, otherwise nil.
- (nullable RiveDataBindingViewModelInstanceArtboardProperty*)
artboardPropertyFromPath:(NSString*)path;
/// Calls all registered property listeners for the properties of the view model
/// instance.
- (void)updateListeners;

View File

@@ -382,6 +382,41 @@
return listProperty;
}
- (RiveDataBindingViewModelInstanceArtboardProperty*)artboardPropertyFromPath:
(NSString*)path
{
RiveDataBindingViewModelInstanceArtboardProperty* cached;
if ((cached = [self
cachedPropertyFromPath:path
asClass:
[RiveDataBindingViewModelInstanceArtboardProperty
class]]))
{
return cached;
}
auto artboard = _instance->propertyArtboard(std::string([path UTF8String]));
if (artboard == nullptr)
{
[RiveLogger logWithViewModelInstance:self
artboardPropertyAtPath:path
found:NO];
return nil;
}
[RiveLogger logWithViewModelInstance:self
artboardPropertyAtPath:path
found:YES];
RiveDataBindingViewModelInstanceArtboardProperty* artboardProperty =
[[RiveDataBindingViewModelInstanceArtboardProperty alloc]
initWithArtboard:artboard];
artboardProperty.valueDelegate = self;
[self cacheProperty:artboardProperty withPath:path];
return artboardProperty;
}
- (void)updateListeners
{
[_properties enumerateKeysAndObjectsUsingBlock:^(

View File

@@ -365,4 +365,35 @@ NS_SWIFT_NAME(RiveDataBindingViewModelInstance.ListProperty)
@end
#pragma mark - Artboard
@class RiveBindableArtboard;
typedef void (^RiveDataBindingViewModelInstanceArtboardPropertyListener)(void)
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceArtboardProperty.Listener);
/// An object that represents a trigger property of a view model instance.
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.ArtboardProperty)
@interface RiveDataBindingViewModelInstanceArtboardProperty
: RiveDataBindingViewModelInstanceProperty
- (instancetype)init NS_UNAVAILABLE;
- (void)setValue:(RiveBindableArtboard*)artboard;
/// 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:
(RiveDataBindingViewModelInstanceArtboardPropertyListener)listener;
@end
NS_ASSUME_NONNULL_END

View File

@@ -692,3 +692,43 @@
}
@end
#pragma mark - Artboard
@implementation RiveDataBindingViewModelInstanceArtboardProperty
{
rive::ViewModelInstanceArtboardRuntime* _artboard;
}
- (instancetype)initWithArtboard:
(rive::ViewModelInstanceArtboardRuntime*)artboard
{
if (self = [super initWithValue:artboard])
{
_artboard = artboard;
}
return self;
}
- (void)setValue:(RiveBindableArtboard*)artboard
{
_artboard->value([artboard artboardInstance]);
[RiveLogger logPropertyUpdated:self value:[artboard name]];
}
- (NSUUID*)addListener:
(RiveDataBindingViewModelInstanceTriggerPropertyListener)listener
{
return [super addListener:listener];
}
- (void)handleListeners
{
for (RiveDataBindingViewModelInstanceImagePropertyListener listener in self
.listeners.allValues)
{
listener();
}
}
@end

View File

@@ -29,6 +29,7 @@ enum RiveLoggerDataBindingEvent {
case triggerProperty(String, Bool)
case imageProperty(String, Bool)
case listProperty(String, Bool)
case artboardProperty(String, Bool)
}
enum Property: DataBindingEvent {
case propertyUpdated(String, String)
@@ -118,6 +119,11 @@ extension RiveLogger {
let message = message(instance: instance, for: "list", path: path, found: found)
dataBinding.debug("\(message)")
}
case .artboardProperty(let path, let found):
_log(event: event, level: .debug) {
let message = message(instance: instance, for: "artboard", path: path, found: found)
dataBinding.debug("\(message)")
}
}
}
@@ -194,6 +200,10 @@ extension RiveLogger {
Self.log(viewModelInstance: instance, event: .listProperty(path, found))
}
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, artboardPropertyAtPath path: String, found: Bool) {
Self.log(viewModelInstance: instance, event: .artboardProperty(path, found))
}
// MARK: - Properties
@objc(logPropertyUpdated:value:) static func log(propertyUpdated property: RiveDataBindingViewModel.Instance.Property, value: String) {

View File

@@ -0,0 +1,39 @@
//
// RiveBindableArtboard.m
// RiveRuntime
//
// Created by David Skuza on 7/14/25.
// Copyright © 2025 Rive. All rights reserved.
//
#import <Rive.h>
#import <RivePrivateHeaders.h>
@implementation RiveBindableArtboard
{
std::unique_ptr<rive::ArtboardInstance> _artboardInstance;
}
- (instancetype)initWithArtboard:
(std::unique_ptr<rive::ArtboardInstance>)artboard
{
if (self = [super init])
{
_artboardInstance = std::move(artboard);
}
return self;
}
- (rive::ArtboardInstance*)artboardInstance
{
return _artboardInstance.get();
}
- (NSString*)name
{
auto name = _artboardInstance->name();
return [NSString stringWithCString:name.c_str()
encoding:[NSString defaultCStringEncoding]];
}
@end

View File

@@ -538,6 +538,52 @@
return [[RiveDataBindingViewModel alloc] initWithViewModel:viewModel];
}
- (RiveArtboard*)bindableArtboardAtIndex:(NSInteger)index
error:(NSError* __autoreleasing _Nullable*)
error
{
auto artboard = riveFile->artboardAt(index);
if (artboard == nullptr)
{
NSString* message = [NSString
stringWithFormat:@"No Artboard Found at index %ld.", (long)index];
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : message,
@"name" : @"NoArtboardFound"
}];
return nil;
}
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
}
- (RiveArtboard*)bindableArtboardWithName:(NSString*)name
error:(NSError* __autoreleasing _Nullable*)
error
{
std::string stdName = std::string([name UTF8String]);
auto artboard = riveFile->artboardNamed(stdName);
if (artboard == nullptr)
{
NSString* message = [NSString
stringWithFormat:@"No Artboard Found with name %@.", name];
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : message,
@"name" : @"NoArtboardFound"
}];
return nil;
}
else
{
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
}
}
/// Clean up rive file
- (void)dealloc
{

View File

@@ -14,6 +14,7 @@
#import <RiveRuntime/RiveFile.h>
#import <RiveRuntime/RiveArtboard.h>
#import <RiveRuntime/RiveBindableArtboard.h>
#import <RiveRuntime/RiveSMIInput.h>
#import <RiveRuntime/RiveLinearAnimationInstance.h>
#import <RiveRuntime/RiveStateMachineInstance.h>

View File

@@ -0,0 +1,19 @@
//
// RiveBindableArtboard.h
// RiveRuntime
//
// Created by David Skuza on 7/14/25.
// Copyright © 2025 Rive. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RiveBindableArtboard : NSObject
@property(nonatomic, readonly) NSString* name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@class RiveFileAsset;
@class RiveFactory;
@class RiveDataBindingViewModel;
@class RiveBindableArtboard;
typedef bool (^LoadAsset)(RiveFileAsset* asset,
NSData* data,
RiveFactory* factory);
@@ -152,6 +153,36 @@ typedef bool (^LoadAsset)(RiveFileAsset* asset,
- (nullable RiveDataBindingViewModel*)defaultViewModelForArtboard:
(RiveArtboard*)artboard;
/// Returns a bindable artboard from the file by its index.
///
/// A bindable artboard is an artboard that can be used with data binding
/// features. The index of an artboard starts at 0, where 0 is the first
/// artboard in the file.
///
/// - Parameter index: The index of the artboard to retrieve.
/// - Parameter error: A pointer to an NSError object. If an error occurs, this
/// pointer will contain an error object describing the problem.
///
/// - Returns: A bindable artboard if one exists at the specified index,
/// otherwise nil.
- (nullable RiveBindableArtboard*)bindableArtboardAtIndex:(NSInteger)index
error:(NSError**)error;
/// Returns a bindable artboard from the file by its name.
///
/// A bindable artboard is an artboard that can be used with data binding
/// features. The name must match exactly with an artboard name in the Rive
/// file.
///
/// - Parameter name: The name of the artboard to retrieve. Must not be nil.
/// - Parameter error: A pointer to an NSError object. If an error occurs, this
/// pointer will contain an error object describing the problem.
///
/// - Returns: A bindable artboard if one exists with the specified name,
/// otherwise nil.
- (nullable RiveBindableArtboard*)bindableArtboardWithName:(NSString*)name
error:(NSError**)error;
@end
/*

View File

@@ -240,4 +240,14 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithList:(rive::ViewModelInstanceListRuntime*)list;
@end
@interface RiveDataBindingViewModelInstanceArtboardProperty ()
- (instancetype)initWithArtboard:(rive::ViewModelInstanceArtboardRuntime*)list;
@end
@interface RiveBindableArtboard ()
- (rive::ArtboardInstance*)artboardInstance;
- (instancetype)initWithArtboard:
(std::unique_ptr<rive::ArtboardInstance>)artboard;
@end
NS_ASSUME_NONNULL_END

Binary file not shown.

View File

@@ -525,6 +525,42 @@ class DataBindingTests: XCTestCase {
XCTAssertEqual(list.count, 2)
}
// MARK: Artboard
func test_viewModelInstance_artboardInstance_canSetValue() throws {
let file = try RiveFile(testfileName: "data_binding_artboard")
let instance = file.viewModelNamed("Default")!.createInstance()!
let artboardProperty = instance.artboardProperty(fromPath: "Artboard")
XCTAssertNotNil(artboardProperty)
XCTAssertNil(instance.artboardProperty(fromPath: "404"))
let red = try file.bindableArtboard(withName: "Red")
let green = try file.bindableArtboard(withName: "Green")
let blue = try file.bindableArtboard(withName: "Blue")
let expectation = expectation(description: "artboard did set value")
expectation.expectedFulfillmentCount = 3
artboardProperty?.addListener {
expectation.fulfill()
}
artboardProperty?.setValue(red)
instance.updateListeners()
artboardProperty?.clearChanges()
artboardProperty?.setValue(green)
instance.updateListeners()
artboardProperty?.clearChanges()
artboardProperty?.setValue(blue)
instance.updateListeners()
artboardProperty?.clearChanges()
wait(for: [expectation], timeout: 1)
}
// MARK: Binding
func test_binding_artboard_stringProperty_updatesTextRun() throws {