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
Adds support for data binding to the iOS runtime.
## Changes
### RiveFile
Updated to match c++ runtime support for getting view models by index, name, and default for an artboard.
### RiveArtboard
Updated to match c++ runtime support for binding a view model instance.
### RiveStateMachine
Updated to match c++ runtime support for binding a view model instance. Additionally, this holds a strong reference to the view model instance that is currently bound (which aids in knowing which state machine should have its property listeners called after advance).
### RiveModel
Adds support for enabling / disabling autoBind functionality. This has to be done _after_ initialization, since Swift default arguments don't bridge to ObjC, and I didn't want to add n number of new initializers. When enabled, a callback will be called with the bound instance.
## New Types
Currently, type names are similar to those found within the c++ runtime. They're kind of ugly when it comes to ObjC, but the Swift names are cleaned up (e.g `RiveViewModelRuntime.Instance` instead of `RiveViewModelInstanceRuntime`).
### RiveDataBindingViewModel
The bridging type between the c++ runtime equivalent (`rive::ViewModelRuntime`) and ObjC.
### RiveDataBindingViewModelInstance
Swift: `RiveDataBindingViewModel.Instance`
The bridging type between the c++ runtime equivalent (`rive::ViewModelInstanceRuntime`) and ObjC.
### RiveDataBindingViewModelInstanceProperty
Swift: `RiveDataBindingViewModel.Instance.Property`
The superclass bridging type between the c++ runtime equivalent (`rive::ViewModelInstanceValueRuntime`) and ObjC.
### Subclasses
- Strings: `RiveDataBindingViewModelInstanceStringProperty`
- Swift: `RiveDataBindingViewModel.Instance.StringProperty`
- Numbers: `RiveDataBindingViewModelInstanceNumberProperty`
- Swift: `RiveDataBindingViewModel.Instance.NumberProperty`
- Boolean: `RiveDataBindingViewModelInstanceBooleanProperty`
- Swift: `RiveDataBindingViewModel.Instance.BooleanProperty`
- Color: `RiveDataBindingViewModelInstanceColorProperty`
- Swift: `RiveDataBindingViewModel.Instance.ColorProperty`
- Enum: `RiveDataBindingViewModelInstanceEnumProperty`
- Swift: `RiveDataBindingViewModel.Instance.EnumProperty`
- Trigger: `RiveDataBindingViewModelInstanceTriggerProperty`
- Swift: `RiveDataBindingViewModel.Instance.TriggerProperty`
### Observability
KVO has (temporarily) been disabled, "forcing" observability to be done through the explicit `addListener` and `removeListener` functions of each property type. `removeListener` exists on the superclass, however the matching `addListener` functions exist on each property type, primarily due to the fact that there is no "pretty" way of handing these functions as generics (where only the value type of the property differs for each callback) other than utilizing `id`. Listeners exist within the context of a property; however, an instance will use its (cached) properties to request that it calls its listeners.
## Details
### Caching properties
When a property getter is called on a view model instance, a cache is first checked for the property, otherwise a new one is returned and cached. This helps with ensuring we are using the same pointer under-the-hood. This isn't strictly necessary (per testing) but does allow for some niceties, such as not having to explicitly maintain a strong reference to a property if you want to just observe: `instance.triggerProperty(from: "...").addListener { ... }`.
Properties are cached for the first component when parsing the path for the property. In the instance that a path with > 1 component is provided to a property (e.g `instance.triggerProperty(from: 'nested/trigger")`, then the appropriate nested view models are created, and the property is associated with the correct view model (e.g above, the view model `nested will be cached, and the trigger property will be cached within that view model).
### Caching nested view models
Similar to caching properties, when a (nested) view model getter is called on a view model instance, a cache is first checked for the view model, otherwise a new one is returned and cached. This helps ensure that when (re)binding an instance to a (new) state machine or artboard, that all properties within that view model still have its listeners attached, regardless of how nested a path goes. This will likely help with implementing replacing instance functionality in v2.
## Testing
Unit tests have been added for data binding, attempting to capture high-level expectations rather than totally verifying the c++ runtime expectations. This includes things like: all getters returning object-or-nil, listeners being called with the correct values, autoBinding, property and view model caching, etc. The `.riv` file for unit tests is the same one that is used within the Example app.
## Example
A new `.riv` file has been added that shows basic usage of each property type (including observability). The same `.riv` file is used in the unit tests.
Diffs=
b2f1db73d7 feat(ios): add support for data binding (#9227)
Co-authored-by: David Skuza <david@rive.app>
This commit is contained in:
94
Source/DataBinding/RiveDataBindingViewModel.h
Normal file
94
Source/DataBinding/RiveDataBindingViewModel.h
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// RiveDataBindingViewModel.h
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 1/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class RiveDataBindingViewModelInstance;
|
||||
@class RiveDataBindingViewModelInstancePropertyData;
|
||||
|
||||
/// An object that represents a View Model of a Rive file.
|
||||
@interface RiveDataBindingViewModel : NSObject
|
||||
|
||||
/// The name of the view model.
|
||||
@property(nonatomic, readonly) NSString* name;
|
||||
|
||||
/// The number of instances in the view model.
|
||||
@property(nonatomic, readonly) NSUInteger instanceCount;
|
||||
|
||||
/// An array of names of all instances in the view model.
|
||||
@property(nonatomic, readonly) NSArray<NSString*>* instanceNames;
|
||||
|
||||
/// The number of all properties in the view model.
|
||||
@property(nonatomic, readonly) NSUInteger propertyCount;
|
||||
|
||||
/// An array of property data of all properties in the view model.
|
||||
@property(nonatomic, readonly)
|
||||
NSArray<RiveDataBindingViewModelInstancePropertyData*>* properties;
|
||||
|
||||
/// Creates a new instance to bind from a given index.
|
||||
///
|
||||
/// The index of an instance starts at 0, where 0 is
|
||||
/// the first instance that appears in the "Data Bind" panel's instances
|
||||
/// dropdown.
|
||||
///
|
||||
/// - Note: A strong reference to this instance must be maintained if it is
|
||||
/// being bound to a state machine or artboard, or for observability. Fetching a
|
||||
/// new instance from the same model, if not bound, will not update its
|
||||
/// properties when properties are updated.
|
||||
///
|
||||
/// - Parameter index: The index of an instance within the view model.
|
||||
- (nullable RiveDataBindingViewModelInstance*)createInstanceFromIndex:
|
||||
(NSUInteger)index NS_SWIFT_NAME(createInstance(fromIndex:));
|
||||
|
||||
/// Creates a new instance to bind from a given name.
|
||||
///
|
||||
/// The name of an instance has to match the name of
|
||||
/// an instance in the "Data Bind" panel's instances dropdown, where the
|
||||
/// instance has been exported.
|
||||
///
|
||||
/// - Note: A strong reference to this instance must be maintained if it is
|
||||
/// being bound to a state machine or artboard, or for observability. Fetching a
|
||||
/// new instance from the same model, if not bound, will not update its
|
||||
/// properties when properties are updated.
|
||||
///
|
||||
/// - Parameter name: The name of an instance within the view model.
|
||||
- (nullable RiveDataBindingViewModelInstance*)createInstanceFromName:
|
||||
(NSString*)name NS_SWIFT_NAME(createInstance(fromName:));
|
||||
|
||||
/// Creates a new default instance to bind from the view model.
|
||||
///
|
||||
/// This is the instance marked as "Default" in the "Data Bind" instances
|
||||
/// dropdown when an artboard is selected.
|
||||
///
|
||||
/// - Note: A strong reference to this instance must be maintained if it is
|
||||
/// being bound to a state machine or artboard, or for observability. Fetching a
|
||||
/// new instance from the same model, if not bound, will not update its
|
||||
/// properties when properties are updated.
|
||||
- (nullable RiveDataBindingViewModelInstance*)createDefaultInstance;
|
||||
|
||||
/// Creates a new instance with Rive default values from the view model to bind
|
||||
/// to an artboard and/or state machine.
|
||||
///
|
||||
/// *Default values*
|
||||
/// - *String*: ""
|
||||
/// - *Number*: 0
|
||||
/// - *Boolean*: false
|
||||
/// - *Color*: ARGB(0, 0, 0, 0)
|
||||
/// - *Enum*: An enum's first value
|
||||
///
|
||||
/// - Note: A strong reference to this instance must be maintained if it is
|
||||
/// being bound to a state machine or artboard, or for observability. Fetching a
|
||||
/// new instance from the same model, if not bound, will not update its
|
||||
/// properties when properties are updated.
|
||||
- (nullable RiveDataBindingViewModelInstance*)createInstance;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
135
Source/DataBinding/RiveDataBindingViewModel.mm
Normal file
135
Source/DataBinding/RiveDataBindingViewModel.mm
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// RiveDataBindingViewModel.m
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 1/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Rive.h>
|
||||
#import <RivePrivateHeaders.h>
|
||||
#import <RiveRuntime/RiveRuntime-Swift.h>
|
||||
|
||||
@implementation RiveDataBindingViewModel
|
||||
{
|
||||
rive::ViewModelRuntime* _viewModel;
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewModel:(rive::ViewModelRuntime*)viewModel
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_viewModel = nullptr;
|
||||
}
|
||||
|
||||
- (NSString*)name
|
||||
{
|
||||
auto name = _viewModel->name();
|
||||
return [NSString stringWithCString:name.c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (NSUInteger)instanceCount
|
||||
{
|
||||
return _viewModel->instanceCount();
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*)instanceNames
|
||||
{
|
||||
auto values = _viewModel->instanceNames();
|
||||
NSMutableArray* mapped = [NSMutableArray arrayWithCapacity:values.size()];
|
||||
for (auto it = values.begin(); it != values.end(); ++it)
|
||||
{
|
||||
auto name = *it;
|
||||
NSString* string = [NSString stringWithCString:name.c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
[mapped addObject:string];
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
- (NSUInteger)propertyCount
|
||||
{
|
||||
return _viewModel->propertyCount();
|
||||
}
|
||||
|
||||
- (NSArray<RiveDataBindingViewModelInstancePropertyData*>*)properties
|
||||
{
|
||||
auto properties = _viewModel->properties();
|
||||
NSMutableArray<RiveDataBindingViewModelInstancePropertyData*>* mapped =
|
||||
[NSMutableArray arrayWithCapacity:properties.size()];
|
||||
for (auto it = properties.begin(); it != properties.end(); ++it)
|
||||
{
|
||||
[mapped addObject:[[RiveDataBindingViewModelInstancePropertyData alloc]
|
||||
initWithData:*it]];
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstance*)createInstanceFromIndex:
|
||||
(NSUInteger)index
|
||||
{
|
||||
auto instance = _viewModel->createInstanceFromIndex(index);
|
||||
if (instance == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelRuntime:self
|
||||
createdInstanceFromIndex:index
|
||||
created:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelRuntime:self
|
||||
createdInstanceFromIndex:index
|
||||
created:YES];
|
||||
return [[RiveDataBindingViewModelInstance alloc] initWithInstance:instance];
|
||||
}
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstance*)createInstanceFromName:
|
||||
(NSString*)name
|
||||
{
|
||||
auto instance =
|
||||
_viewModel->createInstanceFromName(std::string([name UTF8String]));
|
||||
if (instance == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelRuntime:self
|
||||
createdInstanceFromName:name
|
||||
created:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelRuntime:self
|
||||
createdInstanceFromName:name
|
||||
created:YES];
|
||||
return [[RiveDataBindingViewModelInstance alloc] initWithInstance:instance];
|
||||
}
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstance*)createDefaultInstance
|
||||
{
|
||||
auto instance = _viewModel->createDefaultInstance();
|
||||
if (instance == nullptr)
|
||||
{
|
||||
[RiveLogger logViewModelRuntimeCreatedDefaultInstance:self created:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logViewModelRuntimeCreatedDefaultInstance:self created:YES];
|
||||
return [[RiveDataBindingViewModelInstance alloc] initWithInstance:instance];
|
||||
}
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstance*)createInstance
|
||||
{
|
||||
auto instance = _viewModel->createInstance();
|
||||
if (instance == nullptr)
|
||||
{
|
||||
[RiveLogger logViewModelRuntimeCreatedInstance:self created:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logViewModelRuntimeCreatedInstance:self created:YES];
|
||||
return [[RiveDataBindingViewModelInstance alloc] initWithInstance:instance];
|
||||
}
|
||||
|
||||
@end
|
||||
153
Source/DataBinding/RiveDataBindingViewModelInstance.h
Normal file
153
Source/DataBinding/RiveDataBindingViewModelInstance.h
Normal file
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// RiveDataBindingViewModelInstance.h
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 1/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class RiveDataBindingViewModelInstanceProperty;
|
||||
@class RiveDataBindingViewModelInstanceStringProperty;
|
||||
@class RiveDataBindingViewModelInstanceNumberProperty;
|
||||
@class RiveDataBindingViewModelInstanceBooleanProperty;
|
||||
@class RiveDataBindingViewModelInstanceColorProperty;
|
||||
@class RiveDataBindingViewModelInstanceEnumProperty;
|
||||
@class RiveDataBindingViewModelInstanceTriggerProperty;
|
||||
@class RiveDataBindingViewModelInstancePropertyData;
|
||||
|
||||
/// An object that represents an instance of a view model, used to update
|
||||
/// bindings at runtime.
|
||||
///
|
||||
/// - Note: A strong reference to this instance must be maintained if it is
|
||||
/// being bound to a state machine or artboard, or for observability. If a
|
||||
/// property is fetched from an instance different to that bound to an artboard
|
||||
/// or state machine, its value or trigger will not be updated.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModel.Instance)
|
||||
@interface RiveDataBindingViewModelInstance : NSObject
|
||||
|
||||
/// The name of the view model instance.
|
||||
@property(nonatomic, readonly) NSString* name;
|
||||
|
||||
/// The number of all properties in the view model instance.
|
||||
@property(nonatomic, readonly) NSUInteger propertyCount;
|
||||
|
||||
/// An array of property data of all properties in the view model instance.
|
||||
@property(nonatomic, readonly)
|
||||
NSArray<RiveDataBindingViewModelInstancePropertyData*>* properties;
|
||||
|
||||
/// Gets a property from the view model instance. This property is the
|
||||
/// superclass of all other property types.
|
||||
///
|
||||
/// - 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 property.
|
||||
///
|
||||
/// - Returns: The property if it exists at the supplied path, otherwise nil.
|
||||
- (nullable RiveDataBindingViewModelInstanceProperty*)propertyFromPath:
|
||||
(NSString*)path;
|
||||
|
||||
/// Gets a string 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 string property.
|
||||
///
|
||||
/// - Returns: The property if it exists at the supplied path, otherwise nil.
|
||||
- (nullable RiveDataBindingViewModelInstanceStringProperty*)
|
||||
stringPropertyFromPath:(NSString*)path;
|
||||
|
||||
/// Gets a number 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 RiveDataBindingViewModelInstanceNumberProperty*)
|
||||
numberPropertyFromPath:(NSString*)path;
|
||||
|
||||
/// Gets a boolean 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 RiveDataBindingViewModelInstanceBooleanProperty*)
|
||||
booleanPropertyFromPath:(NSString*)path;
|
||||
|
||||
/// Gets a color 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 RiveDataBindingViewModelInstanceColorProperty*)
|
||||
colorPropertyFromPath:(NSString*)path;
|
||||
|
||||
/// Gets a enum 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 RiveDataBindingViewModelInstanceEnumProperty*)enumPropertyFromPath:
|
||||
(NSString*)path;
|
||||
|
||||
/// Gets a view model 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 RiveDataBindingViewModelInstance*)viewModelInstancePropertyFromPath:
|
||||
(NSString*)path;
|
||||
|
||||
/// Returns a trigger 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 RiveDataBindingViewModelInstanceTriggerProperty*)
|
||||
triggerPropertyFromPath:(NSString*)path;
|
||||
|
||||
/// Calls all registered property listeners for the properties of the view model
|
||||
/// instance.
|
||||
- (void)updateListeners;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
430
Source/DataBinding/RiveDataBindingViewModelInstance.mm
Normal file
430
Source/DataBinding/RiveDataBindingViewModelInstance.mm
Normal file
@@ -0,0 +1,430 @@
|
||||
//
|
||||
// RiveDataBindingViewModelInstance.m
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 1/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Rive.h>
|
||||
#import <RivePrivateHeaders.h>
|
||||
#import <RiveRuntime/RiveRuntime-Swift.h>
|
||||
|
||||
@interface RiveDataBindingViewModelInstance () <
|
||||
RiveDataBindingViewModelInstancePropertyDelegate>
|
||||
@end
|
||||
|
||||
@implementation RiveDataBindingViewModelInstance
|
||||
{
|
||||
rive::ViewModelInstanceRuntime* _instance;
|
||||
NSMutableDictionary<NSString*, RiveDataBindingViewModelInstanceProperty*>*
|
||||
_properties;
|
||||
NSMutableDictionary<NSString*, RiveDataBindingViewModelInstance*>*
|
||||
_children;
|
||||
}
|
||||
|
||||
- (instancetype)initWithInstance:(rive::ViewModelInstanceRuntime*)instance
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_instance = instance;
|
||||
_properties = [NSMutableDictionary dictionary];
|
||||
_children = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
- (NSString*)name
|
||||
{
|
||||
return [NSString stringWithCString:_instance->name().c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (NSUInteger)propertyCount
|
||||
{
|
||||
return _instance->propertyCount();
|
||||
}
|
||||
|
||||
- (NSArray<RiveDataBindingViewModelInstancePropertyData*>*)properties
|
||||
{
|
||||
auto properties = _instance->properties();
|
||||
NSMutableArray<RiveDataBindingViewModelInstancePropertyData*>* mapped =
|
||||
[NSMutableArray arrayWithCapacity:properties.size()];
|
||||
for (auto it = properties.begin(); it != properties.end(); ++it)
|
||||
{
|
||||
[mapped addObject:[[RiveDataBindingViewModelInstancePropertyData alloc]
|
||||
initWithData:*it]];
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstanceProperty*)propertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:[RiveDataBindingViewModelInstanceProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto property = _instance->property(std::string([path UTF8String]));
|
||||
if (property == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self propertyAtPath:path found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self propertyAtPath:path found:YES];
|
||||
RiveDataBindingViewModelInstanceProperty* value =
|
||||
[[RiveDataBindingViewModelInstanceProperty alloc]
|
||||
initWithValue:property];
|
||||
value.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:value withPath:path];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstanceStringProperty*)
|
||||
stringPropertyFromPath:(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceStringProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:
|
||||
[RiveDataBindingViewModelInstanceStringProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto string = _instance->propertyString(std::string([path UTF8String]));
|
||||
if (string == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
stringPropertyAtPath:path
|
||||
found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
stringPropertyAtPath:path
|
||||
found:YES];
|
||||
RiveDataBindingViewModelInstanceStringProperty* stringValue =
|
||||
[[RiveDataBindingViewModelInstanceStringProperty alloc]
|
||||
initWithString:string];
|
||||
stringValue.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:stringValue withPath:path];
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstanceNumberProperty*)numberPropertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceNumberProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:
|
||||
[RiveDataBindingViewModelInstanceNumberProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto number = _instance->propertyNumber(std::string([path UTF8String]));
|
||||
if (number == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
numberPropertyAtPath:path
|
||||
found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
numberPropertyAtPath:path
|
||||
found:YES];
|
||||
RiveDataBindingViewModelInstanceNumberProperty* numberValue =
|
||||
[[RiveDataBindingViewModelInstanceNumberProperty alloc]
|
||||
initWithNumber:number];
|
||||
numberValue.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:numberValue withPath:path];
|
||||
|
||||
return numberValue;
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstanceBooleanProperty*)booleanPropertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceBooleanProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:
|
||||
[RiveDataBindingViewModelInstanceBooleanProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto boolean = _instance->propertyBoolean(std::string([path UTF8String]));
|
||||
if (boolean == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
booleanPropertyAtPath:path
|
||||
found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
booleanPropertyAtPath:path
|
||||
found:YES];
|
||||
RiveDataBindingViewModelInstanceBooleanProperty* boolValue =
|
||||
[[RiveDataBindingViewModelInstanceBooleanProperty alloc]
|
||||
initWithBoolean:boolean];
|
||||
boolValue.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:boolValue withPath:path];
|
||||
|
||||
return boolValue;
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstanceColorProperty*)colorPropertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceColorProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:
|
||||
[RiveDataBindingViewModelInstanceColorProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto color = _instance->propertyColor(std::string([path UTF8String]));
|
||||
if (color == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
colorPropertyAtPath:path
|
||||
found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
colorPropertyAtPath:path
|
||||
found:YES];
|
||||
RiveDataBindingViewModelInstanceColorProperty* colorValue =
|
||||
[[RiveDataBindingViewModelInstanceColorProperty alloc]
|
||||
initWithColor:color];
|
||||
colorValue.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:colorValue withPath:path];
|
||||
|
||||
return colorValue;
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstanceEnumProperty*)enumPropertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceEnumProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:
|
||||
[RiveDataBindingViewModelInstanceEnumProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto e = _instance->propertyEnum(std::string([path UTF8String]));
|
||||
if (e == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
enumPropertyAtPath:path
|
||||
found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
enumPropertyAtPath:path
|
||||
found:YES];
|
||||
RiveDataBindingViewModelInstanceEnumProperty* enumProperty =
|
||||
[[RiveDataBindingViewModelInstanceEnumProperty alloc] initWithEnum:e];
|
||||
enumProperty.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:enumProperty withPath:path];
|
||||
|
||||
return enumProperty;
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstance*)viewModelInstancePropertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
return [self childForPath:path];
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstanceTriggerProperty*)triggerPropertyFromPath:
|
||||
(NSString*)path
|
||||
{
|
||||
RiveDataBindingViewModelInstanceTriggerProperty* cached;
|
||||
if ((cached = [self
|
||||
cachedPropertyFromPath:path
|
||||
asClass:
|
||||
[RiveDataBindingViewModelInstanceTriggerProperty
|
||||
class]]))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
auto trigger = _instance->propertyTrigger(std::string([path UTF8String]));
|
||||
if (trigger == nullptr)
|
||||
{
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
triggerPropertyAtPath:path
|
||||
found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logWithViewModelInstance:self
|
||||
triggerPropertyAtPath:path
|
||||
found:YES];
|
||||
RiveDataBindingViewModelInstanceTriggerProperty* triggerProperty =
|
||||
[[RiveDataBindingViewModelInstanceTriggerProperty alloc]
|
||||
initWithTrigger:trigger];
|
||||
triggerProperty.valueDelegate = self;
|
||||
|
||||
[self cacheProperty:triggerProperty withPath:path];
|
||||
|
||||
return triggerProperty;
|
||||
}
|
||||
|
||||
- (void)updateListeners
|
||||
{
|
||||
[_properties enumerateKeysAndObjectsUsingBlock:^(
|
||||
NSString* _Nonnull key,
|
||||
RiveDataBindingViewModelInstanceProperty* _Nonnull obj,
|
||||
BOOL* _Nonnull stop) {
|
||||
if (obj.hasChanged)
|
||||
{
|
||||
[obj handleListeners];
|
||||
}
|
||||
}];
|
||||
|
||||
[_properties enumerateKeysAndObjectsUsingBlock:^(
|
||||
NSString* _Nonnull key,
|
||||
RiveDataBindingViewModelInstanceProperty* _Nonnull obj,
|
||||
BOOL* _Nonnull stop) {
|
||||
if (obj.hasChanged)
|
||||
{
|
||||
[obj clearChanges];
|
||||
}
|
||||
}];
|
||||
|
||||
[_children enumerateKeysAndObjectsUsingBlock:^(
|
||||
NSString* _Nonnull key,
|
||||
RiveDataBindingViewModelInstance* _Nonnull obj,
|
||||
BOOL* _Nonnull stop) {
|
||||
[obj updateListeners];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (rive::ViewModelInstanceRuntime*)instance
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
|
||||
- (void)cacheProperty:(RiveDataBindingViewModelInstanceProperty*)value
|
||||
withPath:(NSString*)path
|
||||
{
|
||||
NSArray<NSString*>* components = [path pathComponents];
|
||||
if (components.count == 1)
|
||||
{
|
||||
_properties[path] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
RiveDataBindingViewModelInstance* child =
|
||||
[self childForPath:components[0]];
|
||||
if (child)
|
||||
{
|
||||
NSArray* subcomponents = [components
|
||||
subarrayWithRange:NSMakeRange(1, components.count - 1)];
|
||||
NSString* subpath = [subcomponents componentsJoinedByString:@"/"];
|
||||
[child cacheProperty:value withPath:subpath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable id)cachedPropertyFromPath:(NSString*)path asClass:(Class)aClass
|
||||
{
|
||||
RiveDataBindingViewModelInstanceProperty* property =
|
||||
[_properties objectForKey:path];
|
||||
if (property != nil && [property isKindOfClass:aClass])
|
||||
{
|
||||
return property;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - RiveDataBindingViewModelInstancePropertyDelegate
|
||||
|
||||
- (void)valuePropertyDidAddListener:
|
||||
(RiveDataBindingViewModelInstanceProperty*)value
|
||||
{}
|
||||
|
||||
- (void)valuePropertyDidRemoveListener:
|
||||
(RiveDataBindingViewModelInstanceProperty*)value
|
||||
isEmpty:(BOOL)isEmpty
|
||||
{}
|
||||
|
||||
#pragma mark - Paths
|
||||
|
||||
- (nullable RiveDataBindingViewModelInstance*)childForPath:(NSString*)path
|
||||
{
|
||||
NSArray* components = [path pathComponents];
|
||||
// If we have no components, we have no child to add
|
||||
if (components.count == 0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// E.g from "current/path", this is "current".
|
||||
// If a child exists with that name, return it.
|
||||
NSString* currentPath = components[0];
|
||||
// Use map over set
|
||||
RiveDataBindingViewModelInstance* existing = nil;
|
||||
if ((existing = [_children objectForKey:currentPath]))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
// Otherwise, for the current path, build a tree recursively, starting with
|
||||
// the current position.
|
||||
auto instance =
|
||||
_instance->propertyViewModel(std::string([currentPath UTF8String]));
|
||||
if (instance == nullptr)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
RiveDataBindingViewModelInstance* child =
|
||||
[[RiveDataBindingViewModelInstance alloc] initWithInstance:instance];
|
||||
_children[currentPath] = child;
|
||||
if (components.count == 1)
|
||||
{
|
||||
return child;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSArray* subpath =
|
||||
[components subarrayWithRange:NSMakeRange(1, components.count - 1)];
|
||||
return [self childForPath:[subpath componentsJoinedByString:@"/"]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
265
Source/DataBinding/RiveDataBindingViewModelInstanceProperty.h
Normal file
265
Source/DataBinding/RiveDataBindingViewModelInstanceProperty.h
Normal file
@@ -0,0 +1,265 @@
|
||||
//
|
||||
// RiveDataBindingViewModelInstanceStringProperty.h
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 1/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#import <AppKit/NSColor.h>
|
||||
#define RiveDataBindingViewModelInstanceColor NSColor
|
||||
#else
|
||||
#import <UIKit/UIColor.h>
|
||||
#define RiveDataBindingViewModelInstanceColor UIColor
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// An object that represents a property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.Property)
|
||||
@interface RiveDataBindingViewModelInstanceProperty : NSObject
|
||||
|
||||
/// The name of the property.
|
||||
@property(nonatomic, readonly) NSString* name;
|
||||
|
||||
/// Returns whether the property has changed, and the change will be reflected
|
||||
/// on next advance.
|
||||
@property(nonatomic, readonly) BOOL hasChanged;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Resets a property's changed status, resetting `hasChanged` to false.
|
||||
- (void)clearChanges;
|
||||
|
||||
/// Removes a listener for the property.
|
||||
///
|
||||
/// - Parameter listener: The listener to remove. This value will be returned by
|
||||
/// the matching call to `addListener`.
|
||||
- (void)removeListener:(NSUUID*)listener;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - String
|
||||
|
||||
typedef void (^RiveDataBindingViewModelInstanceStringPropertyListener)(
|
||||
NSString*)
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceStringProperty.Listener);
|
||||
|
||||
/// An object that represents a string property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.StringProperty)
|
||||
@interface RiveDataBindingViewModelInstanceStringProperty
|
||||
: RiveDataBindingViewModelInstanceProperty
|
||||
|
||||
/// The string value of the property.
|
||||
@property(nonatomic, copy) NSString* value;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Adds a block as a listener, called with the latest value when value is
|
||||
/// updated.
|
||||
///
|
||||
/// - Note: The value can be updated 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:
|
||||
(RiveDataBindingViewModelInstanceStringPropertyListener)listener;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Number
|
||||
|
||||
typedef void (^RiveDataBindingViewModelInstanceNumberPropertyListener)(float)
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceNumberProperty.Listener);
|
||||
|
||||
/// An object that represents a number property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.NumberProperty)
|
||||
@interface RiveDataBindingViewModelInstanceNumberProperty
|
||||
: RiveDataBindingViewModelInstanceProperty
|
||||
|
||||
/// The number value of the property.
|
||||
@property(nonatomic, assign) float value;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Adds a block as a listener, called with the latest value when value is
|
||||
/// updated.
|
||||
///
|
||||
/// - Note: The value can be updated 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:
|
||||
(RiveDataBindingViewModelInstanceNumberPropertyListener)listener;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Boolean
|
||||
|
||||
typedef void (^RiveDataBindingViewModelInstanceBooleanPropertyListener)(BOOL)
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceBooleanProperty.Listener);
|
||||
|
||||
/// An object that represents a boolean property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.BooleanProperty)
|
||||
@interface RiveDataBindingViewModelInstanceBooleanProperty
|
||||
: RiveDataBindingViewModelInstanceProperty
|
||||
|
||||
/// The boolean value of the property.
|
||||
@property(nonatomic, assign) BOOL value;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Adds a block as a listener, called with the latest value when value is
|
||||
/// updated.
|
||||
///
|
||||
/// - Note: The value can be updated 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:
|
||||
(RiveDataBindingViewModelInstanceBooleanPropertyListener)listener;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Color
|
||||
|
||||
typedef void (^RiveDataBindingViewModelInstanceColorPropertyListener)(
|
||||
RiveDataBindingViewModelInstanceColor*)
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceColorProperty.Listener);
|
||||
|
||||
/// An object that represents a color property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.ColorProperty)
|
||||
@interface RiveDataBindingViewModelInstanceColorProperty
|
||||
: RiveDataBindingViewModelInstanceProperty
|
||||
|
||||
/// The color value of the property as an integer, as 0xAARRGGBB.
|
||||
@property(nonatomic, copy) RiveDataBindingViewModelInstanceColor* value;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Sets a new color value based on RGB values, preserving its alpha value.
|
||||
/// - Parameters:
|
||||
/// - red: The red value of the color (0-255).
|
||||
/// - green: The green value of the color (0-255)
|
||||
/// - blue: The blue value of the color (0-255)
|
||||
- (void)setRed:(CGFloat)red
|
||||
green:(CGFloat)green
|
||||
blue:(CGFloat)blue NS_SWIFT_NAME(set(red:green:blue:));
|
||||
|
||||
/// Sets a new color value based on alpha and RGB values.
|
||||
/// - Parameters:
|
||||
/// - red: The red value of the color (0-255).
|
||||
/// - green: The green value of the color (0-255)
|
||||
/// - blue: The blue value of the color (0-255)
|
||||
/// - alpha: The alpha value of the color (0-255)
|
||||
- (void)setRed:(CGFloat)red
|
||||
green:(CGFloat)green
|
||||
blue:(CGFloat)blue
|
||||
alpha:(CGFloat)alpha NS_SWIFT_NAME(set(red:green:blue:alpha:));
|
||||
|
||||
/// Sets a new alpha value, preserving the current color.
|
||||
- (void)setAlpha:(CGFloat)alpha;
|
||||
|
||||
/// Adds a block as a listener, called with the latest value when value is
|
||||
/// updated.
|
||||
///
|
||||
/// - Note: The value can be updated 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:
|
||||
(RiveDataBindingViewModelInstanceColorPropertyListener)listener;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Enum
|
||||
|
||||
typedef void (^RiveDataBindingViewModelInstanceEnumPropertyListener)(NSString*)
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceEnumProperty.Listener);
|
||||
|
||||
/// An object that represents an enum property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.EnumProperty)
|
||||
@interface RiveDataBindingViewModelInstanceEnumProperty
|
||||
: RiveDataBindingViewModelInstanceProperty
|
||||
|
||||
/// The current string value of the enum property.
|
||||
@property(nonatomic, copy) NSString* value;
|
||||
|
||||
/// An array of all possible values for the enum.
|
||||
@property(nonatomic, readonly) NSArray<NSString*>* values;
|
||||
|
||||
/// The index of the current value in `values`. Setting a new index will also
|
||||
/// update the `value` of this property.
|
||||
///
|
||||
/// - Note: If the new index is outside of the bounds of `values`, this will do
|
||||
/// nothing, or return 0.
|
||||
@property(nonatomic, assign) int valueIndex;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Adds a block as a listener, called with the latest value when value is
|
||||
/// updated.
|
||||
///
|
||||
/// - Note: The value can be updated 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:
|
||||
(RiveDataBindingViewModelInstanceEnumPropertyListener)listener;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Trigger
|
||||
|
||||
typedef void (^RiveDataBindingViewModelInstanceTriggerPropertyListener)(void)
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceTriggerProperty.Listener);
|
||||
|
||||
/// An object that represents a trigger property of a view model instance.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstance.TriggerProperty)
|
||||
@interface RiveDataBindingViewModelInstanceTriggerProperty
|
||||
: RiveDataBindingViewModelInstanceProperty
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Triggers a trigger property.
|
||||
- (void)trigger;
|
||||
|
||||
/// 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:
|
||||
(RiveDataBindingViewModelInstanceTriggerPropertyListener)listener;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
538
Source/DataBinding/RiveDataBindingViewModelInstanceProperty.mm
Normal file
538
Source/DataBinding/RiveDataBindingViewModelInstanceProperty.mm
Normal file
@@ -0,0 +1,538 @@
|
||||
//
|
||||
// RiveDataBindingViewModelInstanceProperty.m
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 1/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Rive.h>
|
||||
#import <RivePrivateHeaders.h>
|
||||
#import <RiveRuntime/RiveRuntime-Swift.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <WeakContainer.h>
|
||||
|
||||
@interface RiveDataBindingViewModelInstancePropertyListener<ValueType>
|
||||
: NSObject
|
||||
@property(nonatomic, readonly) void (^listener)(ValueType);
|
||||
- (instancetype)initWithListener:(void (^)(ValueType))listener;
|
||||
@end
|
||||
|
||||
#pragma mark - String
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceProperty
|
||||
{
|
||||
rive::ViewModelInstanceValueRuntime* _value;
|
||||
NSUUID* _uuid;
|
||||
NSMutableDictionary<NSUUID*, id>* _listeners;
|
||||
WeakContainer<id<RiveDataBindingViewModelInstancePropertyDelegate>>*
|
||||
_delegateContainer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithValue:(rive::ViewModelInstanceValueRuntime*)value
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_value = value;
|
||||
_uuid = [NSUUID UUID];
|
||||
_listeners = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_value = nullptr;
|
||||
if (self.valueDelegate != nil)
|
||||
{
|
||||
[self.valueDelegate valuePropertyDidRemoveListener:self isEmpty:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString*)name
|
||||
{
|
||||
return [NSString stringWithCString:_value->name().c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (BOOL)hasValue
|
||||
{
|
||||
return [self respondsToSelector:@selector(value)];
|
||||
}
|
||||
|
||||
- (BOOL)hasChanged
|
||||
{
|
||||
return _value->hasChanged();
|
||||
}
|
||||
|
||||
- (void)clearChanges
|
||||
{
|
||||
_value->clearChanges();
|
||||
}
|
||||
|
||||
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSUUID*, id>*)listeners
|
||||
{
|
||||
return _listeners;
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:(id)listener
|
||||
{
|
||||
NSUUID* uuid = [NSUUID UUID];
|
||||
_listeners[uuid] = [listener copy];
|
||||
if (self.valueDelegate)
|
||||
{
|
||||
[self.valueDelegate valuePropertyDidAddListener:self];
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
- (void)removeListener:(NSUUID*)listener
|
||||
{
|
||||
_listeners[listener] = nil;
|
||||
if (self.valueDelegate)
|
||||
{
|
||||
[self.valueDelegate
|
||||
valuePropertyDidRemoveListener:self
|
||||
isEmpty:(_listeners.count == 0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
NSAssert(
|
||||
NO, @"handleListeners is not implemented by a subclass of this class.");
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
if (other == self)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
else if ([other isKindOfClass:[self class]])
|
||||
{
|
||||
return ((RiveDataBindingViewModelInstanceProperty*)other).hash ==
|
||||
self.hash;
|
||||
}
|
||||
else if (![super isEqual:other])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return _uuid.hash;
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (nullable id<RiveDataBindingViewModelInstancePropertyDelegate>)valueDelegate
|
||||
{
|
||||
return [_delegateContainer object];
|
||||
}
|
||||
|
||||
- (void)setValueDelegate:
|
||||
(id<RiveDataBindingViewModelInstancePropertyDelegate>)delegate
|
||||
{
|
||||
WeakContainer* container = [[WeakContainer alloc] init];
|
||||
container.object = delegate;
|
||||
_delegateContainer = container;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceStringProperty
|
||||
{
|
||||
rive::ViewModelInstanceStringRuntime* _string;
|
||||
}
|
||||
|
||||
- (instancetype)initWithString:(rive::ViewModelInstanceStringRuntime*)string
|
||||
{
|
||||
if (self = [super initWithValue:string])
|
||||
{
|
||||
_string = string;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_string = nullptr;
|
||||
}
|
||||
|
||||
- (void)setValue:(NSString*)value
|
||||
{
|
||||
_string->value(std::string([value UTF8String]));
|
||||
[RiveLogger logPropertyUpdated:self value:value];
|
||||
}
|
||||
|
||||
- (NSString*)value
|
||||
{
|
||||
auto value = _string->value();
|
||||
return [NSString stringWithCString:value.c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:
|
||||
(RiveDataBindingViewModelInstanceStringPropertyListener)listener
|
||||
{
|
||||
return [super addListener:listener];
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
for (RiveDataBindingViewModelInstanceStringPropertyListener listener in self
|
||||
.listeners.allValues)
|
||||
{
|
||||
listener(self.value);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Number
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceNumberProperty
|
||||
{
|
||||
rive::ViewModelInstanceNumberRuntime* _number;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNumber:(rive::ViewModelInstanceNumberRuntime*)number
|
||||
{
|
||||
if (self = [super initWithValue:number])
|
||||
{
|
||||
_number = number;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_number = nullptr;
|
||||
}
|
||||
|
||||
- (void)setValue:(float)value
|
||||
{
|
||||
_number->value(value);
|
||||
[RiveLogger logPropertyUpdated:self
|
||||
value:[NSString stringWithFormat:@"%f", value]];
|
||||
}
|
||||
|
||||
- (float)value
|
||||
{
|
||||
return _number->value();
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:
|
||||
(RiveDataBindingViewModelInstanceNumberPropertyListener)listener
|
||||
{
|
||||
return [super addListener:listener];
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
for (RiveDataBindingViewModelInstanceNumberPropertyListener listener in self
|
||||
.listeners.allValues)
|
||||
{
|
||||
listener(self.value);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Boolean
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceBooleanProperty
|
||||
{
|
||||
rive::ViewModelInstanceBooleanRuntime* _boolean;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBoolean:(rive::ViewModelInstanceBooleanRuntime*)boolean
|
||||
{
|
||||
if (self = [super initWithValue:boolean])
|
||||
{
|
||||
_boolean = boolean;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_boolean = nullptr;
|
||||
}
|
||||
|
||||
- (void)setValue:(BOOL)value
|
||||
{
|
||||
_boolean->value(value);
|
||||
[RiveLogger
|
||||
logPropertyUpdated:self
|
||||
value:[NSString
|
||||
stringWithFormat:@"%@",
|
||||
value ? @"true" : @"false"]];
|
||||
}
|
||||
|
||||
- (BOOL)value
|
||||
{
|
||||
return _boolean->value();
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:
|
||||
(RiveDataBindingViewModelInstanceBooleanPropertyListener)listener
|
||||
{
|
||||
return [super addListener:listener];
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
for (RiveDataBindingViewModelInstanceBooleanPropertyListener listener in
|
||||
self.listeners.allValues)
|
||||
{
|
||||
listener(self.value);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Color
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceColorProperty
|
||||
{
|
||||
rive::ViewModelInstanceColorRuntime* _color;
|
||||
}
|
||||
|
||||
- (instancetype)initWithColor:(rive::ViewModelInstanceColorRuntime*)color
|
||||
{
|
||||
if (self = [super initWithValue:color])
|
||||
{
|
||||
_color = color;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_color = nullptr;
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModelInstanceColor*)value
|
||||
{
|
||||
int value = _color->value();
|
||||
CGFloat a = ((CGFloat)((value >> 24) & 0xFF)) / 255;
|
||||
CGFloat r = ((CGFloat)((value >> 16) & 0xFF)) / 255;
|
||||
CGFloat g = ((CGFloat)((value >> 8) & 0xFF)) / 255;
|
||||
CGFloat b = ((CGFloat)(value & 0xFF)) / 255;
|
||||
return [RiveDataBindingViewModelInstanceColor colorWithRed:r
|
||||
green:g
|
||||
blue:b
|
||||
alpha:a];
|
||||
}
|
||||
|
||||
- (void)setValue:(RiveDataBindingViewModelInstanceColor*)value
|
||||
{
|
||||
CGFloat a;
|
||||
CGFloat r;
|
||||
CGFloat g;
|
||||
CGFloat b;
|
||||
[value getRed:&r green:&g blue:&b alpha:&a];
|
||||
int intA = (int)(a * 255) << 24;
|
||||
int intR = (int)(r * 255) << 16;
|
||||
int intG = (int)(g * 255) << 8;
|
||||
int intB = (int)(b * 255);
|
||||
int color = intA | intR | intG | intB;
|
||||
_color->value(color);
|
||||
[RiveLogger
|
||||
logPropertyUpdated:self
|
||||
value:[NSString stringWithFormat:@"(Color: %@)", value]];
|
||||
}
|
||||
|
||||
- (void)setRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue
|
||||
{
|
||||
CGFloat r = fmax(0, fmin(red, 1.0));
|
||||
CGFloat g = fmax(0, fmin(green, 1.0));
|
||||
CGFloat b = fmax(0, fmin(blue, 1.0));
|
||||
_color->rgb((int)(r * 255), (int)(g * 255), (int)(b * 255));
|
||||
[RiveLogger
|
||||
logPropertyUpdated:self
|
||||
value:[NSString stringWithFormat:@"(R: %f, G: %f, B: %f)",
|
||||
red,
|
||||
green,
|
||||
blue]];
|
||||
}
|
||||
|
||||
- (void)setRed:(CGFloat)red
|
||||
green:(CGFloat)green
|
||||
blue:(CGFloat)blue
|
||||
alpha:(CGFloat)alpha
|
||||
{
|
||||
CGFloat r = fmax(0, fmin(red, 1.0));
|
||||
CGFloat g = fmax(0, fmin(green, 1.0));
|
||||
CGFloat b = fmax(0, fmin(blue, 1.0));
|
||||
CGFloat a = fmax(0, fmin(alpha, 1.0));
|
||||
_color->argb(
|
||||
(int)(a * 255), (int)(r * 255), (int)(g * 255), (int)(b * 255));
|
||||
[RiveLogger
|
||||
logPropertyUpdated:self
|
||||
value:[NSString
|
||||
stringWithFormat:@"(A: %f, R: %f, G: %f, B: %f)",
|
||||
alpha,
|
||||
red,
|
||||
green,
|
||||
blue]];
|
||||
}
|
||||
|
||||
- (void)setAlpha:(CGFloat)alpha
|
||||
{
|
||||
CGFloat a = fmax(0, fmin(alpha, 1.0));
|
||||
_color->alpha((int)(a * 255));
|
||||
[RiveLogger
|
||||
logPropertyUpdated:self
|
||||
value:[NSString stringWithFormat:@"(A: %lf)", alpha]];
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:
|
||||
(RiveDataBindingViewModelInstanceColorPropertyListener)listener
|
||||
{
|
||||
return [super addListener:listener];
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
for (RiveDataBindingViewModelInstanceColorPropertyListener listener in self
|
||||
.listeners.allValues)
|
||||
{
|
||||
listener(self.value);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Enum
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceEnumProperty
|
||||
{
|
||||
rive::ViewModelInstanceEnumRuntime* _enum;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEnum:(rive::ViewModelInstanceEnumRuntime*)e
|
||||
{
|
||||
if (self = [super initWithValue:e])
|
||||
{
|
||||
_enum = e;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_enum = nullptr;
|
||||
}
|
||||
|
||||
- (NSString*)value
|
||||
{
|
||||
auto value = _enum->value();
|
||||
return [NSString stringWithCString:value.c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setValue:(NSString*)value
|
||||
{
|
||||
_enum->value(std::string([value UTF8String]));
|
||||
[RiveLogger logPropertyUpdated:self value:value];
|
||||
}
|
||||
|
||||
- (int)valueIndex
|
||||
{
|
||||
return _enum->valueIndex();
|
||||
}
|
||||
|
||||
- (void)setValueIndex:(int)valueIndex
|
||||
{
|
||||
_enum->valueIndex(valueIndex);
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*)values
|
||||
{
|
||||
auto values = _enum->values();
|
||||
NSMutableArray* mapped = [NSMutableArray arrayWithCapacity:values.size()];
|
||||
for (auto it = values.begin(); it != values.end(); ++it)
|
||||
{
|
||||
auto value = *it;
|
||||
NSString* string = [NSString stringWithCString:value.c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
[mapped addObject:string];
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:
|
||||
(RiveDataBindingViewModelInstanceEnumPropertyListener)listener
|
||||
{
|
||||
return [super addListener:listener];
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
for (RiveDataBindingViewModelInstanceEnumPropertyListener listener in self
|
||||
.listeners.allValues)
|
||||
{
|
||||
listener(self.value);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Trigger
|
||||
|
||||
@implementation RiveDataBindingViewModelInstanceTriggerProperty
|
||||
{
|
||||
rive::ViewModelInstanceTriggerRuntime* _trigger;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTrigger:(rive::ViewModelInstanceTriggerRuntime*)trigger
|
||||
{
|
||||
if (self = [super initWithValue:trigger])
|
||||
{
|
||||
_trigger = trigger;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_trigger = nullptr;
|
||||
}
|
||||
|
||||
- (void)trigger
|
||||
{
|
||||
_trigger->trigger();
|
||||
[RiveLogger logPropertyTriggered:self];
|
||||
}
|
||||
|
||||
- (NSUUID*)addListener:
|
||||
(RiveDataBindingViewModelInstanceTriggerPropertyListener)listener
|
||||
{
|
||||
return [super addListener:listener];
|
||||
}
|
||||
|
||||
- (void)handleListeners
|
||||
{
|
||||
for (RiveDataBindingViewModelInstanceTriggerPropertyListener listener in
|
||||
self.listeners.allValues)
|
||||
{
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// RivePropertyData.h
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 2/4/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, RiveDataBindingViewModelInstancePropertyDataType) {
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeNone = 0,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeString,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeNumber,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeBoolean,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeColor,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeList,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeEnum,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeTrigger,
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeViewModel,
|
||||
} NS_SWIFT_NAME(RiveDataBindingViewModelInstancePropertyData.DataType);
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// An object that represents the metadata of a view model instance property.
|
||||
NS_SWIFT_NAME(RiveDataBindingViewModelInstanceProperty.Data)
|
||||
@interface RiveDataBindingViewModelInstancePropertyData : NSObject
|
||||
|
||||
/// The type of property within the view model instance.
|
||||
@property(nonatomic, readonly)
|
||||
RiveDataBindingViewModelInstancePropertyDataType type;
|
||||
|
||||
/// The name of the property within the view model instance.
|
||||
@property(nonatomic, readonly) NSString* name;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// RivePropertyData.m
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 2/4/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Rive.h>
|
||||
#import <RivePrivateHeaders.h>
|
||||
|
||||
RiveDataBindingViewModelInstancePropertyDataType
|
||||
RiveDataBindingViewModelInstancePropertyDataTypeFromRuntime(rive::DataType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case rive::DataType::none:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeNone;
|
||||
case rive::DataType::string:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeString;
|
||||
case rive::DataType::number:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeNumber;
|
||||
case rive::DataType::boolean:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeBoolean;
|
||||
case rive::DataType::color:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeColor;
|
||||
case rive::DataType::list:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeList;
|
||||
case rive::DataType::enumType:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeEnum;
|
||||
case rive::DataType::trigger:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeTrigger;
|
||||
case rive::DataType::viewModel:
|
||||
return RiveDataBindingViewModelInstancePropertyDataTypeViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation RiveDataBindingViewModelInstancePropertyData
|
||||
|
||||
- (instancetype)initWithData:(rive::PropertyData)data
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_type = RiveDataBindingViewModelInstancePropertyDataTypeFromRuntime(
|
||||
data.type);
|
||||
_name = [NSString stringWithCString:data.name.c_str()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
19
Source/DataBinding/WeakContainer.h
Normal file
19
Source/DataBinding/WeakContainer.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// WeakContainer.h
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 3/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface WeakContainer<WeakType> : NSObject
|
||||
|
||||
@property(nonatomic, nullable, weak) WeakType object;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
13
Source/DataBinding/WeakContainer.m
Normal file
13
Source/DataBinding/WeakContainer.m
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// WeakContainer.m
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 3/13/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "WeakContainer.h"
|
||||
|
||||
@implementation WeakContainer
|
||||
|
||||
@end
|
||||
@@ -12,6 +12,7 @@ import OSLog
|
||||
enum RiveLoggerArtboardEvent {
|
||||
case advance(Double)
|
||||
case error(String)
|
||||
case instanceBind(String)
|
||||
}
|
||||
|
||||
extension RiveLogger {
|
||||
@@ -25,6 +26,10 @@ extension RiveLogger {
|
||||
log(artboard: artboard, event: .error(error))
|
||||
}
|
||||
|
||||
@objc(logArtboard:instanceBind:) static func log(artboard: RiveArtboard, instanceBind name: String) {
|
||||
log(artboard: artboard, event: .instanceBind(name))
|
||||
}
|
||||
|
||||
static func log(artboard: RiveArtboard, event: RiveLoggerArtboardEvent) {
|
||||
switch event {
|
||||
case .advance(let elapsed):
|
||||
@@ -36,6 +41,10 @@ extension RiveLogger {
|
||||
_log(event: event, level: .error) {
|
||||
Self.artboard.error("\(error)")
|
||||
}
|
||||
case .instanceBind(let name):
|
||||
_log(event: event, level: .debug) {
|
||||
Self.artboard.debug("\(self.prefix(for: artboard))Bound view model instance \(name)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
205
Source/Logging/RiveLogger+DataBinding.swift
Normal file
205
Source/Logging/RiveLogger+DataBinding.swift
Normal file
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// RiveLogger+DataBinding.swift
|
||||
// RiveRuntime
|
||||
//
|
||||
// Created by David Skuza on 2/10/25.
|
||||
// Copyright © 2025 Rive. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
private protocol DataBindingEvent { }
|
||||
|
||||
enum RiveLoggerDataBindingEvent {
|
||||
enum ViewModel: DataBindingEvent {
|
||||
case createdInstanceFromIndex(Int, Bool)
|
||||
case createdInstanceFromName(String, Bool)
|
||||
case createdDefaultInstance(Bool)
|
||||
case createdInstance(Bool)
|
||||
}
|
||||
enum Instance: DataBindingEvent {
|
||||
case property(String, Bool)
|
||||
case stringProperty(String, Bool)
|
||||
case numberProperty(String, Bool)
|
||||
case booleanProperty(String, Bool)
|
||||
case colorProperty(String, Bool)
|
||||
case enumProperty(String, Bool)
|
||||
case viewModelProperty(String, Bool)
|
||||
case triggerProperty(String, Bool)
|
||||
}
|
||||
enum Property: DataBindingEvent {
|
||||
case propertyUpdated(String, String)
|
||||
case propertyTriggered(String)
|
||||
}
|
||||
}
|
||||
|
||||
extension RiveLogger {
|
||||
private static let dataBinding = Logger(subsystem: subsystem, category: "rive-data-binding")
|
||||
|
||||
// MARK: - Log
|
||||
|
||||
static func log(viewModelRuntime viewModel: RiveDataBindingViewModel, event: RiveLoggerDataBindingEvent.ViewModel) {
|
||||
switch event {
|
||||
case .createdInstanceFromIndex(let index, let created):
|
||||
_log(event: event, level: .debug) {
|
||||
let start = created ? "Created" : "Could not create"
|
||||
dataBinding.debug("[\(viewModel.name)] \(start) instance from index \(index)")
|
||||
}
|
||||
case .createdInstanceFromName(let name, let created):
|
||||
_log(event: event, level: .debug) {
|
||||
let start = created ? "Created" : "Could not create"
|
||||
dataBinding.debug("[\(viewModel.name)] \(start) instance from name \(name)")
|
||||
}
|
||||
case .createdDefaultInstance(let created):
|
||||
_log(event: event, level: .debug) {
|
||||
let start = created ? "Created" : "Could not create"
|
||||
dataBinding.debug("[\(viewModel.name)] \(start) default instance")
|
||||
}
|
||||
case .createdInstance(let created):
|
||||
_log(event: event, level: .debug) {
|
||||
let start = created ? "Created" : "Could not create"
|
||||
dataBinding.debug("[\(viewModel.name)] \(start) new instance")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, event: RiveLoggerDataBindingEvent.Instance) {
|
||||
switch event {
|
||||
case .property(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "base", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .stringProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "string", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .numberProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "number", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .booleanProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "boolean", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .colorProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "color", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .enumProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "enum", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .viewModelProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "view model", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
case .triggerProperty(let path, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = message(instance: instance, for: "trigger", path: path, found: found)
|
||||
dataBinding.debug("\(message)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func log(event: RiveLoggerDataBindingEvent.Property) {
|
||||
switch event {
|
||||
case .propertyUpdated(let name, let value):
|
||||
_log(event: event, level: .debug) {
|
||||
dataBinding.debug("[\(name)] Updated property value to \(value)")
|
||||
}
|
||||
case .propertyTriggered(let name):
|
||||
_log(event: event, level: .debug) {
|
||||
dataBinding.debug("[\(name)] Triggered")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RiveDataBindingViewModel
|
||||
|
||||
@objc static func log(viewModelRuntime runtime: RiveDataBindingViewModel, createdInstanceFromIndex index: Int, created: Bool) {
|
||||
Self.log(viewModelRuntime: runtime, event: .createdInstanceFromIndex(index, created))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelRuntime runtime: RiveDataBindingViewModel, createdInstanceFromName name: String, created: Bool) {
|
||||
Self.log(viewModelRuntime: runtime, event: .createdInstanceFromName(name, created))
|
||||
}
|
||||
|
||||
@objc static func logViewModelRuntimeCreatedDefaultInstance(_ runtime: RiveDataBindingViewModel, created: Bool) {
|
||||
Self.log(viewModelRuntime: runtime, event: .createdDefaultInstance(created))
|
||||
}
|
||||
|
||||
@objc static func logViewModelRuntimeCreatedInstance(_ runtime: RiveDataBindingViewModel, created: Bool) {
|
||||
Self.log(viewModelRuntime: runtime, event: .createdInstance(created))
|
||||
}
|
||||
|
||||
// MARK: - RiveDataBindingViewModelInstance
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, propertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .property(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, stringPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .stringProperty(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, numberPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .numberProperty(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, booleanPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .booleanProperty(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, colorPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .colorProperty(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, enumPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .enumProperty(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, viewModelPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .viewModelProperty(path, found))
|
||||
}
|
||||
|
||||
@objc static func log(viewModelInstance instance: RiveDataBindingViewModel.Instance, triggerPropertyAtPath path: String, found: Bool) {
|
||||
Self.log(viewModelInstance: instance, event: .triggerProperty(path, found))
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc(logPropertyUpdated:value:) static func log(propertyUpdated property: RiveDataBindingViewModel.Instance.Property, value: String) {
|
||||
Self.log(event: .propertyUpdated(property.name, value))
|
||||
}
|
||||
|
||||
@objc(logPropertyTriggered:) static func log(propertyTriggered property: RiveDataBindingViewModel.Instance.Property) {
|
||||
Self.log(event: .propertyTriggered(property.name))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static func _log(event: DataBindingEvent, level: RiveLogLevel, log: () -> Void) {
|
||||
guard isEnabled,
|
||||
categories.contains(.dataBinding),
|
||||
levels.contains(level)
|
||||
else { return }
|
||||
|
||||
log()
|
||||
}
|
||||
|
||||
private static func message(instance: RiveDataBindingViewModel.Instance, for type: String, path: String, found: Bool) -> String {
|
||||
if found {
|
||||
return "[\(instance.name)] Found \(type) property at path \(path)"
|
||||
} else {
|
||||
return "[\(instance.name)] Could not find \(type) property at path \(path)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ enum RiveLoggerFileEvent {
|
||||
case loadedAsset(RiveFileAsset)
|
||||
case loadedFromURL(URL)
|
||||
case loadingFromResource(String)
|
||||
case viewModelWithName(String, Bool)
|
||||
case viewModelAtIndex(Int, Bool)
|
||||
case defaultViewModelForArtboard(String, Bool)
|
||||
}
|
||||
|
||||
extension RiveLogger {
|
||||
@@ -51,6 +54,18 @@ extension RiveLogger {
|
||||
log(file: nil, event: .loadingFromResource(name))
|
||||
}
|
||||
|
||||
@objc(logFileViewModelWithName:found:) static func log(fileViewModelName name: String, found: Bool) {
|
||||
log(file: nil, event: .viewModelWithName(name, found))
|
||||
}
|
||||
|
||||
@objc(logFileViewModelAtIndex:found:) static func log(fileViewModelAtIndex index: Int, found: Bool) {
|
||||
log(file: nil, event: .viewModelAtIndex(index, found))
|
||||
}
|
||||
|
||||
@objc(logFileDefaultViewModelForArtboard:found:) static func log(fileDefaultViewModelForArtboard artboard: RiveArtboard, found: Bool) {
|
||||
log(file: nil, event: .defaultViewModelForArtboard(artboard.name(), found))
|
||||
}
|
||||
|
||||
static func log(file: RiveFile?, event: RiveLoggerFileEvent) {
|
||||
switch event {
|
||||
case .fatalError(let message):
|
||||
@@ -85,6 +100,21 @@ extension RiveLogger {
|
||||
_log(event: event, level: .debug) {
|
||||
Self.file.debug("Loading resource \(name)")
|
||||
}
|
||||
case .viewModelWithName(let name, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = found ? "Found view model named \(name)" : "Could not find view model named \(name)"
|
||||
Self.file.debug("\(message)")
|
||||
}
|
||||
case .viewModelAtIndex(let index, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = found ? "Found view model at index \(index)" : "Could not find view model at index \(index)"
|
||||
Self.file.debug("\(message)")
|
||||
}
|
||||
case .defaultViewModelForArtboard(let name, let found):
|
||||
_log(event: event, level: .debug) {
|
||||
let message = found ? "Found default view model for artboard \(name)" : "Could not find default view for artboard \(name)"
|
||||
Self.file.debug("\(message)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ enum RiveLoggerStateMachineEvent {
|
||||
case advance(Double)
|
||||
case eventReceived(RiveEvent)
|
||||
case error(String)
|
||||
case instanceBind(String)
|
||||
}
|
||||
|
||||
extension RiveLogger {
|
||||
@@ -26,6 +27,10 @@ extension RiveLogger {
|
||||
log(stateMachine: stateMachine, event: .error(error))
|
||||
}
|
||||
|
||||
@objc(logStateMachine:instanceBind:) static func log(stateMachine: RiveStateMachineInstance, instanceBind name: String) {
|
||||
log(stateMachine: stateMachine, event: .instanceBind(name))
|
||||
}
|
||||
|
||||
static func log(stateMachine: RiveStateMachineInstance, event: RiveLoggerStateMachineEvent) {
|
||||
switch event {
|
||||
case .advance(let elapsed):
|
||||
@@ -41,6 +46,10 @@ extension RiveLogger {
|
||||
_log(event: event, level: .error) {
|
||||
Self.stateMachine.error("\(error)")
|
||||
}
|
||||
case .instanceBind(let name):
|
||||
_log(event: event, level: .debug) {
|
||||
Self.stateMachine.debug("\(self.prefix(for: stateMachine))Bound view model instance \(name)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,11 +66,13 @@ import OSLog
|
||||
@objc public static let file = RiveLogCategory(rawValue: 1 << 4)
|
||||
/// The category used when logging from a Rive view.
|
||||
@objc public static let view = RiveLogCategory(rawValue: 1 << 5)
|
||||
/// The category used when logging Data Binding.
|
||||
@objc public static let dataBinding = RiveLogCategory(rawValue: 1 << 6)
|
||||
|
||||
/// An option set of no categories.
|
||||
@objc public static let none: RiveLogCategory = []
|
||||
/// An option set containing all possible categories
|
||||
@objc public static let all: RiveLogCategory = [.stateMachine, .artboard, .viewModel, .model, .file, .view]
|
||||
@objc public static let all: RiveLogCategory = [.stateMachine, .artboard, .viewModel, .model, .file, .view, .dataBinding]
|
||||
|
||||
override public func isEqual(_ object: Any?) -> Bool {
|
||||
guard let other = object as? RiveLogCategory else { return false }
|
||||
|
||||
@@ -420,4 +420,25 @@ static int artInstanceCount = 0;
|
||||
_artboardInstance->height(_artboardInstance->originalHeight());
|
||||
}
|
||||
|
||||
#pragma mark - Data Binding
|
||||
|
||||
- (void)bindViewModelInstance:(RiveDataBindingViewModelInstance*)instance
|
||||
{
|
||||
// Let's walk through the instances of the word instance
|
||||
//
|
||||
// _artboardInstance is the underlying c++ type of ourself
|
||||
// to which we bind
|
||||
//
|
||||
// instance is the ObjC bridging type of the underlying
|
||||
// c++ type of a view model instance.
|
||||
//
|
||||
// instance.instance is the underlying c++ type of the bridging type
|
||||
// so that we can call into the c++ runtime
|
||||
//
|
||||
// instance.instance->instance() is the c++ rcp of the actual
|
||||
// type that gets bound to the artboard
|
||||
_artboardInstance->bindViewModelInstance(instance.instance->instance());
|
||||
[RiveLogger logArtboard:self instanceBind:instance.name];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -494,6 +494,50 @@
|
||||
return artboardNames;
|
||||
}
|
||||
|
||||
#pragma mark - Data Binding
|
||||
|
||||
- (NSUInteger)viewModelCount
|
||||
{
|
||||
return riveFile->viewModelCount();
|
||||
}
|
||||
|
||||
- (nullable id)viewModelAtIndex:(NSUInteger)index
|
||||
{
|
||||
auto viewModel = riveFile->viewModelByIndex(index);
|
||||
if (viewModel == nullptr)
|
||||
{
|
||||
[RiveLogger logFileViewModelAtIndex:index found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logFileViewModelAtIndex:index found:YES];
|
||||
return [[RiveDataBindingViewModel alloc] initWithViewModel:viewModel];
|
||||
}
|
||||
|
||||
- (nullable id)viewModelNamed:(NSString*)name
|
||||
{
|
||||
auto viewModel = riveFile->viewModelByName(std::string([name UTF8String]));
|
||||
if (viewModel == nullptr)
|
||||
{
|
||||
[RiveLogger logFileViewModelWithName:name found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logFileViewModelWithName:name found:YES];
|
||||
return [[RiveDataBindingViewModel alloc] initWithViewModel:viewModel];
|
||||
}
|
||||
|
||||
- (RiveDataBindingViewModel*)defaultViewModelForArtboard:(RiveArtboard*)artboard
|
||||
{
|
||||
auto viewModel =
|
||||
riveFile->defaultArtboardViewModel(artboard.artboardInstance);
|
||||
if (viewModel == nullptr)
|
||||
{
|
||||
[RiveLogger logFileDefaultViewModelForArtboard:artboard found:NO];
|
||||
return nil;
|
||||
}
|
||||
[RiveLogger logFileDefaultViewModelForArtboard:artboard found:YES];
|
||||
return [[RiveDataBindingViewModel alloc] initWithViewModel:viewModel];
|
||||
}
|
||||
|
||||
/// Clean up rive file
|
||||
- (void)dealloc
|
||||
{
|
||||
|
||||
@@ -430,4 +430,28 @@ RiveHitResult RiveHitResultFromRuntime(rive::HitResult result)
|
||||
instance->pointerUp(rive::Vec2D(touchLocation.x, touchLocation.y)));
|
||||
}
|
||||
|
||||
#pragma mark - Data Binding
|
||||
|
||||
// Argument named i to not conflict with higher-level private variable named
|
||||
// instance
|
||||
- (void)bindViewModelInstance:(RiveDataBindingViewModelInstance*)i
|
||||
{
|
||||
// Let's walk through the instances of the word instance
|
||||
//
|
||||
// instance is the underlying c++ type of ourself
|
||||
// to which we bind
|
||||
//
|
||||
// i is the ObjC bridging type of the underlying
|
||||
// c++ type of a view model instance.
|
||||
//
|
||||
// i.instance is the underlying c++ type of the bridging type
|
||||
// so that we can call into the c++ runtime
|
||||
//
|
||||
// i.instance->instance() is the c++ rcp of the actual
|
||||
// type that gets bound to the state machine
|
||||
instance->bindViewModelInstance(i.instance->instance());
|
||||
_viewModelInstance = i;
|
||||
[RiveLogger logStateMachine:self instanceBind:i.name];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
#import <RiveRuntime/CDNFileAssetLoader.h>
|
||||
#import <RiveRuntime/RiveFont.h>
|
||||
|
||||
#import <RiveRuntime/RiveDataBindingViewModel.h>
|
||||
#import <RiveRuntime/RiveDataBindingViewModelInstance.h>
|
||||
#import <RiveRuntime/RiveDataBindingViewModelInstanceProperty.h>
|
||||
#import <RiveRuntime/RiveDataBindingViewModelInstancePropertyData.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class RiveStateMachineInstance;
|
||||
@class RiveRenderer;
|
||||
@class RiveTextValueRun;
|
||||
@class RiveDataBindingViewModelInstance;
|
||||
|
||||
// MARK: - RiveArtboard
|
||||
//
|
||||
@@ -59,6 +60,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)advanceBy:(double)elapsedSeconds;
|
||||
- (void)draw:(RiveRenderer*)renderer;
|
||||
|
||||
// MARK: - Data Binding
|
||||
|
||||
/// Binds an instance of a view model to the artboard for updates.
|
||||
///
|
||||
/// A strong reference to the instance being bound must be made if you wish to
|
||||
/// reuse instance properties, or for observability.
|
||||
///
|
||||
/// The same instance must also be bound to a state machine, if one exists.
|
||||
///
|
||||
/// - Parameter instance: The instance of a view model to bind.
|
||||
- (void)bindViewModelInstance:(RiveDataBindingViewModelInstance*)instance
|
||||
NS_SWIFT_NAME(bind(viewModelInstance:));
|
||||
|
||||
// MARK: Debug
|
||||
|
||||
#if RIVE_ENABLE_REFERENCE_COUNTING
|
||||
|
||||
@@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol RiveFileDelegate;
|
||||
@class RiveFileAsset;
|
||||
@class RiveFactory;
|
||||
@class RiveDataBindingViewModel;
|
||||
typedef bool (^LoadAsset)(RiveFileAsset* asset,
|
||||
NSData* data,
|
||||
RiveFactory* factory);
|
||||
@@ -35,6 +36,9 @@ typedef bool (^LoadAsset)(RiveFileAsset* asset,
|
||||
/// Delegate for calling when a file has finished loading
|
||||
@property(weak) id delegate;
|
||||
|
||||
/// The number of view models in the file.
|
||||
@property(nonatomic, readonly) NSUInteger viewModelCount;
|
||||
|
||||
/// Used to manage url sessions Rive, this is to enable testing.
|
||||
- (nullable instancetype)initWithByteArray:(NSArray*)bytes
|
||||
loadCdn:(bool)cdn
|
||||
@@ -105,6 +109,49 @@ typedef bool (^LoadAsset)(RiveFileAsset* asset,
|
||||
/// Returns the names of all artboards in the file.
|
||||
- (NSArray<NSString*>*)artboardNames;
|
||||
|
||||
#pragma mark - Data Binding
|
||||
|
||||
/// Returns a view model from the file by index.
|
||||
///
|
||||
/// The index of a view model starts at 0, where 0 is the first view model
|
||||
/// listed in the editor's "Data" panel from top-to-bottom.
|
||||
///
|
||||
/// Unlike `RiveDataBindingViewModel.Instance`, a strong reference to this model
|
||||
/// does not have to be made.
|
||||
///
|
||||
/// - Parameter index: The index of the view model.
|
||||
///
|
||||
/// - Returns: A view model if one exists by index, otherwise nil.
|
||||
- (nullable RiveDataBindingViewModel*)viewModelAtIndex:(NSUInteger)index;
|
||||
|
||||
/// Returns a view model from the file by name.
|
||||
///
|
||||
/// The name of the view model has to match the name of a view model in the
|
||||
/// editor's "Data" panel.
|
||||
///
|
||||
/// Unlike `RiveDataBindingViewModel.Instance`, a strong reference to this model
|
||||
/// does not have to be made.
|
||||
///
|
||||
/// - Parameter name: The name of the view model.
|
||||
///
|
||||
/// - Returns: A view model if one exists by name, otherwise nil.
|
||||
- (nullable RiveDataBindingViewModel*)viewModelNamed:(nonnull NSString*)name;
|
||||
|
||||
/// Returns the default view model for an artboard.
|
||||
///
|
||||
/// The default view model is the view model selected under the "Data Bind"
|
||||
/// panel for an artboard.
|
||||
///
|
||||
/// Unlike `RiveDataBindingViewModel.Instance`, a strong reference to this model
|
||||
/// does not have to be made.
|
||||
///
|
||||
/// - Parameter artboard: The artboard within the `RiveFile` that contains a
|
||||
/// data binding view model.
|
||||
///
|
||||
/// - Returns: A view model if one exists for the artboard, otherwise nil.
|
||||
- (nullable RiveDataBindingViewModel*)defaultViewModelForArtboard:
|
||||
(RiveArtboard*)artboard;
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#import "rive/assets/audio_asset.hpp"
|
||||
#import "rive/assets/file_asset.hpp"
|
||||
#import "rive/file_asset_loader.hpp"
|
||||
#import "rive/viewmodel/runtime/viewmodel_instance_runtime.hpp"
|
||||
#import "rive/viewmodel/runtime/viewmodel_runtime.hpp"
|
||||
|
||||
#include "rive/open_url_event.hpp"
|
||||
#include "rive/custom_property_boolean.hpp"
|
||||
@@ -43,6 +45,8 @@
|
||||
|
||||
#define RIVE_ENABLE_REFERENCE_COUNTING false
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// MARK: - Public Interfaces
|
||||
|
||||
/*
|
||||
@@ -170,3 +174,62 @@
|
||||
- (instancetype)initWithAudio:(rive::rcp<rive::AudioSource>)audio;
|
||||
- (rive::rcp<rive::AudioSource>)instance;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModel ()
|
||||
- (instancetype)initWithViewModel:(rive::ViewModelRuntime*)viewModel;
|
||||
@end
|
||||
|
||||
@protocol RiveDataBindingViewModelInstancePropertyDelegate
|
||||
- (void)valuePropertyDidAddListener:
|
||||
(RiveDataBindingViewModelInstanceProperty*)value;
|
||||
- (void)valuePropertyDidRemoveListener:
|
||||
(RiveDataBindingViewModelInstanceProperty*)listener
|
||||
isEmpty:(BOOL)isEmpty;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstance ()
|
||||
@property(nonatomic, readonly) rive::ViewModelInstanceRuntime* instance;
|
||||
- (instancetype)initWithInstance:(rive::ViewModelInstanceRuntime*)instance;
|
||||
- (void)cacheProperty:(RiveDataBindingViewModelInstanceProperty*)value
|
||||
withPath:(NSString*)path;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceProperty ()
|
||||
@property(nonatomic, weak) id<RiveDataBindingViewModelInstancePropertyDelegate>
|
||||
valueDelegate;
|
||||
@property(nonatomic, readonly) NSDictionary<NSUUID*, id>* listeners;
|
||||
- (instancetype)initWithValue:(rive::ViewModelInstanceValueRuntime*)value;
|
||||
- (NSUUID*)addListener:(id)listener;
|
||||
- (void)removeListener:(NSUUID*)listener;
|
||||
- (void)handleListeners;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceStringProperty ()
|
||||
- (instancetype)initWithString:(rive::ViewModelInstanceStringRuntime*)string;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceNumberProperty ()
|
||||
- (instancetype)initWithNumber:(rive::ViewModelInstanceNumberRuntime*)number;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceBooleanProperty ()
|
||||
- (instancetype)initWithBoolean:(rive::ViewModelInstanceBooleanRuntime*)boolean;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceColorProperty ()
|
||||
- (instancetype)initWithColor:(rive::ViewModelInstanceColorRuntime*)color;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceEnumProperty ()
|
||||
- (instancetype)initWithEnum:(rive::ViewModelInstanceEnumRuntime*)e;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstanceTriggerProperty ()
|
||||
- (instancetype)initWithTrigger:(rive::ViewModelInstanceTriggerRuntime*)trigger;
|
||||
@end
|
||||
|
||||
@interface RiveDataBindingViewModelInstancePropertyData ()
|
||||
- (instancetype)initWithData:(rive::PropertyData)data;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class RiveSMINumber;
|
||||
@class RiveLayerState;
|
||||
@class RiveEvent;
|
||||
@class RiveDataBindingViewModelInstance;
|
||||
|
||||
/// A type mirroring rive::HitResult, but available in both ObjC and Swift.
|
||||
typedef NS_ENUM(NSInteger, RiveHitResult) { none, hit, hitOpaque };
|
||||
@@ -28,6 +29,10 @@ typedef NS_ENUM(NSInteger, RiveHitResult) { none, hit, hitOpaque };
|
||||
* RiveStateMachineInstance
|
||||
*/
|
||||
@interface RiveStateMachineInstance : NSObject
|
||||
|
||||
@property(nonatomic, nullable, readonly)
|
||||
RiveDataBindingViewModelInstance* viewModelInstance;
|
||||
|
||||
- (NSString*)name;
|
||||
- (bool)advanceBy:(double)elapsedSeconds;
|
||||
- (const RiveSMIBool*)getBool:(NSString*)name;
|
||||
@@ -76,6 +81,19 @@ typedef NS_ENUM(NSInteger, RiveHitResult) { none, hit, hitOpaque };
|
||||
/// location.
|
||||
- (RiveHitResult)touchCancelledAtLocation:(CGPoint)touchLocation;
|
||||
|
||||
// MARK: - Data Binding
|
||||
|
||||
/// Binds an instance of a view model to the state machine for updates.
|
||||
///
|
||||
/// A strong reference to the instance being bound must be made if you wish to
|
||||
/// reuse instance properties, or for observability. By default, the instance
|
||||
/// will also automatically be bound to the artboard containing the state
|
||||
/// machine.
|
||||
///
|
||||
/// - Parameter instance: The instance of a view model to bind.
|
||||
- (void)bindViewModelInstance:(RiveDataBindingViewModelInstance*)instance
|
||||
NS_SWIFT_NAME(bind(viewModelInstance:));
|
||||
|
||||
// MARK: Debug
|
||||
|
||||
#if RIVE_ENABLE_REFERENCE_COUNTING
|
||||
|
||||
@@ -10,12 +10,17 @@ import Foundation
|
||||
import Combine
|
||||
|
||||
@objc open class RiveModel: NSObject, ObservableObject {
|
||||
public typealias AutoBindCallback = (RiveDataBindingViewModel.Instance) -> Void
|
||||
|
||||
// NOTE: the order here determines the order in which memory garbage collected
|
||||
public internal(set) var stateMachine: RiveStateMachineInstance?
|
||||
public internal(set) var animation: RiveLinearAnimationInstance?
|
||||
public private(set) var artboard: RiveArtboard!
|
||||
internal private(set) var riveFile: RiveFile
|
||||
|
||||
public private(set) var riveFile: RiveFile
|
||||
|
||||
private var isAutoBindEnabled = false
|
||||
private var autoBindCallback: AutoBindCallback?
|
||||
|
||||
public init(riveFile: RiveFile) {
|
||||
self.riveFile = riveFile
|
||||
}
|
||||
@@ -59,6 +64,7 @@ import Combine
|
||||
animation = nil
|
||||
artboard = try riveFile.artboard(fromName: name)
|
||||
artboard.__volume = _volume
|
||||
autoBind()
|
||||
}
|
||||
catch { throw RiveModelError.invalidArtboard("Name \(name) not found") }
|
||||
}
|
||||
@@ -72,6 +78,7 @@ import Combine
|
||||
artboard = try riveFile.artboard(from: index)
|
||||
artboard.__volume = _volume
|
||||
RiveLogger.log(model: self, event: .artboardByIndex(index))
|
||||
autoBind()
|
||||
}
|
||||
catch {
|
||||
let errorMessage = "Artboard at index \(index) not found"
|
||||
@@ -84,6 +91,7 @@ import Combine
|
||||
artboard = try riveFile.artboard()
|
||||
artboard.__volume = _volume
|
||||
RiveLogger.log(model: self, event: .defaultArtboard)
|
||||
autoBind()
|
||||
}
|
||||
catch {
|
||||
let errorMessage = "No Default Artboard"
|
||||
@@ -97,6 +105,7 @@ import Combine
|
||||
do {
|
||||
stateMachine = try artboard.stateMachine(fromName: name)
|
||||
RiveLogger.log(model: self, event: .stateMachineByName(name))
|
||||
autoBind()
|
||||
}
|
||||
catch {
|
||||
let errorMessage = "State machine named \(name) not found"
|
||||
@@ -124,6 +133,8 @@ import Combine
|
||||
stateMachine = try artboard.stateMachine(from: 0)
|
||||
RiveLogger.log(model: self, event: .stateMachineByIndex(0))
|
||||
}
|
||||
|
||||
autoBind()
|
||||
}
|
||||
catch {
|
||||
let errorMessage = "State machine at index \(index ?? 0) not found"
|
||||
@@ -158,7 +169,43 @@ import Combine
|
||||
throw RiveModelError.invalidAnimation(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Data Binding
|
||||
|
||||
/// Automatically binds the default instance of the current artboard when the artboard and/or state machine changes,
|
||||
/// including when it is first set. The callback will be called with the instance that has been bound.
|
||||
/// A strong reference to the instance must be made in order to update properties and utilize observability.
|
||||
///
|
||||
/// - Parameter callback: The callback to be called when a `RiveDataBindingViewModel.Instance`
|
||||
/// is bound to the current artboard and/or state machine.
|
||||
@objc open func enableAutoBind(_ callback: @escaping AutoBindCallback) {
|
||||
isAutoBindEnabled = true
|
||||
autoBindCallback = callback
|
||||
|
||||
autoBind()
|
||||
}
|
||||
|
||||
/// Disables the auto-binding featured enabled by `enableAutoBind`.
|
||||
@objc open func disableAutoBind() {
|
||||
isAutoBindEnabled = false
|
||||
autoBindCallback = nil
|
||||
}
|
||||
|
||||
private func autoBind() {
|
||||
// autobind needs at _least_ an artboard
|
||||
guard isAutoBindEnabled,
|
||||
let artboard,
|
||||
let viewModel = riveFile.defaultViewModel(for: artboard),
|
||||
let instance = viewModel.createDefaultInstance()
|
||||
else { return }
|
||||
|
||||
artboard.bind(viewModelInstance: instance)
|
||||
// If, for some reason, there is no state machine (e.g linear animation) then no need to bind
|
||||
stateMachine?.bind(viewModelInstance: instance)
|
||||
|
||||
autoBindCallback?(instance)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public override var description: String {
|
||||
|
||||
@@ -376,6 +376,8 @@ open class RiveView: RiveRendererView {
|
||||
if let delegate = stateMachineDelegate {
|
||||
stateMachine.stateChanges().forEach { delegate.stateMachine?(stateMachine, didChangeState: $0) }
|
||||
}
|
||||
|
||||
stateMachine.viewModelInstance?.updateListeners()
|
||||
} else if let animation = riveModel?.animation {
|
||||
isPlaying = animation.advance(by: delta) && wasPlaying
|
||||
|
||||
|
||||
Reference in New Issue
Block a user