From cd07875eb72a9cca1a5c37c579c6a3429d32afdc Mon Sep 17 00:00:00 2001 From: dskuza Date: Tue, 15 Jul 2025 16:02:52 +0000 Subject: [PATCH] feat(apple): add support for data binding artboards (#10131) b4ce2d25fd Co-authored-by: David Skuza --- .github/workflows/tests.yml | 12 ++--- .rive_head | 2 +- RiveRuntime.xcodeproj/project.pbxproj | 20 ++++++++ .../RiveDataBindingViewModelInstance.h | 14 ++++++ .../RiveDataBindingViewModelInstance.mm | 35 +++++++++++++ ...RiveDataBindingViewModelInstanceProperty.h | 31 ++++++++++++ ...iveDataBindingViewModelInstanceProperty.mm | 40 +++++++++++++++ Source/Logging/RiveLogger+DataBinding.swift | 10 ++++ Source/Renderer/RiveBindableArtboard.mm | 39 +++++++++++++++ Source/Renderer/RiveFile.mm | 46 ++++++++++++++++++ Source/Renderer/include/Rive.h | 1 + .../Renderer/include/RiveBindableArtboard.h | 19 ++++++++ Source/Renderer/include/RiveFile.h | 31 ++++++++++++ Source/Renderer/include/RivePrivateHeaders.h | 10 ++++ Tests/Assets/data_binding_artboard.riv | Bin 0 -> 685 bytes Tests/DataBindingTests.swift | 36 ++++++++++++++ 16 files changed, 339 insertions(+), 7 deletions(-) create mode 100644 Source/Renderer/RiveBindableArtboard.mm create mode 100644 Source/Renderer/include/RiveBindableArtboard.h create mode 100644 Tests/Assets/data_binding_artboard.riv diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 53ac9af..9dafd01 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 diff --git a/.rive_head b/.rive_head index be22f76..e33fb41 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -8b7aa847042cbb17ee62d126d3b488dc7a4f8614 +b4ce2d25fd4bc81f53c9e5148d57fbc542e763e3 diff --git a/RiveRuntime.xcodeproj/project.pbxproj b/RiveRuntime.xcodeproj/project.pbxproj index 5221dd5..c6f94d0 100644 --- a/RiveRuntime.xcodeproj/project.pbxproj +++ b/RiveRuntime.xcodeproj/project.pbxproj @@ -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 = ""; }; F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+File.swift"; sourceTree = ""; }; F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+View.swift"; sourceTree = ""; }; + F268DE3F2E25D42300BAB7CF /* RiveBindableArtboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveBindableArtboard.h; sourceTree = ""; }; + F268DE402E25D42300BAB7CF /* RiveBindableArtboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveBindableArtboard.mm; sourceTree = ""; }; F27B61562D35C00E003C0345 /* RiveDataBindingViewModelInstance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModelInstance.h; sourceTree = ""; }; F27B61572D35C00E003C0345 /* RiveDataBindingViewModelInstance.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveDataBindingViewModelInstance.mm; sourceTree = ""; }; F27B615A2D35C75A003C0345 /* RiveDataBindingViewModelInstanceProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModelInstanceProperty.h; sourceTree = ""; }; @@ -264,6 +269,7 @@ F28DE4522C5002D900F3C379 /* RiveModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveModelTests.swift; sourceTree = ""; }; F2B29EA02D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveDataBindingViewModelInstancePropertyData.h; sourceTree = ""; }; F2B29EA12D52B5EB00CB30ED /* RiveDataBindingViewModelInstancePropertyData.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveDataBindingViewModelInstancePropertyData.m; sourceTree = ""; }; + F2BBD3512E1DB9E10077FE94 /* data_binding_artboard.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = data_binding_artboard.riv; sourceTree = ""; }; F2BD96D52DDCC7A200E7F49A /* Catalyst.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Catalyst.xcconfig; sourceTree = ""; }; F2BD96D62DDCC7A200E7F49A /* macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; F2BE72102E05DB8E00B66C78 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = ""; }; @@ -329,6 +335,7 @@ 043025FD2AFA8FCF00320F2E /* RiveFileAsset.h */, 043026012AFB9FCD00320F2E /* RiveFactory.h */, F2C003E72C933D2300339E67 /* RiveMetalDrawableView.h */, + F268DE3F2E25D42300BAB7CF /* RiveBindableArtboard.h */, ); path = include; sourceTree = ""; @@ -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 = ""; @@ -419,6 +427,7 @@ 043025FB2AFA862E00320F2E /* FileAssetLoaderAdapter.mm */, 043025FF2AFA915B00320F2E /* RiveFileAsset.mm */, 043026032AFBA04100320F2E /* RiveFactory.mm */, + F268DE402E25D42300BAB7CF /* RiveBindableArtboard.mm */, ); path = Renderer; sourceTree = ""; @@ -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)"; }; diff --git a/Source/DataBinding/RiveDataBindingViewModelInstance.h b/Source/DataBinding/RiveDataBindingViewModelInstance.h index a941bbc..dab3c4c 100644 --- a/Source/DataBinding/RiveDataBindingViewModelInstance.h +++ b/Source/DataBinding/RiveDataBindingViewModelInstance.h @@ -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; diff --git a/Source/DataBinding/RiveDataBindingViewModelInstance.mm b/Source/DataBinding/RiveDataBindingViewModelInstance.mm index e49a11b..8d6b735 100644 --- a/Source/DataBinding/RiveDataBindingViewModelInstance.mm +++ b/Source/DataBinding/RiveDataBindingViewModelInstance.mm @@ -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:^( diff --git a/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.h b/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.h index a127ed8..fa2c4b1 100644 --- a/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.h +++ b/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.h @@ -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 diff --git a/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.mm b/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.mm index e9e1e1f..fb9d73c 100644 --- a/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.mm +++ b/Source/DataBinding/RiveDataBindingViewModelInstanceProperty.mm @@ -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 diff --git a/Source/Logging/RiveLogger+DataBinding.swift b/Source/Logging/RiveLogger+DataBinding.swift index 0c5d72b..84bf886 100644 --- a/Source/Logging/RiveLogger+DataBinding.swift +++ b/Source/Logging/RiveLogger+DataBinding.swift @@ -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) { diff --git a/Source/Renderer/RiveBindableArtboard.mm b/Source/Renderer/RiveBindableArtboard.mm new file mode 100644 index 0000000..9314be4 --- /dev/null +++ b/Source/Renderer/RiveBindableArtboard.mm @@ -0,0 +1,39 @@ +// +// RiveBindableArtboard.m +// RiveRuntime +// +// Created by David Skuza on 7/14/25. +// Copyright © 2025 Rive. All rights reserved. +// + +#import +#import + +@implementation RiveBindableArtboard +{ + std::unique_ptr _artboardInstance; +} + +- (instancetype)initWithArtboard: + (std::unique_ptr)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 diff --git a/Source/Renderer/RiveFile.mm b/Source/Renderer/RiveFile.mm index 230812d..c12a750 100644 --- a/Source/Renderer/RiveFile.mm +++ b/Source/Renderer/RiveFile.mm @@ -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 { diff --git a/Source/Renderer/include/Rive.h b/Source/Renderer/include/Rive.h index ca4e72c..beaa1d1 100644 --- a/Source/Renderer/include/Rive.h +++ b/Source/Renderer/include/Rive.h @@ -14,6 +14,7 @@ #import #import +#import #import #import #import diff --git a/Source/Renderer/include/RiveBindableArtboard.h b/Source/Renderer/include/RiveBindableArtboard.h new file mode 100644 index 0000000..c04497e --- /dev/null +++ b/Source/Renderer/include/RiveBindableArtboard.h @@ -0,0 +1,19 @@ +// +// RiveBindableArtboard.h +// RiveRuntime +// +// Created by David Skuza on 7/14/25. +// Copyright © 2025 Rive. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RiveBindableArtboard : NSObject + +@property(nonatomic, readonly) NSString* name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Renderer/include/RiveFile.h b/Source/Renderer/include/RiveFile.h index 4d5fed4..20b0d48 100644 --- a/Source/Renderer/include/RiveFile.h +++ b/Source/Renderer/include/RiveFile.h @@ -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 /* diff --git a/Source/Renderer/include/RivePrivateHeaders.h b/Source/Renderer/include/RivePrivateHeaders.h index 000b4e8..8a58e5c 100644 --- a/Source/Renderer/include/RivePrivateHeaders.h +++ b/Source/Renderer/include/RivePrivateHeaders.h @@ -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)artboard; +@end + NS_ASSUME_NONNULL_END diff --git a/Tests/Assets/data_binding_artboard.riv b/Tests/Assets/data_binding_artboard.riv new file mode 100644 index 0000000000000000000000000000000000000000..2906473af38f96693ff534fcde0fbf2983c29e32 GIT binary patch literal 685 zcmcK1y-EW?6b0aOGiON|6A?k1C{{MB88Hwegrv|%uvcHeHkA)x z6A6S=g7y|MzXVKav(D~{NU(^4Q(T6bVdlGMvRnHbVLU#`8*B2)9-ADh5!>vu!v+U3 zV2gEjStYz7ehrE@U&NiNGMv%JPNh)BvpE+@mCA=_PALbe3Btg8I^va~3JnBx;@gP> z3)8?wdd4fbR}DT$lOc2Qt+LRfAxWFkp>!!dN}mQ~tTLit&SK2AZ5Q(vuoR!#%b)5( zzNn)%mgBR#Wv8s8JI>Kbu(Wb1;ce$!m(2DWQa7TIL;|Z