From 53a8e5c7141f0ae0d9862a2d5a02ef285d05edff Mon Sep 17 00:00:00 2001 From: matt Sullivan Date: Fri, 7 May 2021 17:00:06 -0700 Subject: [PATCH] Delegate for loop event --- .../Source/SwiftUI/RiveSwiftUIView.swift | 7 +++- .../Source/SwiftUI/RiveViewWrapper.swift | 33 ++++++++++++++--- Source/Renderer/Rive.h | 2 ++ Source/Renderer/Rive.mm | 8 +++++ Source/Views/RiveView.swift | 35 ++++++++++++++++--- 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/Example-iOS/Source/SwiftUI/RiveSwiftUIView.swift b/Example-iOS/Source/SwiftUI/RiveSwiftUIView.swift index 12ada39d..486b469d 100644 --- a/Example-iOS/Source/SwiftUI/RiveSwiftUIView.swift +++ b/Example-iOS/Source/SwiftUI/RiveSwiftUIView.swift @@ -6,15 +6,20 @@ struct RiveSwiftUIView: View { @State private var isPlaying: Bool = true @State private var fit: Fit = Fit.Cover @State private var alignment: RiveRuntime.Alignment = RiveRuntime.Alignment.Center + @State private var loopCount: Int = 0 var body: some View { ZStack(alignment: .bottomLeading) { UIRiveView( resource: "off_road_car_blog", fit: $fit, - alignment: $alignment + alignment: $alignment, + loopCount: $loopCount ) VStack { + Text("Looped \(loopCount) times") + .foregroundColor(.blue) + .padding() HStack { Button(action: { alignment = RiveRuntime.Alignment.TopLeft }, label: { Text("Top Left") }) diff --git a/Example-iOS/Source/SwiftUI/RiveViewWrapper.swift b/Example-iOS/Source/SwiftUI/RiveViewWrapper.swift index 906fb5f5..fd9843db 100644 --- a/Example-iOS/Source/SwiftUI/RiveViewWrapper.swift +++ b/Example-iOS/Source/SwiftUI/RiveViewWrapper.swift @@ -5,13 +5,17 @@ struct UIRiveView: UIViewRepresentable { let resource: String @Binding var fit: Fit // Binding = Binding.constant(Fit.Contain) @Binding var alignment: RiveRuntime.Alignment + @Binding var loopCount: Int // Constructs the view func makeUIView(context: Context) -> RiveView { - // print("Making Rive View") let riveView = RiveView(riveFile: getRiveFile(resourceName: resource)) - riveView.setFit(fit: Fit.Contain) + riveView.setFit(fit: fit) riveView.setAlignment(alignment: alignment) + + // Set the delegates + riveView.loopDelegate = context.coordinator + return riveView } @@ -22,8 +26,27 @@ struct UIRiveView: UIViewRepresentable { } // Constructs a coordinator for managing updating state -// func makeCoordinator() -> Coordinator { -// Coordinator(self, fit: fit) -// } + func makeCoordinator() -> Coordinator { + Coordinator(loopCount: $loopCount) + } + +} + +// Coordinator between RiveView and UIRiveView +class Coordinator: NSObject, LoopDelegate { + @Binding private var loopCount: Int + init(loopCount: Binding) { + self._loopCount = loopCount + } + + func loop(_ animationName: String, type: Int) { + loopCount += 1 + } + +// @Binding private var fit: Fit +// +// init(fit: Binding) { +// self._fit = fit +// } } diff --git a/Source/Renderer/Rive.h b/Source/Renderer/Rive.h index e25fb288..e3225efb 100644 --- a/Source/Renderer/Rive.h +++ b/Source/Renderer/Rive.h @@ -97,6 +97,8 @@ typedef NS_ENUM(NSInteger, Alignment) { - (void)direction:(int)direction; - (int)loop; - (void)loop:(int)loopMode; +- (bool)didLoop; +- (NSString *)name; @end diff --git a/Source/Renderer/Rive.mm b/Source/Renderer/Rive.mm index e9707810..5494b6e3 100644 --- a/Source/Renderer/Rive.mm +++ b/Source/Renderer/Rive.mm @@ -396,6 +396,14 @@ - (void)loop:(int)loopType { instance->loopValue(loopType); } +- (bool)didLoop { + return instance->didLoop(); +} + +- (NSString *)name { + std::string str = instance->animation()->name(); + return [NSString stringWithCString:str.c_str() encoding:[NSString defaultCStringEncoding]]; +} @end /* diff --git a/Source/Views/RiveView.swift b/Source/Views/RiveView.swift index d75b46ae..337c970f 100644 --- a/Source/Views/RiveView.swift +++ b/Source/Views/RiveView.swift @@ -8,6 +8,11 @@ import UIKit +// Delegate for handling loop events +public protocol LoopDelegate: AnyObject { + func loop(_ animationName: String, type: Int) +} + public class RiveView: UIView { var displayLink: CADisplayLink? @@ -27,6 +32,9 @@ public class RiveView: UIView { var autoPlay: Bool = true var lastTime: CFTimeInterval = 0 + // Delegates + public weak var loopDelegate: LoopDelegate? + public init(riveFile: RiveFile, fit: Fit = Fit.Contain, alignment: Alignment = Alignment.Center) { super.init(frame: .zero) self.configure(withRiveFile: riveFile) @@ -50,10 +58,19 @@ public class RiveView: UIView { open func setFit(fit: Fit){ self.fit = fit } - open func setAlignment(alignment: Alignment){ + open func setAlignment(alignment: Alignment) { self.alignment = alignment } + + @objc func animationWillMoveToBackground() { + print("Triggers when app is moving to background") + } + + @objc func animationWillEnterForeground() { + print("Triggers when app is moving to foreground") + } + /* * Updates the artboard and layout options */ @@ -63,6 +80,12 @@ public class RiveView: UIView { andAnimation animation: String?=nil, andAutoPlay autoPlay: Bool=true ) { + // Testing stuff + NotificationCenter.default.addObserver(self, selector: #selector(animationWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(animationWillMoveToBackground), + name: UIApplication.didEnterBackgroundNotification, object: nil) + // Ensure the view's transparent self.isOpaque = false @@ -177,21 +200,25 @@ public class RiveView: UIView { return } animations.forEach{ animation in - if playingAnimations.contains(animation){ + if playingAnimations.contains(animation) { let stillPlaying = animation.advance(by: delta) animation.apply(to: artboard) if !stillPlaying { playingAnimations.remove(animation) if (animation.loop() == Loop.LoopOneShot.rawValue) { - animations.removeAll(where: {animationInstance in + animations.removeAll(where: { animationInstance in return animationInstance == animation }) } } + // Check if the animation looped and if so, call the delegate + if animation.didLoop() { + loopDelegate?.loop(animation.name(), type: Int(animation.loop())) + } } } stateMachines.forEach{ stateMachine in - if playingStateMachines.contains(stateMachine){ + if playingStateMachines.contains(stateMachine) { let stillPlaying = stateMachine.advance(by: delta) stateMachine.apply(to: artboard) if !stillPlaying {