Add logging to rive-ios

Adds (structured) logging via the iOS `Logger` API. Under the hood, this uses `os_log` for both in-memory and on-disk logging, depending on which level is used; this means _where_ is handled by the system, we just provide the level.

The API is a little interesting; you can't have a "generic" `log` function that takes in the message. iOS requires that you use interpolation when logging.

Logging is structured so that various categories are set under one subsystem. These categories are: view model, state machine, artboard, file, and view. Each of these can log one of debug, info, default, error, and fault levels. The developer can filter which categories and levels can be logged; Xcode also supports filtering within the console.

Logging itself is split into three things: the categories, the levels, and the logging. Within each "category" of logging, there exist events that can be logged. These are enums with associated values. When using Objective-C, there are helper functions that under-the-hood call logging functions with these events. Since there are a few categories, and various events for each category, these categories are split into extensions on `RiveLogger`. At the end of the day, there exists a single log function, which ensures a log category and level are available for logging, and then a log function that is essentially a switch statement on each event, logging the (interpolated) message.

These logging events are then utilized in the files mentioned above; the categories match the files where logging has been added. Primarily setters are called, or errors that may not be handled, but may be useful. Fatal errors are also logged.

When adding new logging:
1. Check if an existing extension exists. If not, create one.
2. Create an enum of possible events.
3. Create a `log` function that takes the model, and the event.
4. Create a `_log` function that verifies that an event has been called.

## Example usage

```swift
// Somewhere early in the app lifecycle…
RiveLogger.isEnabled = true
RiveLogger.isVerbose = true // advances are considered verbose
RiveLogger.levels = [.fatal] // filter for only specific levels, such as fatal errors
RiveLogger.categories = [.stateMachine, .viewModel] // filter for only specific categories
```

Diffs=
e1fc239974 Add logging to rive-ios (#8252)

Co-authored-by: David Skuza <david@rive.app>
This commit is contained in:
dskuza
2024-10-16 14:28:13 +00:00
parent 4fa81c5820
commit c2f8738739
19 changed files with 951 additions and 77 deletions

View File

@@ -1 +1 @@
9d5076b88363c7811a9a70107ee43013b8bfd0cb
e1fc239974df492517f74f30a456f765d4bea96c

View File

@@ -50,6 +50,13 @@
ReferencedContainer = "container:RiveExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "IDEPreferLogStreaming"
value = "YES"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -15,6 +15,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
RenderContextManager.shared().defaultRenderer = RendererType.riveRenderer
RiveLogger.isEnabled = true
RiveLogger.categories = [.viewModel, .model]
RiveLogger.levels = [.debug]
return true
}

View File

@@ -95,8 +95,15 @@
E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; };
F21F08142C66526D00FFA205 /* RiveFallbackFontDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */; };
F23626AA2C8F90FA00727D9A /* nested_text_run.riv in Resources */ = {isa = PBXBuildFile; fileRef = F23626A92C8F90FA00727D9A /* nested_text_run.riv */; };
F2610DD22CA5B4C40090D50B /* RiveLogger+StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD12CA5B4C40090D50B /* RiveLogger+StateMachine.swift */; };
F2610DD42CA5B5460090D50B /* RiveLogger+Artboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD32CA5B5460090D50B /* RiveLogger+Artboard.swift */; };
F2610DD62CA5B5DD0090D50B /* RiveLogger+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD52CA5B5DD0090D50B /* RiveLogger+ViewModel.swift */; };
F2610DD82CA5B6570090D50B /* RiveLogger+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */; };
F2610DDA2CA5B84B0090D50B /* RiveLogger+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */; };
F2610DE12CA5FBE30090D50B /* RiveLogger+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */; };
F28DE4532C5002D900F3C379 /* RiveModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28DE4522C5002D900F3C379 /* RiveModelTests.swift */; };
F2CCA9792C9B2799007DC0D2 /* referenced_image_asset.riv in Resources */ = {isa = PBXBuildFile; fileRef = F2CCA9782C9B2799007DC0D2 /* referenced_image_asset.riv */; };
F2CCA9C22C9E13BA007DC0D2 /* RiveLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2CCA9C12C9E13BA007DC0D2 /* RiveLogger.swift */; };
F2D285492C6D469900728340 /* RiveFallbackFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */; };
F2ECC2312C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */; };
F2ECC23A2C66B949008B20E5 /* RiveFontTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */; };
@@ -203,8 +210,15 @@
E599DCF82AAFA06100D1E49A /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = rating_animation.riv; path = "Example-iOS/Assets/rating_animation.riv"; sourceTree = SOURCE_ROOT; };
F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFallbackFontDescriptor.swift; sourceTree = "<group>"; };
F23626A92C8F90FA00727D9A /* nested_text_run.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = nested_text_run.riv; sourceTree = "<group>"; };
F2610DD12CA5B4C40090D50B /* RiveLogger+StateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+StateMachine.swift"; sourceTree = "<group>"; };
F2610DD32CA5B5460090D50B /* RiveLogger+Artboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+Artboard.swift"; sourceTree = "<group>"; };
F2610DD52CA5B5DD0090D50B /* RiveLogger+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+ViewModel.swift"; sourceTree = "<group>"; };
F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+Model.swift"; sourceTree = "<group>"; };
F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+File.swift"; sourceTree = "<group>"; };
F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+View.swift"; sourceTree = "<group>"; };
F28DE4522C5002D900F3C379 /* RiveModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveModelTests.swift; sourceTree = "<group>"; };
F2CCA9782C9B2799007DC0D2 /* referenced_image_asset.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = referenced_image_asset.riv; sourceTree = "<group>"; };
F2CCA9C12C9E13BA007DC0D2 /* RiveLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveLogger.swift; sourceTree = "<group>"; };
F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFallbackFontProvider.swift; sourceTree = "<group>"; };
F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveFallbackFontDescriptor+Extensions.swift"; sourceTree = "<group>"; };
F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFontTests.swift; sourceTree = "<group>"; };
@@ -370,6 +384,7 @@
C9C73ED324FC478800EF9516 /* Source */ = {
isa = PBXGroup;
children = (
F2CCA9C02C9E13B2007DC0D2 /* Logging */,
C3468E5727EB9887008652FD /* RiveView.swift */,
C3468E5927ECC7C6008652FD /* RiveViewModel.swift */,
C3468E5B27ED4C41008652FD /* RiveModel.swift */,
@@ -416,6 +431,20 @@
path = Fonts;
sourceTree = "<group>";
};
F2CCA9C02C9E13B2007DC0D2 /* Logging */ = {
isa = PBXGroup;
children = (
F2CCA9C12C9E13BA007DC0D2 /* RiveLogger.swift */,
F2610DD52CA5B5DD0090D50B /* RiveLogger+ViewModel.swift */,
F2610DD12CA5B4C40090D50B /* RiveLogger+StateMachine.swift */,
F2610DD32CA5B5460090D50B /* RiveLogger+Artboard.swift */,
F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */,
F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */,
F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */,
);
path = Logging;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -581,17 +610,22 @@
2A707937272628AD00C035A1 /* rive_renderer_view.mm in Sources */,
C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */,
C3468E5827EB9887008652FD /* RiveView.swift in Sources */,
F2610DD82CA5B6570090D50B /* RiveLogger+Model.swift in Sources */,
F2610DD62CA5B5DD0090D50B /* RiveLogger+ViewModel.swift in Sources */,
E5964A982A9697B600140479 /* RiveEvent.mm in Sources */,
F21F08142C66526D00FFA205 /* RiveFallbackFontDescriptor.swift in Sources */,
F2CCA9C22C9E13BA007DC0D2 /* RiveLogger.swift in Sources */,
043025F82AFA46EF00320F2E /* CDNFileAssetLoader.mm in Sources */,
043026042AFBA04100320F2E /* RiveFactory.mm in Sources */,
04BE5434264D267900427B39 /* LayerState.mm in Sources */,
F2610DDA2CA5B84B0090D50B /* RiveLogger+File.swift in Sources */,
C9601F2B250C25930032AA07 /* CoreGraphicsRenderer.mm in Sources */,
C9601F2B250C25930032AA07 /* CoreGraphicsRenderer.mm in Sources */,
043025F42AF90EAC00320F2E /* RiveFileAssetLoader.mm in Sources */,
F2D285492C6D469900728340 /* RiveFallbackFontProvider.swift in Sources */,
043025FC2AFA862E00320F2E /* FileAssetLoaderAdapter.mm in Sources */,
83DE4C912AA8DD7B00B88B72 /* RenderContextManager.mm in Sources */,
F2610DE12CA5FBE30090D50B /* RiveLogger+View.swift in Sources */,
C3E2B580282F242400A8651B /* RiveStateMachineInstance+Extensions.swift in Sources */,
046FB7F5264EAA60000129B1 /* RiveSMIInput.mm in Sources */,
043026002AFA915B00320F2E /* RiveFileAsset.mm in Sources */,
@@ -599,10 +633,12 @@
C3745FD3282BFAB90087F4AF /* FPSCounterView.swift in Sources */,
F2ECC2312C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift in Sources */,
C9C741F524FC510200EF9516 /* Rive.mm in Sources */,
F2610DD42CA5B5460090D50B /* RiveLogger+Artboard.swift in Sources */,
046FB7F8264EAA60000129B1 /* RiveStateMachineInstance.mm in Sources */,
C3468E5C27ED4C41008652FD /* RiveModel.swift in Sources */,
046FB7FF264EAA61000129B1 /* RiveFile.mm in Sources */,
046FB7F2264EAA60000129B1 /* RiveArtboard.mm in Sources */,
F2610DD22CA5B4C40090D50B /* RiveLogger+StateMachine.swift in Sources */,
046FB7F4264EAA60000129B1 /* RiveLinearAnimationInstance.mm in Sources */,
E57798A62A72C9C500FF25C3 /* RiveTextValueRun.mm in Sources */,
);

View File

@@ -0,0 +1,54 @@
//
// RiveLogger+Artboard.swift
// RiveRuntime
//
// Created by David Skuza on 9/26/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
enum RiveLoggerArtboardEvent {
case advance(Double)
case error(String)
}
extension RiveLogger {
private static let artboard = Logger(subsystem: subsystem, category: "rive-artboard")
@objc(logArtboard:advance:) static func log(artboard: RiveArtboard, advance: Double) {
log(artboard: artboard, event: .advance(advance))
}
@objc(logArtboard:error:) static func log(artboard: RiveArtboard, error: String) {
log(artboard: artboard, event: .error(error))
}
static func log(artboard: RiveArtboard, event: RiveLoggerArtboardEvent) {
switch event {
case .advance(let elapsed):
guard isVerbose else { return }
_log(event: event, level: .debug) {
Self.artboard.debug("\(self.prefix(for: artboard))Advanced by \(elapsed)s")
}
case .error(let error):
_log(event: event, level: .error) {
Self.artboard.error("\(error)")
}
}
}
private static func _log(event: RiveLoggerArtboardEvent, level: RiveLogLevel, log: () -> Void) {
guard isEnabled,
categories.contains(.artboard),
levels.contains(level)
else { return }
log()
}
private static func prefix(for artboard: RiveArtboard) -> String {
return "\(artboard.name()): "
}
}

View File

@@ -0,0 +1,99 @@
//
// RiveLogger+File.swift
// RiveRuntime
//
// Created by David Skuza on 9/26/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
enum RiveLoggerFileEvent {
case fatalError(String)
case error(String)
case loadingAsset(RiveFileAsset)
case loadedFontAssetFromURL(URL, RiveFontAsset)
case loadedImageAssetFromURL(URL, RiveImageAsset)
case loadedAsset(RiveFileAsset)
case loadedFromURL(URL)
case loadingFromResource(String)
}
extension RiveLogger {
private static let file = Logger(subsystem: subsystem, category: "rive-file")
@objc(logFile:error:) static func log(file: RiveFile?, error message: String) {
log(file: file, event: .error(message))
}
@objc(logLoadingAsset:) static func log(loadingAsset asset: RiveFileAsset) {
log(file: nil, event: .loadingAsset(asset))
}
@objc(logFontAssetLoad:fromURL:) static func log(fontAssetLoad fontAsset: RiveFontAsset, from url: URL) {
log(file: nil, event: .loadedFontAssetFromURL(url, fontAsset))
}
@objc(logImageAssetLoad:fromURL:) static func log(imageAssetLoad imageAsset: RiveImageAsset, from url: URL) {
log(file: nil, event: .loadedImageAssetFromURL(url, imageAsset))
}
@objc(logAssetLoaded:) static func log(assetLoaded asset: RiveFileAsset) {
log(file: nil, event: .loadedAsset(asset))
}
@objc(logLoadedFromURL:) static func log(loadedFromURL url: URL) {
log(file: nil, event: .loadedFromURL(url))
}
@objc(logLoadingFromResource:) static func log(loadingFromResource name: String) {
log(file: nil, event: .loadingFromResource(name))
}
static func log(file: RiveFile?, event: RiveLoggerFileEvent) {
switch event {
case .fatalError(let message):
_log(event: event, level: .fault) {
Self.file.fault("\(message)")
}
case .error(let message):
_log(event: event, level: .error) {
Self.file.error("\(message)")
}
case .loadingAsset(let asset):
_log(event: event, level: .debug) {
Self.file.debug("Loading asset \(asset.name())")
}
case .loadedAsset(let asset):
_log(event: event, level: .debug) {
Self.file.debug("Loaded asset \(asset.name())")
}
case .loadedFontAssetFromURL(let url, let asset):
_log(event: event, level: .debug) {
Self.file.debug("Loaded font asset \(asset.name()) from URL: \(url)")
}
case .loadedImageAssetFromURL(let url, let asset):
_log(event: event, level: .debug) {
Self.file.debug("Loaded image asset \(asset.name()) from URL: \(url)")
}
case .loadedFromURL(let url):
_log(event: event, level: .debug) {
Self.file.debug("Loaded file \(url)")
}
case .loadingFromResource(let name):
_log(event: event, level: .debug) {
Self.file.debug("Loading resource \(name)")
}
}
}
private static func _log(event: RiveLoggerFileEvent, level: RiveLogLevel, log: () -> Void) {
guard isEnabled,
categories.contains(.file),
levels.contains(level)
else { return }
log()
}
}

View File

@@ -0,0 +1,112 @@
//
// RiveLogger+Model.swift
// RiveRuntime
//
// Created by David Skuza on 9/26/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
enum RiveLoggerModelEvent {
case volume(Float)
case artboardByName(String)
case artboardByIndex(Int)
case defaultArtboard
case error(String)
case stateMachineByName(String)
case stateMachineByIndex(Int)
case defaultStateMachine
case animationByName(String)
case animationByIndex(Int)
}
extension RiveLogger {
private static let model = Logger(subsystem: subsystem, category: "rive-model")
static func log(model: RiveModel, event: RiveLoggerModelEvent) {
switch event {
case .volume(let volume):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))Volume set to \(volume)"
)
}
case .artboardByName(let name):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))Artboard set to artboard named \(name)"
)
}
case .artboardByIndex(let index):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))Artboard set to artboard at index \(index)"
)
}
case .defaultArtboard:
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))Artboard set to default artboard"
)
}
case .error(let message):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))\(message)"
)
}
case .stateMachineByName(let name):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))State machine set to state machine named \(name)"
)
}
case .stateMachineByIndex(let index):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))State machine set to state machine at index \(index)"
)
}
case .defaultStateMachine:
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))State machine set to default state machine"
)
}
case .animationByName(let name):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))Animation set to animation named \(name)"
)
}
case .animationByIndex(let index):
_log(event: event, level: .debug) {
Self.model.debug(
"\(self.prefix(for: model))Animation set to animation at index \(index)"
)
}
}
}
private static func _log(event: RiveLoggerModelEvent, level: RiveLogLevel, log: () -> Void) {
guard isEnabled,
categories.contains(.model),
levels.contains(level)
else { return }
log()
}
private static func prefix(for model: RiveModel) -> String {
if let stateMachine = model.stateMachine {
return "[\(stateMachine.name())]: "
} else if let animation = model.animation {
return "[\(animation.name())]: "
} else {
return ""
}
}
}

View File

@@ -0,0 +1,59 @@
//
// RiveLogger+StateMachine.swift
// RiveRuntime
//
// Created by David Skuza on 9/26/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
enum RiveLoggerStateMachineEvent {
case advance(Double)
case eventReceived(RiveEvent)
case error(String)
}
extension RiveLogger {
private static let stateMachine = Logger(subsystem: subsystem, category: "rive-state-machine")
@objc(logStateMachine:advance:) static func log(stateMachine: RiveStateMachineInstance, advance: Double) {
log(stateMachine: stateMachine, event: .advance(advance))
}
@objc(logStateMachine:error:) static func log(stateMachine: RiveStateMachineInstance, error: String) {
log(stateMachine: stateMachine, event: .error(error))
}
static func log(stateMachine: RiveStateMachineInstance, event: RiveLoggerStateMachineEvent) {
switch event {
case .advance(let elapsed):
guard isVerbose else { return }
_log(event: event, level: .debug) {
Self.stateMachine.debug("\(self.prefix(for: stateMachine))Advancing by \(elapsed)s")
}
case .eventReceived(let receivedEvent):
_log(event: event, level: .debug) {
Self.stateMachine.debug("\(self.prefix(for: stateMachine))Received event \(receivedEvent.name())")
}
case .error(let error):
_log(event: event, level: .error) {
Self.stateMachine.error("\(error)")
}
}
}
private static func _log(event: RiveLoggerStateMachineEvent, level: RiveLogLevel, log: () -> Void) {
guard isEnabled,
categories.contains(.stateMachine),
levels.contains(level)
else { return }
log()
}
private static func prefix(for stateMachine: RiveStateMachineInstance) -> String {
return "\(stateMachine.name()): "
}
}

View File

@@ -0,0 +1,99 @@
//
// RiveLogger+View.swift
// RiveRuntime
//
// Created by David Skuza on 9/26/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
enum RiveLoggerViewEvent {
case touchBegan(CGPoint)
case touchMoved(CGPoint)
case touchEnded(CGPoint)
case touchCancelled(CGPoint)
case play
case pause
case stop
case reset
case advance(Double)
case eventReceived(String)
case drawing(CGSize)
}
extension RiveLogger {
private static let view = Logger(subsystem: subsystem, category: "rive-view")
static func log(view: RiveView, event: RiveLoggerViewEvent) {
switch event {
case .touchBegan(let point):
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Touch began at {\(point.x),\(point.y)}")
}
case .touchMoved(let point):
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Touch moved to {\(point.x),\(point.y)}")
}
case .touchEnded(let point):
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Touch ended at {\(point.x),\(point.y)}")
}
case .touchCancelled(let point):
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Touch cancelled at {\(point.x),\(point.y)}")
}
case .play:
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Playing")
}
case .pause:
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Paused")
}
case .stop:
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Stopped")
}
case .reset:
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Reset")
}
case .advance(let elapsed):
guard isVerbose else { return }
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Advancing by \(elapsed)s")
}
case .eventReceived(let name):
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Received event \(name)")
}
case .drawing(let size):
guard isVerbose else { return }
_log(event: event, level: .debug) {
Self.view.debug("\(self.prefix(for: view))Drawing size {\(size.width),\(size.height)}")
}
}
}
private static func _log(event: RiveLoggerViewEvent, level: RiveLogLevel, log: () -> Void) {
guard isEnabled,
categories.contains(.view),
levels.contains(level)
else { return }
log()
}
private static func prefix(for view: RiveView) -> String {
if let stateMachine = view.riveModel?.stateMachine {
return "[\(stateMachine.name())]: "
} else if let animation = view.riveModel?.animation {
return "[\(animation.name())]: "
} else {
return ""
}
}
}

View File

@@ -0,0 +1,111 @@
//
// RiveLogger+ViewModel.swift
// RiveRuntime
//
// Created by David Skuza on 9/26/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
enum RiveLoggerViewModelEvent {
case booleanInput(String, String?, Bool)
case floatInput(String, String?, Float)
case doubleInput(String, String?, Double)
case triggerInput(String, String?)
case textRun(String, String?, String)
case error(String)
case fatalError(String)
case play
case pause
case stop
case reset
}
extension RiveLogger {
private static let viewModel = Logger(subsystem: subsystem, category: "rive-view-model")
static func log(viewModel: RiveViewModel, event: RiveLoggerViewModelEvent) {
switch event {
case .booleanInput(let name, let path, let value):
_log(event: event, level: .debug) {
let atPath = path != nil ? " at path \(path!) " : " "
Self.viewModel.debug(
"\(self.prefix(for: viewModel))Input \(name)\(atPath)set to \(value)"
)
}
case .floatInput(let name, let path, let value):
_log(event: event, level: .debug) {
let atPath = path != nil ? " at path \(path!) " : " "
Self.viewModel.debug(
"\(self.prefix(for: viewModel))Input \(name)\(atPath)set to \(value)"
)
}
case .doubleInput(let name, let path, let value):
_log(event: event, level: .debug) {
let atPath = path != nil ? " at path \(path!) " : " "
Self.viewModel.debug(
"\(self.prefix(for: viewModel))Input \(name)\(atPath)set to \(value)"
)
}
case .triggerInput(let name, let path):
_log(event: event, level: .debug) {
let atPath = path != nil ? " at path: \(path!) " : " "
Self.viewModel.debug(
"\(self.prefix(for: viewModel))Input \(name)\(atPath) triggered"
)
}
case .textRun(let name, let path, let value):
_log(event: event, level: .debug) {
let atPath = path != nil ? " at path \(path!) " : " "
Self.viewModel.debug(
"\(self.prefix(for: viewModel))Text run \(name)\(atPath)set to \(value)"
)
}
case .error(let message):
_log(event: event, level: .error) {
Self.viewModel.error("\(self.prefix(for: viewModel))\(message)")
}
case .fatalError(let message):
_log(event: event, level: .fault) {
Self.viewModel.fault("\(self.prefix(for: viewModel))\(message)")
}
case .play:
_log(event: event, level: .debug) {
Self.viewModel.debug("\(self.prefix(for: viewModel))Playing")
}
case .pause:
_log(event: event, level: .debug) {
Self.viewModel.debug("\(self.prefix(for: viewModel))Paused")
}
case .stop:
_log(event: event, level: .debug) {
Self.viewModel.debug("\(self.prefix(for: viewModel))Stopped")
}
case .reset:
_log(event: event, level: .debug) {
Self.viewModel.debug("\(self.prefix(for: viewModel))Reset")
}
}
}
static private func _log(event: RiveLoggerViewModelEvent, level: RiveLogLevel, log: () -> Void) {
guard isEnabled,
categories.contains(.viewModel),
levels.contains(level)
else { return }
log()
}
private static func prefix(for viewModel: RiveViewModel) -> String {
if let stateMachine = viewModel.riveModel?.stateMachine {
return "[\(stateMachine.name())]: "
} else if let animation = viewModel.riveModel?.animation {
return "[\(animation.name())]: "
} else {
return ""
}
}
}

View File

@@ -0,0 +1,99 @@
//
// RiveLogger.swift
// RiveRuntime
//
// Created by David Skuza on 9/20/24.
// Copyright © 2024 Rive. All rights reserved.
//
import Foundation
import OSLog
// MARK: - RiveLogLevel
/// An option set of possible log levels, checked when attempting to log.
@objc public class RiveLogLevel: NSObject, OptionSet {
public var rawValue: Int
public required init(rawValue: Int) {
self.rawValue = rawValue
}
/// A log level that captures debug information
@objc public static let debug = RiveLogLevel(rawValue: 1 << 0)
/// A log level that captures additional information.
@objc public static let info = RiveLogLevel(rawValue: 1 << 1)
/// The default log level.
@objc public static let `default` = RiveLogLevel(rawValue: 1 << 2)
/// A log level that captures an error.
@objc public static let error = RiveLogLevel(rawValue: 1 << 3)
/// A log level that captures a fatal error, or fault.
@objc public static let fault = RiveLogLevel(rawValue: 1 << 4)
/// An option set containing no levels.
@objc public static let none: RiveLogLevel = []
/// An option set containing all possible levels.
@objc public static let all: RiveLogLevel = [.debug, .info, .default, .error, .fault]
override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? RiveLogLevel else { return false }
return rawValue == other.rawValue
}
public override var hash: Int {
return rawValue
}
}
// MARK: - RiveLogCategory
@objc public class RiveLogCategory: NSObject, OptionSet {
public var rawValue: Int
public required init(rawValue: Int) {
self.rawValue = rawValue
}
/// The category used when logging from a Rive state machine.
@objc public static let stateMachine = RiveLogCategory(rawValue: 1 << 0)
/// The category used when logging from a Rive artboard.
@objc public static let artboard = RiveLogCategory(rawValue: 1 << 1)
/// The category used when logging from a Rive view model.
@objc public static let viewModel = RiveLogCategory(rawValue: 1 << 2)
/// The category used when logging from a Rive model.
@objc public static let model = RiveLogCategory(rawValue: 1 << 3)
/// The category used when logging from a Rive file.
@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)
/// 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]
override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? RiveLogCategory else { return false }
return rawValue == other.rawValue
}
public override var hash: Int {
return rawValue
}
}
// MARK: - RiveLogger
@objc public final class RiveLogger: NSObject {
static let subsystem = Bundle(for: RiveLogger.self).bundleIdentifier!
/// A Bool indicating whether logging is enabled or not.
@objc public static var isEnabled = false
/// A Bool indicating whether verbose logs are enabled or not.
/// - Note: Logs that emit a constant stream of information, such as state machine advances, are considererd verbose.
@objc public static var isVerbose = false
/// A set of levels that should be logged. Only used when `isEnabled` is set to `true`.
@objc public static var levels: RiveLogLevel = .all
/// A set of categories that should be logged. Only used when `isEnabled` is set to `true`.
@objc public static var categories: RiveLogCategory = .all
}

View File

@@ -9,6 +9,7 @@
#import <RiveFileAsset.h>
#import <RiveFactory.h>
#import <CDNFileAssetLoader.h>
#import <RiveRuntime/RiveRuntime-Swift.h>
@implementation CDNFileAssetLoader
{}
@@ -38,14 +39,26 @@
if ([asset isKindOfClass:[RiveFontAsset class]])
{
[(RiveFontAsset*)asset font:[factory decodeFont:data]];
RiveFontAsset* fontAsset = (RiveFontAsset*)asset;
[fontAsset font:[factory decodeFont:data]];
[RiveLogger logFontAssetLoad:fontAsset fromURL:URL];
}
else if ([asset isKindOfClass:[RiveImageAsset class]])
{
[(RiveImageAsset*)asset
renderImage:[factory decodeImage:data]];
RiveImageAsset* imageAsset = (RiveImageAsset*)asset;
[imageAsset renderImage:[factory decodeImage:data]];
[RiveLogger logImageAssetLoad:imageAsset fromURL:URL];
}
}
else
{
NSString* message =
[NSString stringWithFormat:
@"Failed to load asset from URL %@: %@",
URL.absoluteString,
error.localizedDescription];
[RiveLogger logFile:nil error:message];
}
}];
// Kick off the http download
@@ -108,7 +121,13 @@
andData:(NSData*)data
andFactory:(RiveFactory*)factory
{
return _loadAsset(asset, data, factory);
[RiveLogger logLoadingAsset:asset];
bool loaded = _loadAsset(asset, data, factory);
if (loaded)
{
[RiveLogger logAssetLoaded:asset];
}
return loaded;
}
@end

View File

@@ -8,6 +8,7 @@
#import <Rive.h>
#import <RivePrivateHeaders.h>
#import <RiveRuntime/RiveRuntime-Swift.h>
// MARK: - Globals
@@ -229,6 +230,7 @@ static int artInstanceCount = 0;
- (void)advanceBy:(double)elapsedSeconds
{
[RiveLogger logArtboard:self advance:elapsedSeconds];
_artboardInstance->advance(elapsedSeconds);
}
@@ -299,6 +301,13 @@ static int artInstanceCount = 0;
rive::SMIBool* smi = _artboardInstance->getBool(stdName, stdPath);
if (smi == nullptr)
{
[RiveLogger
logArtboard:self
error:[NSString
stringWithFormat:
@"Could not find input named %@ at path %@",
name,
path]];
return NULL;
}
else
@@ -325,6 +334,13 @@ static int artInstanceCount = 0;
rive::SMITrigger* smi = _artboardInstance->getTrigger(stdName, stdPath);
if (smi == nullptr)
{
[RiveLogger
logArtboard:self
error:[NSString
stringWithFormat:
@"Could not find input named %@ at path %@",
name,
path]];
return NULL;
}
else
@@ -351,6 +367,13 @@ static int artInstanceCount = 0;
rive::SMINumber* smi = _artboardInstance->getNumber(stdName, stdPath);
if (smi == nullptr)
{
[RiveLogger
logArtboard:self
error:[NSString
stringWithFormat:
@"Could not find input named %@ at path %@",
name,
path]];
return NULL;
}
else

View File

@@ -12,6 +12,7 @@
#import <RenderContextManager.h>
#import <RiveFileAssetLoader.h>
#import <CDNFileAssetLoader.h>
#import <RiveRuntime/RiveRuntime-Swift.h>
#import <FileAssetLoaderAdapter.hpp>
@@ -180,6 +181,10 @@
// QUESTION: good ideas on how we can combine a few of these into following
// the same path better?
// there's a lot of copy pasta here.
[RiveLogger logLoadingFromResource:[NSString stringWithFormat:@"%@.%@",
resourceName,
extension]];
NSString* filepath = [[NSBundle mainBundle] pathForResource:resourceName
ofType:extension];
NSURL* fileUrl = [NSURL fileURLWithPath:filepath];
@@ -222,6 +227,9 @@
customAssetLoader:(nonnull LoadAsset)customAssetLoader
error:(NSError* __autoreleasing _Nullable* _Nullable)error
{
[RiveLogger logLoadingFromResource:[NSString stringWithFormat:@"%@.%@",
resourceName,
extension]];
NSString* filepath = [[NSBundle mainBundle] pathForResource:resourceName
ofType:extension];
NSURL* fileUrl = [NSURL fileURLWithPath:filepath];
@@ -253,6 +261,7 @@
customAssetLoader:(nonnull LoadAsset)customAssetLoader
withDelegate:(nonnull id<RiveFileDelegate>)delegate
{
[RiveLogger logLoadingFromResource:url];
self.isLoaded = false;
if (self = [super init])
{
@@ -280,6 +289,7 @@
customAssetLoader:customAssetLoader
error:&error];
self.isLoaded = true;
[RiveLogger logLoadedFromURL:URL];
dispatch_async(dispatch_get_main_queue(), ^{
if ([[NSThread currentThread] isMainThread])
{
@@ -292,6 +302,14 @@
}
});
}
else
{
NSString* message = [NSString
stringWithFormat:@"Failed to load file from URL %@: %@",
URL.absoluteString,
error.localizedDescription];
[RiveLogger logFile:nil error:message];
}
}];
// Kick off the http download
@@ -354,32 +372,41 @@
switch (result)
{
case rive::ImportResult::unsupportedVersion:
{
NSString* message = @"Unsupported Rive File Version";
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveUnsupportedVersion
userInfo:@{
NSLocalizedDescriptionKey :
@"Unsupported Rive File Version",
NSLocalizedDescriptionKey : message,
@"name" : @"UnsupportedVersion"
}];
break;
}
case rive::ImportResult::malformed:
*error = [NSError
errorWithDomain:RiveErrorDomain
code:RiveMalformedFile
userInfo:@{
NSLocalizedDescriptionKey : @"Malformed Rive File.",
@"name" : @"Malformed"
}];
{
NSString* message = @"Malformed Rive File.";
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveMalformedFile
userInfo:@{
NSLocalizedDescriptionKey : message,
@"name" : @"Malformed"
}];
break;
}
default:
{
NSString* message = @"Unknown error loading file.";
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveUnknownError
userInfo:@{
NSLocalizedDescriptionKey :
@"Unknown error loading file.",
NSLocalizedDescriptionKey : message,
@"name" : @"Unknown"
}];
break;
}
}
return false;
}
@@ -389,13 +416,14 @@
auto artboard = riveFile->artboardDefault();
if (artboard == nullptr)
{
*error = [NSError
errorWithDomain:RiveErrorDomain
code:RiveNoArtboardsFound
userInfo:@{
NSLocalizedDescriptionKey : @"No Artboards Found.",
@"name" : @"NoArtboardsFound"
}];
NSString* message = @"No Artboards Found.";
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardsFound
userInfo:@{
NSLocalizedDescriptionKey : message,
@"name" : @"NoArtboardsFound"
}];
return nil;
}
else
@@ -414,15 +442,15 @@
auto artboard = riveFile->artboardAt(index);
if (artboard == nullptr)
{
*error = [NSError
errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"No Artboard Found at index %ld.",
(long)index],
@"name" : @"NoArtboardFound"
}];
NSString* message = [NSString
stringWithFormat:@"No Artboard Found at index %ld.", (long)index];
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : message,
@"name" : @"NoArtboardFound"
}];
return nil;
}
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
@@ -434,15 +462,15 @@
auto artboard = riveFile->artboardNamed(stdName);
if (artboard == nullptr)
{
*error = [NSError
errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"No Artboard Found with name %@.",
name],
@"name" : @"NoArtboardFound"
}];
NSString* message = [NSString
stringWithFormat:@"No Artboard Found with name %@.", name];
[RiveLogger logFile:nil error:message];
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : message,
@"name" : @"NoArtboardFound"
}];
return nil;
}
else

View File

@@ -8,6 +8,7 @@
#import <Rive.h>
#import <RivePrivateHeaders.h>
#import <RiveRuntime/RiveRuntime-Swift.h>
// MARK: - Globals
@@ -103,6 +104,7 @@ RiveHitResult RiveHitResultFromRuntime(rive::HitResult result)
- (bool)advanceBy:(double)elapsedSeconds
{
[RiveLogger logStateMachine:self advance:elapsedSeconds];
return instance->advanceAndApply(elapsedSeconds);
}
@@ -121,6 +123,11 @@ RiveHitResult RiveHitResultFromRuntime(rive::HitResult result)
rive::SMIBool* smi = instance->getBool(stdName);
if (smi == nullptr)
{
[RiveLogger
logStateMachine:self
error:[NSString
stringWithFormat:
@"Could not find input named %@", name]];
return NULL;
}
else
@@ -145,6 +152,11 @@ RiveHitResult RiveHitResultFromRuntime(rive::HitResult result)
rive::SMITrigger* smi = instance->getTrigger(stdName);
if (smi == nullptr)
{
[RiveLogger
logStateMachine:self
error:[NSString
stringWithFormat:
@"Could not find trigger named %@", name]];
return NULL;
}
else
@@ -169,6 +181,11 @@ RiveHitResult RiveHitResultFromRuntime(rive::HitResult result)
rive::SMINumber* smi = instance->getNumber(stdName);
if (smi == nullptr)
{
[RiveLogger
logStateMachine:self
error:[NSString
stringWithFormat:
@"Could not find input named %@", name]];
return NULL;
}
else

View File

@@ -43,6 +43,7 @@ import Combine
return _volume
}
set {
RiveLogger.log(model: self, event: .volume(newValue))
_volume = newValue
artboard?.__volume = newValue
}
@@ -53,6 +54,7 @@ import Combine
/// Sets a new Artboard and makes the current StateMachine and Animation nil
open func setArtboard(_ name: String) throws {
do {
RiveLogger.log(model: self, event: .artboardByName(name))
stateMachine = nil
animation = nil
artboard = try riveFile.artboard(fromName: name)
@@ -69,21 +71,38 @@ import Combine
animation = nil
artboard = try riveFile.artboard(from: index)
artboard.__volume = _volume
RiveLogger.log(model: self, event: .artboardByIndex(index))
}
catch {
let errorMessage = "Artboard at index \(index) not found"
RiveLogger.log(model: self, event: .error(errorMessage))
throw RiveModelError.invalidArtboard(errorMessage)
}
catch { throw RiveModelError.invalidArtboard("Index \(index) not found") }
} else {
// This tries to find the 'default' Artboard
do {
artboard = try riveFile.artboard()
artboard.__volume = _volume
RiveLogger.log(model: self, event: .defaultArtboard)
}
catch {
let errorMessage = "No Default Artboard"
RiveLogger.log(model: self, event: .error(errorMessage))
throw RiveModelError.invalidArtboard(errorMessage)
}
catch { throw RiveModelError.invalidArtboard("No Default Artboard") }
}
}
open func setStateMachine(_ name: String) throws {
do { stateMachine = try artboard.stateMachine(fromName: name) }
catch { throw RiveModelError.invalidStateMachine("Name \(name) not found") }
do {
stateMachine = try artboard.stateMachine(fromName: name)
RiveLogger.log(model: self, event: .stateMachineByName(name))
}
catch {
let errorMessage = "State machine named \(name) not found"
RiveLogger.log(model: self, event: .error(errorMessage))
throw RiveModelError.invalidStateMachine(errorMessage)
}
}
open func setStateMachine(_ index: Int? = nil) throws {
@@ -91,32 +110,53 @@ import Combine
// Set by index
if let index = index {
stateMachine = try artboard.stateMachine(from: index)
RiveLogger.log(model: self, event: .stateMachineByIndex(index))
}
// Set from Artboard's default StateMachine configured in editor
else if let defaultStateMachine = artboard.defaultStateMachine() {
stateMachine = defaultStateMachine
RiveLogger.log(model: self, event: .defaultStateMachine)
}
// Set by index 0 as a fallback
else {
stateMachine = try artboard.stateMachine(from: 0)
RiveLogger.log(model: self, event: .stateMachineByIndex(0))
}
}
catch { throw RiveModelError.invalidStateMachine("Index \(index ?? 0) not found") }
catch {
let errorMessage = "State machine at index \(index ?? 0) not found"
RiveLogger.log(model: self, event: .error(errorMessage))
throw RiveModelError.invalidStateMachine(errorMessage)
}
}
open func setAnimation(_ name: String) throws {
guard animation?.name() != name else { return }
do { animation = try artboard.animation(fromName: name) }
catch { throw RiveModelError.invalidAnimation("Name \(name) not found") }
do {
animation = try artboard.animation(fromName: name)
RiveLogger.log(model: self, event: .animationByName(name))
}
catch {
let errorMessage = "Animation named \(name) not found"
RiveLogger.log(model: self, event: .error(errorMessage))
throw RiveModelError.invalidAnimation(errorMessage)
}
}
open func setAnimation(_ index: Int? = nil) throws {
// Defaults to 0 as it's assumed to be the first element in the collection
let index = index ?? 0
do { animation = try artboard.animation(from: index) }
catch { throw RiveModelError.invalidAnimation("Index \(index) not found") }
do {
animation = try artboard.animation(from: index)
RiveLogger.log(model: self, event: .animationByIndex(index))
}
catch {
let errorMessage = "Animation at index index \(index) not found"
RiveLogger.log(model: self, event: .error(errorMessage))
throw RiveModelError.invalidAnimation(errorMessage)
}
}
// MARK: -

View File

@@ -149,6 +149,8 @@ open class RiveView: RiveRendererView {
/// Starts the render loop
internal func play() {
RiveLogger.log(view: self, event: .play)
eventQueue.add {
self.playerDelegate?.player(playedWithModel: self.riveModel)
}
@@ -159,6 +161,8 @@ open class RiveView: RiveRendererView {
/// Asks the render loop to stop on the next cycle
internal func pause() {
RiveLogger.log(view: self, event: .pause)
if isPlaying {
eventQueue.add {
self.playerDelegate?.player(pausedWithModel: self.riveModel)
@@ -169,6 +173,8 @@ open class RiveView: RiveRendererView {
/// Asks the render loop to stop on the next cycle
internal func stop() {
RiveLogger.log(view: self, event: .stop)
playerDelegate?.player(stoppedWithModel: riveModel)
isPlaying = false
@@ -176,8 +182,10 @@ open class RiveView: RiveRendererView {
}
internal func reset() {
RiveLogger.log(view: self, event: .reset)
lastTime = 0
if !isPlaying {
advance(delta: 0)
}
@@ -265,6 +273,7 @@ open class RiveView: RiveRendererView {
if (firedEventCount > 0) {
for i in 0..<firedEventCount {
let event = stateMachine.reportedEvent(at: i)
RiveLogger.log(view: self, event: .eventReceived(event.name()))
stateMachineDelegate?.onRiveEventReceived?(onRiveEvent: event)
}
}
@@ -288,10 +297,12 @@ open class RiveView: RiveRendererView {
// This will be true when coming to a hault automatically
if wasPlaying {
RiveLogger.log(view: self, event: .pause)
playerDelegate?.player(pausedWithModel: riveModel)
}
}
RiveLogger.log(view: self, event: .advance(delta))
playerDelegate?.player(didAdvanceby: delta, riveModel: riveModel)
// Trigger a redraw
@@ -303,6 +314,7 @@ open class RiveView: RiveRendererView {
// This prevents breaking when loading RiveFile async
guard let artboard = riveModel?.artboard else { return }
RiveLogger.log(view: self, event: .drawing(size))
let newFrame = CGRect(origin: rect.origin, size: size)
align(with: newFrame, contentRect: artboard.bounds(), alignment: alignment, fit: fit)
draw(with: artboard)
@@ -326,8 +338,12 @@ open class RiveView: RiveRendererView {
// MARK: - UIResponder
#if os(iOS)
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouch(touches.first!, delegate: stateMachineDelegate?.touchBegan) {
let result = $0.touchBegan(atLocation: $1)
guard let touch = touches.first else { return }
handleTouch(touch, delegate: stateMachineDelegate?.touchBegan) { stateMachine, location in
let result = stateMachine.touchBegan(atLocation: location)
RiveLogger.log(view: self, event: .touchBegan(location))
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .began)
}
@@ -339,8 +355,12 @@ open class RiveView: RiveRendererView {
}
open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouch(touches.first!, delegate: stateMachineDelegate?.touchMoved) {
let result = $0.touchMoved(atLocation: $1)
guard let touch = touches.first else { return }
handleTouch(touch, delegate: stateMachineDelegate?.touchMoved) { stateMachine, location in
RiveLogger.log(view: self, event: .touchMoved(location))
let result = stateMachine.touchMoved(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .moved)
}
@@ -352,8 +372,12 @@ open class RiveView: RiveRendererView {
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouch(touches.first!, delegate: stateMachineDelegate?.touchEnded) {
let result = $0.touchEnded(atLocation: $1)
guard let touch = touches.first else { return }
handleTouch(touch, delegate: stateMachineDelegate?.touchEnded) { stateMachine, location in
RiveLogger.log(view: self, event: .touchEnded(location))
let result = stateMachine.touchEnded(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .ended)
}
@@ -365,8 +389,12 @@ open class RiveView: RiveRendererView {
}
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouch(touches.first!, delegate: stateMachineDelegate?.touchCancelled) {
let result = $0.touchCancelled(atLocation: $1)
guard let touch = touches.first else { return }
handleTouch(touch, delegate: stateMachineDelegate?.touchCancelled) { stateMachine, location in
RiveLogger.log(view: self, event: .touchCancelled(location))
let result = stateMachine.touchCancelled(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .cancelled)
}
@@ -409,8 +437,10 @@ open class RiveView: RiveRendererView {
}
#else
open override func mouseDown(with event: NSEvent) {
handleTouch(event, delegate: stateMachineDelegate?.touchBegan) {
let result = $0.touchBegan(atLocation: $1)
handleTouch(event, delegate: stateMachineDelegate?.touchBegan) { stateMachine, location in
RiveLogger.log(view: self, event: .touchBegan(location))
let result = stateMachine.touchBegan(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .began)
}
@@ -422,8 +452,10 @@ open class RiveView: RiveRendererView {
}
open override func mouseMoved(with event: NSEvent) {
handleTouch(event, delegate: stateMachineDelegate?.touchMoved) {
let result = $0.touchMoved(atLocation: $1)
handleTouch(event, delegate: stateMachineDelegate?.touchMoved) { stateMachine, location in
RiveLogger.log(view: self, event: .touchMoved(location))
let result = stateMachine.touchMoved(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .moved)
}
@@ -435,8 +467,10 @@ open class RiveView: RiveRendererView {
}
open override func mouseDragged(with event: NSEvent) {
handleTouch(event, delegate: stateMachineDelegate?.touchMoved) {
let result = $0.touchMoved(atLocation: $1)
handleTouch(event, delegate: stateMachineDelegate?.touchMoved) { stateMachine, location in
RiveLogger.log(view: self, event: .touchMoved(location))
let result = stateMachine.touchMoved(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .moved)
}
@@ -448,8 +482,10 @@ open class RiveView: RiveRendererView {
}
open override func mouseUp(with event: NSEvent) {
handleTouch(event, delegate: stateMachineDelegate?.touchEnded) {
let result = $0.touchEnded(atLocation: $1)
handleTouch(event, delegate: stateMachineDelegate?.touchEnded) { stateMachine, location in
RiveLogger.log(view: self, event: .touchEnded(location))
let result = stateMachine.touchEnded(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .ended)
}
@@ -461,8 +497,10 @@ open class RiveView: RiveRendererView {
}
open override func mouseExited(with event: NSEvent) {
handleTouch(event, delegate: stateMachineDelegate?.touchCancelled) {
let result = $0.touchCancelled(atLocation: $1)
handleTouch(event, delegate: stateMachineDelegate?.touchCancelled) { stateMachine, location in
RiveLogger.log(view: self, event: .touchCancelled(location))
let result = stateMachine.touchCancelled(atLocation: location)
if let stateMachine = riveModel?.stateMachine {
stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .cancelled)
}

View File

@@ -241,23 +241,26 @@ import Combine
// We're not checking if a StateMachine is "ended" or "ExitState"
// But we may want to in the future to enable restarting it by playing again
RiveLogger.log(viewModel: self, event: .play)
riveView?.play()
}
/// Halts the active Animation or StateMachine and will resume from it's current position when next played
@objc open func pause() {
RiveLogger.log(viewModel: self, event: .pause)
riveView?.pause()
}
/// Halts the active Animation or StateMachine and sets it at its starting position
@objc open func stop() {
RiveLogger.log(viewModel: self, event: .stop)
resetCurrentModel()
riveView?.stop()
}
/// Sets the active Animation or StateMachine back to their starting position
@objc open func reset() {
RiveLogger.log(viewModel: self, event: .reset)
resetCurrentModel()
riveView?.reset()
}
@@ -266,8 +269,12 @@ import Combine
/// Instantiates elements in the model needed to play in a `RiveView`
@objc open func configureModel(artboardName: String? = nil, stateMachineName: String? = nil, animationName: String? = nil) throws {
guard let model = riveModel else { fatalError("Cannot configure nil RiveModel") }
guard let model = riveModel else {
let errorMessage = "Cannot configure nil RiveModel"
RiveLogger.log(viewModel: self, event: .fatalError(errorMessage))
fatalError(errorMessage)
}
model.animation = nil
model.stateMachine = nil
@@ -300,7 +307,11 @@ import Combine
/// Puts the active Animation or StateMachine back to their starting position
private func resetCurrentModel() {
guard let model = riveModel else { fatalError("Current model is nil") }
guard let model = riveModel else {
let errorMessage = "Current model is nil"
RiveLogger.log(viewModel: self, event: .fatalError(errorMessage))
fatalError(errorMessage)
}
try! configureModel(
artboardName: model.artboard.name(),
stateMachineName: model.stateMachine?.name(),
@@ -321,6 +332,7 @@ import Combine
/// Provide the active StateMachine a `Trigger` input
/// - Parameter inputName: The name of a `Trigger` input on the active StateMachine
@objc open func triggerInput(_ inputName: String) {
RiveLogger.log(viewModel: self, event: .triggerInput(inputName, nil))
riveModel?.stateMachine?.getTrigger(inputName).fire()
play()
}
@@ -330,6 +342,7 @@ import Combine
/// - inputName: The name of a `Boolean` input on the active StateMachine
/// - value: A Bool value for the input
@objc(setBooleanInput::) open func setInput(_ inputName: String, value: Bool) {
RiveLogger.log(viewModel: self, event: .booleanInput(inputName, nil, value))
riveModel?.stateMachine?.getBool(inputName).setValue(value)
play()
}
@@ -339,6 +352,7 @@ import Combine
/// - inputName: The name of a `Number` input on the active StateMachine
/// - value: A Float value for the input
@objc(setFloatInput::) open func setInput(_ inputName: String, value: Float) {
RiveLogger.log(viewModel: self, event: .floatInput(inputName, nil, value))
riveModel?.stateMachine?.getNumber(inputName).setValue(value)
play()
}
@@ -348,6 +362,7 @@ import Combine
/// - inputName: The name of a `Number` input on the active StateMachine
/// - value: A Double value for the input
@objc(setDoubleInput::) open func setInput(_ inputName: String, value: Double) {
RiveLogger.log(viewModel: self, event: .doubleInput(inputName, nil, value))
setInput(inputName, value: Float(value))
}
@@ -356,6 +371,7 @@ import Combine
/// - inputName: The name of a `Trigger` input on the active StateMachine
/// - path: A String representing the path to the nested artboard delimited by "/" (ie. "Nested" or "Level1/Level2/Level3")
open func triggerInput(_ inputName: String, path: String) {
RiveLogger.log(viewModel: self, event: .triggerInput(inputName, path))
riveModel?.artboard?.getTrigger(inputName, path: path).fire()
play()
}
@@ -366,6 +382,7 @@ import Combine
/// - value: A Bool value for the input
/// - path: A String representing the path to the nested artboard delimited by "/" (ie. "Nested" or "Level1/Level2/Level3")
open func setInput(_ inputName: String, value: Bool, path: String) {
RiveLogger.log(viewModel: self, event: .booleanInput(inputName, path, value))
riveModel?.artboard?.getBool(inputName, path: path).setValue(value)
play()
}
@@ -376,6 +393,7 @@ import Combine
/// - value: A Float value for the input
/// - path: A String representing the path to the nested artboard delimited by "/" (ie. "Nested" or "Level1/Level2/Level3")
open func setInput(_ inputName: String, value: Float, path: String) {
RiveLogger.log(viewModel: self, event: .floatInput(inputName, path, value))
riveModel?.artboard?.getNumber(inputName, path: path).setValue(value);
play()
}
@@ -386,6 +404,7 @@ import Combine
/// - value: A Double value for the input
/// - path: A String representing the path to the nested artboard delimited by "/" (ie. "Nested" or "Level1/Level2/Level3")
open func setInput(_ inputName: String, value: Double, path: String) {
RiveLogger.log(viewModel: self, event: .doubleInput(inputName, path, value))
setInput(inputName, value: Float(value), path: path)
}
@@ -418,13 +437,16 @@ import Combine
/// - value: A String value for the text run
@objc open func setTextRunValue(_ textRunName: String, textValue: String) throws {
if let textRun = riveModel?.artboard?.textRun(textRunName) {
RiveLogger.log(viewModel: self, event: .textRun(textRunName, nil, textValue))
textRun.setText(textValue)
if isPlaying == false {
riveView?.advance(delta: 0)
}
} else {
throw RiveError.textValueRunError("Could not set text value on text run: \(textRunName) as the text run could not be found from the active artboard")
let errorMessage = "Could not set text value on text run: \(textRunName) as the text run could not be found from the active artboard"
RiveLogger.log(viewModel: self, event: .error(errorMessage))
throw RiveError.textValueRunError(errorMessage)
}
}
@@ -436,13 +458,16 @@ import Combine
/// - Note: If the specified path is empty, the parent artboard will be used to find the text run.
@objc open func setTextRunValue(_ textRunName: String, path: String, textValue: String) throws {
if let textRun = riveModel?.artboard?.textRun(textRunName, path: path) {
RiveLogger.log(viewModel: self, event: .textRun(textRunName, path, textValue))
textRun.setText(textValue)
if isPlaying == false {
riveView?.advance(delta: 0)
}
} else {
throw RiveError.textValueRunError("Could not set text value on text run: \(textRunName) as the text run could not be found from the active artboard")
let errorMessage = "Could not set text value on text run: \(textRunName) as the text run could not be found from the active artboard"
RiveLogger.log(viewModel: self, event: .error(errorMessage))
throw RiveError.textValueRunError(errorMessage)
}
}

View File

@@ -10,6 +10,7 @@ import Foundation
public extension RiveFile {
convenience init(name fileName: String, extension ext: String = ".riv", in bundle: Bundle = .main, loadCdn: Bool=true, customLoader: LoadAsset? = nil) throws {
RiveLogger.log(loadingFromResource: "\(fileName)\(ext)")
let byteArray = RiveFile.getBytes(fileName: fileName, extension: ext, in: bundle)
if (customLoader == nil){
try self.init(byteArray: byteArray, loadCdn: loadCdn)
@@ -20,10 +21,14 @@ public extension RiveFile {
static func getBytes(fileName: String, extension ext: String = ".riv", in bundle: Bundle = .main) -> [UInt8] {
guard let url = bundle.url(forResource: fileName, withExtension: ext) else {
fatalError("Failed to locate \(fileName) in bundle \(bundle).")
let errorMessage = "Failed to locate \(fileName) in bundle \(bundle)."
RiveLogger.log(file: nil, event: .fatalError(errorMessage))
fatalError(errorMessage)
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
let errorMessage = "Failed to load \(url) from bundle."
RiveLogger.log(file: nil, event: .fatalError(errorMessage))
fatalError()
}
// Import the data into a RiveFile