Skip to content

Commit

Permalink
feature: Add Rive Event bindings to iOS runtime
Browse files Browse the repository at this point in the history
Adds event bindings for iOS/macOS

**Caveat:** tl;dr We may have to explicitly make app owners open URLs for `OpenUrlEvent`, rather than doing it implicitly

Recently, there was a [request for a specific change](#267) to mark `RiveRuntime` package as using application-extension-safe in terms of API usge. This allows RiveRuntime to be used in places other than apps in the Apple ecosystem, which they needed, so that Apple doesn't flag their apps negatively in App Store submissions. Because we mark this setting in build settings for our package, this means we can't use a specific API to access `UIApplication`, which is needed to open URLs from our side (i.e. `UIApplication.shared.open(url)`), thus not allowing us to _directly_ open URLs on `OpenUrlEvent`. Couldn't find a way around this unfortunately, so the onus on opening the URL will be on the consumer when they listen for this event.

Diffs=
b47ff1523 feature: Add Rive Event bindings to iOS runtime (#5899)

Co-authored-by: Zachary Plata <plata.zach@gmail.com>
  • Loading branch information
zplata and zplata committed Sep 13, 2023
1 parent 5bd6438 commit 064fd46
Show file tree
Hide file tree
Showing 16 changed files with 373 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0dcbdade49885fcae4de304972ae37f6283f19d1
b47ff1523153056a3359ad9b65d7dbef212bbd17
2 changes: 1 addition & 1 deletion .rive_renderer
Original file line number Diff line number Diff line change
@@ -1 +1 @@
d7323869190ee9b8819c1ad6824888456d47b9b3
54501530ad9f358b743da5599145c708b04bfcc3
Binary file added Example-iOS/Assets/rating_animation.riv
Binary file not shown.
12 changes: 12 additions & 0 deletions Example-iOS/RiveExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@
E57798AC2A731C5800FF25C3 /* testtext.riv in Resources */ = {isa = PBXBuildFile; fileRef = E577989D2A72C77900FF25C3 /* testtext.riv */; };
E57798AE2A73264100FF25C3 /* text_test_2.riv in Resources */ = {isa = PBXBuildFile; fileRef = E57798AD2A73264100FF25C3 /* text_test_2.riv */; };
E57798AF2A73264100FF25C3 /* text_test_2.riv in Resources */ = {isa = PBXBuildFile; fileRef = E57798AD2A73264100FF25C3 /* text_test_2.riv */; };
E5964AAB2A9CD49200140479 /* SwiftEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5964AAA2A9CD49200140479 /* SwiftEvents.swift */; };
E5964AAC2A9CD49200140479 /* SwiftEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5964AAA2A9CD49200140479 /* SwiftEvents.swift */; };
E5964AB12A9CDB2100140479 /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5964AB02A9CDB2100140479 /* rating_animation.riv */; };
E5964AB22A9CDB2100140479 /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5964AB02A9CDB2100140479 /* rating_animation.riv */; };
E5A7874A27E115170056F24B /* energy_bar_example.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5A7874727E115170056F24B /* energy_bar_example.riv */; };
E5A7874C27E1158E0056F24B /* prop_example.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5A7874B27E1158E0056F24B /* prop_example.riv */; };
E5CD7D7127DC331900BFE5E2 /* SwiftMeshAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5CD7D7027DC331900BFE5E2 /* SwiftMeshAnimation.swift */; };
Expand Down Expand Up @@ -262,6 +266,8 @@
E577989D2A72C77900FF25C3 /* testtext.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = testtext.riv; sourceTree = "<group>"; };
E57798A82A730A9B00FF25C3 /* SwiftTestText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTestText.swift; sourceTree = "<group>"; };
E57798AD2A73264100FF25C3 /* text_test_2.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = text_test_2.riv; sourceTree = "<group>"; };
E5964AAA2A9CD49200140479 /* SwiftEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftEvents.swift; sourceTree = "<group>"; };
E5964AB02A9CDB2100140479 /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = rating_animation.riv; sourceTree = "<group>"; };
E5A7874727E115170056F24B /* energy_bar_example.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = energy_bar_example.riv; sourceTree = "<group>"; };
E5A7874B27E1158E0056F24B /* prop_example.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = prop_example.riv; sourceTree = "<group>"; };
E5CD7D7027DC331900BFE5E2 /* SwiftMeshAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMeshAnimation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -371,6 +377,7 @@
C3E383472837E6B00029D65E /* watch_v1.riv */,
C3ECAC29281837B300A81123 /* leg_day_events_example.riv */,
C3ECAC28281837B300A81123 /* play_button_event_example.riv */,
E5964AB02A9CDB2100140479 /* rating_animation.riv */,
C3ECAC2A281837B300A81123 /* switch_event_example.riv */,
C3ECAC222817BE1100A81123 /* magic_8-ball_v2.riv */,
E577989D2A72C77900FF25C3 /* testtext.riv */,
Expand Down Expand Up @@ -426,6 +433,7 @@
C3E2B58B2833ECFE00A8651B /* SwiftCannonGame.swift */,
C3C074ED28414F4600E8EB33 /* SwiftTestParityAnimSM.swift */,
E57798A82A730A9B00FF25C3 /* SwiftTestText.swift */,
E5964AAA2A9CD49200140479 /* SwiftEvents.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
Expand Down Expand Up @@ -611,6 +619,7 @@
04E51C5A2A151C230075E473 /* flux_capacitor.riv in Resources */,
04E51C5B2A151C230075E473 /* juice_v7.riv in Resources */,
04E51C5C2A151C230075E473 /* life_bar.riv in Resources */,
E5964AB22A9CDB2100140479 /* rating_animation.riv in Resources */,
04E51C5D2A151C230075E473 /* prop_example.riv in Resources */,
04E51C5E2A151C230075E473 /* two_bone_ik.riv in Resources */,
04E51C5F2A151C230075E473 /* energy_bar_example.riv in Resources */,
Expand Down Expand Up @@ -682,6 +691,7 @@
042C88E22644447500E7DBB2 /* f22.riv in Resources */,
046AFA712673AF04004ED497 /* blendmodes.riv in Resources */,
042C88EC2644447500E7DBB2 /* vader.riv in Resources */,
E5964AB12A9CDB2100140479 /* rating_animation.riv in Resources */,
C9C73E9E24FC471E00EF9516 /* Assets.xcassets in Resources */,
0450446126B3F71E007B25CA /* constrained.riv in Resources */,
C9D3DE68264F49F4001BA265 /* life_bar.riv in Resources */,
Expand All @@ -701,6 +711,7 @@
buildActionMask = 2147483647;
files = (
04E51C382A151A1E0075E473 /* ContentView.swift in Sources */,
E5964AAC2A9CD49200140479 /* SwiftEvents.swift in Sources */,
04E51C362A151A1E0075E473 /* Example__macOS_App.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -726,6 +737,7 @@
C3ECAC2F281840A300A81123 /* ClockViewModel.swift in Sources */,
04026DC827CE3EE6002B3DBF /* SwiftLayout.swift in Sources */,
C9C73E9A24FC471E00EF9516 /* SceneDelegate.swift in Sources */,
E5964AAB2A9CD49200140479 /* SwiftEvents.swift in Sources */,
04C4C83E2646FE410047E614 /* StateMachine.swift in Sources */,
C324DB5B2807216B0060589F /* RiveSlider.swift in Sources */,
042C888C2643EEE300E7DBB2 /* Layout.swift in Sources */,
Expand Down
63 changes: 63 additions & 0 deletions Example-iOS/Source/Examples/SwiftUI/SwiftEvents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// SwiftEvents.swift
// RiveExample
//
// Created by Zach Plata on 8/28/23.
// Copyright © 2023 Rive. All rights reserved.
//

import SwiftUI
import RiveRuntime

struct SwiftEvents: DismissableView {
var dismiss: () -> Void = {}
@StateObject private var rvm = RiveEventsVMExample()

var body: some View {
VStack {
rvm.view()
Text("Event Message")
.font(.headline)
.padding(.bottom, 10)
Text(rvm.eventText)
.padding()
.background(rvm.eventText.isEmpty ? Color.clear : Color.black)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}

class RiveEventsVMExample: RiveViewModel {
@Published var eventText = ""

init() {
super.init(fileName: "rating_animation")
}

func view() -> some View {
return super.view().frame(width: 400, height: 400, alignment: .center)
}

@objc func onRiveEventReceived(onRiveEvent riveEvent: RiveEvent) {
debugPrint("Event Name: \(riveEvent.name())")
debugPrint("Event Type: \(riveEvent.type())")
if let openUrlEvent = riveEvent as? RiveOpenUrlEvent {
debugPrint("Open URL Event Properties: \(openUrlEvent.properties())")
if let url = URL(string: openUrlEvent.url()) {
#if os(iOS)
UIApplication.shared.open(url)
#else
NSWorkspace.shared.open(url)
#endif
}
} else if let generalEvent = riveEvent as? RiveGeneralEvent {
let genEventProperties = generalEvent.properties();
debugPrint("General Event Properites: \(genEventProperties)")
if let msg = genEventProperties["message"] {
eventText = msg as! String
}
}

}
}
3 changes: 2 additions & 1 deletion Example-iOS/Source/ExamplesMaster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class ExamplesMasterTableViewController: UITableViewController {
("Cannon Game", typeErased(dismissableView: SwiftCannonGame())),
("State Machine", typeErased(dismissableView: SwiftStateMachine())),
("Mesh Animation", typeErased(dismissableView: SwiftMeshAnimation())),
("Playing with Text", typeErased(dismissableView: TextInputView()))
("Playing with Text", typeErased(dismissableView: TextInputView())),
("Rive Events", typeErased(dismissableView: SwiftEvents()))
]


Expand Down
15 changes: 15 additions & 0 deletions RiveRuntime.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
E57798A62A72C9C500FF25C3 /* RiveTextValueRun.mm in Sources */ = {isa = PBXBuildFile; fileRef = E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */; };
E57798A72A72EEAD00FF25C3 /* RiveTextValueRun.h in Headers */ = {isa = PBXBuildFile; fileRef = E57798A12A72C81F00FF25C3 /* RiveTextValueRun.h */; settings = {ATTRIBUTES = (Public, ); }; };
E57798AB2A7310B700FF25C3 /* testtext.riv in Resources */ = {isa = PBXBuildFile; fileRef = E57798AA2A7310B700FF25C3 /* testtext.riv */; };
E5964A962A965A9300140479 /* RiveEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = E5964A952A965A9300140479 /* RiveEvent.h */; settings = {ATTRIBUTES = (Public, ); }; };
E5964A982A9697B600140479 /* RiveEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5964A972A9697B600140479 /* RiveEvent.mm */; };
E599DCF92AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; };
E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -147,6 +151,9 @@
E57798A12A72C81F00FF25C3 /* RiveTextValueRun.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = RiveTextValueRun.h; sourceTree = "<group>"; };
E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveTextValueRun.mm; sourceTree = "<group>"; };
E57798AA2A7310B700FF25C3 /* testtext.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = testtext.riv; sourceTree = "<group>"; };
E5964A952A965A9300140479 /* RiveEvent.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = RiveEvent.h; sourceTree = "<group>"; };
E5964A972A9697B600140479 /* RiveEvent.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveEvent.mm; sourceTree = "<group>"; };
E599DCF82AAFA06100D1E49A /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = rating_animation.riv; path = "Example-iOS/Assets/rating_animation.riv"; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -185,6 +192,7 @@
E57798A12A72C81F00FF25C3 /* RiveTextValueRun.h */,
83DE4CA62AAAE72000B88B72 /* RenderContext.hh */,
83DE4C922AA8DD9F00B88B72 /* RenderContextManager.h */,
E5964A952A965A9300140479 /* RiveEvent.h */,
);
path = include;
sourceTree = "<group>";
Expand All @@ -199,6 +207,7 @@
04ED72F2299C115100E8DE53 /* empty_animation_state.riv */,
04BE53FF2649403600427B39 /* multiple_animations.riv */,
E57798AA2A7310B700FF25C3 /* testtext.riv */,
E599DCF82AAFA06100D1E49A /* rating_animation.riv */,
04BE54022649403600427B39 /* multiple_state_machines.riv */,
04BE54002649403600427B39 /* multipleartboards.riv */,
04BE53FE2649403600427B39 /* noanimation.riv */,
Expand Down Expand Up @@ -239,6 +248,8 @@
E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */,
04BE5431264D243D00427B39 /* LayerState.mm */,
83DE4C902AA8DD7B00B88B72 /* RenderContextManager.mm */,
274175FC286DB9CE000A60D1 /* cg_skia_factory.cpp */,
E5964A972A9697B600140479 /* RiveEvent.mm */,
);
path = Renderer;
sourceTree = "<group>";
Expand Down Expand Up @@ -320,6 +331,7 @@
E57798A72A72EEAD00FF25C3 /* RiveTextValueRun.h in Headers */,
046FB7F1264EAA60000129B1 /* RiveLinearAnimationInstance.h in Headers */,
04BE5430264D1F4100427B39 /* LayerState.h in Headers */,
E5964A962A965A9300140479 /* RiveEvent.h in Headers */,
C9C741F424FC510200EF9516 /* Rive.h in Headers */,
04BE5436264D2A7500427B39 /* RivePrivateHeaders.h in Headers */,
C9C73EE224FC478900EF9516 /* RiveRuntime.h in Headers */,
Expand Down Expand Up @@ -410,6 +422,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E599DCF92AAFA06100D1E49A /* rating_animation.riv in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -426,6 +439,7 @@
04BE540B2649403600427B39 /* multiple_animations.riv in Resources */,
E57798AB2A7310B700FF25C3 /* testtext.riv in Resources */,
04ED72F3299C115100E8DE53 /* empty_animation_state.riv in Resources */,
E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */,
04BE54062649403600427B39 /* off_road_car_blog.riv in Resources */,
04BE540C2649403600427B39 /* multipleartboards.riv in Resources */,
04BE540D2649403600427B39 /* junk.riv in Resources */,
Expand All @@ -447,6 +461,7 @@
2A707937272628AD00C035A1 /* rive_renderer_view.mm in Sources */,
C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */,
C3468E5827EB9887008652FD /* RiveView.swift in Sources */,
E5964A982A9697B600140479 /* RiveEvent.mm in Sources */,
04BE5434264D267900427B39 /* LayerState.mm in Sources */,
C9601F2B250C25930032AA07 /* RiveRenderer.mm in Sources */,
83DE4C912AA8DD7B00B88B72 /* RenderContextManager.mm in Sources */,
Expand Down
137 changes: 137 additions & 0 deletions Source/Renderer/RiveEvent.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//
// RiveEvent.m
// RiveRuntime
//
// Created by Zach Plata on 8/23/23.
// Copyright © 2023 Rive. All rights reserved.
//

#import <Rive.h>
#import <RivePrivateHeaders.h>


/*
* RiveEvent
*/
@implementation RiveEvent
{
const rive::Event* instance;
float secondsDelay;
}

- (const rive::Event*)getInstance
{
return instance;
}

- (instancetype)initWithRiveEvent:(const rive::Event*)riveEvent
delay:(float)delay
{
if (self = [super init])
{
secondsDelay = delay;
instance = riveEvent;
return self;
}
else
{
return nil;
}
}

- (NSString*)name
{
std::string str = ((const rive::Event*)instance)->name();
return [NSString stringWithCString:str.c_str() encoding:[NSString defaultCStringEncoding]];
}

- (NSInteger)type
{
return ((rive::Event*)[self getInstance])->coreType();
}

- (float)delay
{
return secondsDelay;
}

- (NSDictionary<NSString*, id>*)properties
{
bool hasCustomProperties = false;
NSMutableDictionary<NSString *, id>* customProperties = [NSMutableDictionary dictionary];
for (auto child : ((const rive::Event*)instance)->children())
{
if (child->is<rive::CustomProperty>())
{
std::string eventName = child->name();
if (!eventName.empty())
{
NSString* convertedName = [NSString stringWithCString:eventName.c_str() encoding:[NSString defaultCStringEncoding]];
switch (child->coreType())
{
case rive::CustomPropertyBoolean::typeKey: {
bool customBoolValue = child->as<rive::CustomPropertyBoolean>()->propertyValue();
customProperties[convertedName] = @(customBoolValue);
break;
}
case rive::CustomPropertyString::typeKey: {
std::string customStringValue = child->as<rive::CustomPropertyString>()->propertyValue();
NSString* convertedStringValue = [NSString stringWithCString:customStringValue.c_str() encoding:[NSString defaultCStringEncoding]];
customProperties[convertedName] = convertedStringValue;
break;
}
case rive::CustomPropertyNumber::typeKey: {
float customNumValue = child->as<rive::CustomPropertyNumber>()->propertyValue();
customProperties[convertedName] = @(customNumValue);
break;
}
}
hasCustomProperties = true;
}
}
}
if (hasCustomProperties) {
return customProperties;
}
return nil;
}
@end

/*
* RiveGeneralEvent
*/
@implementation RiveGeneralEvent
@end

/*
* RiveOpenUrlEvent
*/
@implementation RiveOpenUrlEvent
- (NSString*)url
{
std::string str = ((const rive::OpenUrlEvent*)[self getInstance])->url();
return [NSString stringWithCString:str.c_str() encoding:[NSString defaultCStringEncoding]];
}

- (NSString*)target
{
uint32_t targetValue = ((const rive::OpenUrlEvent*)[self getInstance])->targetValue();
std::string targetString;
switch (targetValue)
{
case 0:
targetString = "_blank";
break;
case 1:
targetString = "_parent";
break;
case 2:
targetString = "_self";
break;
case 3:
targetString = "_top";
break;
}
return [NSString stringWithCString:targetString.c_str() encoding:[NSString defaultCStringEncoding]];
}
@end
Loading

0 comments on commit 064fd46

Please sign in to comment.