-
Notifications
You must be signed in to change notification settings - Fork 66
/
Copy pathRiveViewModel.swift
690 lines (600 loc) · 26.8 KB
/
RiveViewModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
//
// RiveViewModel.swift
// RiveRuntime
//
// Created by Zachary Duncan on 3/24/22.
// Copyright © 2022 Rive. All rights reserved.
//
import SwiftUI
import Combine
/// An object used for controlling a RiveView. For most common Rive files you should only need
/// to interact with a `RiveViewModel` object.
///
/// - Usage:
/// - You should initialize with either an Animation name or a StateMachine name, but not both.
/// Only one will be used and if both are given the StateMachine will be used.
/// - Default StateMachine or Animation from the file can be used by leaving their parameters nil
/// - Examples:
///
/// ```
/// // SwiftUI Example
/// struct Animation: View {
/// var body: some View {
/// RiveViewModel(fileName: "cool_rive_file").view()
/// }
/// }
/// ```
///
/// ```
/// // UIKit Example
/// class AnimationViewController: UIViewController {
/// @IBOutlet weak var riveView: RiveView!
/// var viewModel = RiveViewModel(fileName: "cool_rive_file")
///
/// override func viewDidLoad() {
/// viewModel.setView(riveView)
/// }
/// }
/// ```
@objc open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStateMachineDelegate, RivePlayerDelegate{
/// The default layout scale factor that allows for the scale factor to be determined by Rive.
@objc public static let layoutScaleFactorAutomatic: Double = RiveView.Constants.layoutScaleFactorAutomatic
// TODO: could be a weak ref, need to look at this in more detail.
open private(set) var riveView: RiveView?
private var defaultModel: RiveModelBuffer!
@objc public init(
_ model: RiveModel,
stateMachineName: String?,
fit: RiveFit = .contain,
alignment: RiveAlignment = .center,
autoPlay: Bool = true,
artboardName: String? = nil
) {
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
riveModel = model
sharedInit(artboardName: artboardName, stateMachineName: stateMachineName, animationName: nil)
}
@objc public init(
_ model: RiveModel,
animationName: String? = nil,
fit: RiveFit = .contain,
alignment: RiveAlignment = .center,
autoPlay: Bool = true,
artboardName: String? = nil
) {
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
riveModel = model
sharedInit(artboardName: artboardName, stateMachineName: nil, animationName: animationName)
}
@objc public init(
fileName: String,
extension: String = ".riv",
in bundle: Bundle = .main,
stateMachineName: String?,
fit: RiveFit = .contain,
alignment: RiveAlignment = .center,
autoPlay: Bool = true,
artboardName: String? = nil,
loadCdn: Bool = true,
customLoader: LoadAsset? = nil
) {
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
riveModel = try! RiveModel(fileName: fileName, extension: `extension`, in: bundle, loadCdn: loadCdn, customLoader:customLoader)
sharedInit(artboardName: artboardName, stateMachineName: stateMachineName, animationName: nil)
}
public init(
fileName: String,
extension: String = ".riv",
in bundle: Bundle = .main,
animationName: String? = nil,
fit: RiveFit = .contain,
alignment: RiveAlignment = .center,
autoPlay: Bool = true,
artboardName: String? = nil,
preferredFramesPerSecond: Int? = nil,
loadCdn: Bool = true,
customLoader: LoadAsset? = nil
) {
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
riveModel = try! RiveModel(fileName: fileName, extension: `extension`, in: bundle, loadCdn: loadCdn, customLoader:customLoader)
sharedInit(artboardName: artboardName, stateMachineName: nil, animationName: animationName)
}
@objc public init(
webURL: String,
stateMachineName: String?,
fit: RiveFit = .contain,
alignment: RiveAlignment = .center,
autoPlay: Bool = true,
loadCdn: Bool = true,
artboardName: String? = nil
) {
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
riveModel = RiveModel(webURL: webURL, delegate: self, loadCdn: loadCdn)
defaultModel = RiveModelBuffer(artboardName: artboardName, stateMachineName: stateMachineName, animationName: nil)
}
@objc public init(
webURL: String,
animationName: String? = nil,
fit: RiveFit = .contain,
alignment: RiveAlignment = .center,
autoPlay: Bool = true,
loadCdn: Bool = true,
artboardName: String? = nil
) {
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
riveModel = RiveModel(webURL: webURL, delegate: self, loadCdn: loadCdn)
defaultModel = RiveModelBuffer(artboardName: artboardName, stateMachineName: nil, animationName: animationName)
}
private func sharedInit(artboardName: String?, stateMachineName: String?, animationName: String?) {
try! configureModel(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
defaultModel = RiveModelBuffer(
artboardName: artboardName,
stateMachineName: stateMachineName,
animationName: animationName
)
try! riveView?.setModel(riveModel!, autoPlay: autoPlay)
}
// MARK: - RiveView
open private(set) var riveModel: RiveModel? {
didSet {
if let model = riveModel {
try! riveView?.setModel(model, autoPlay: autoPlay)
}
}
}
open var isPlaying: Bool { riveView?.isPlaying ?? false }
open var autoPlay: Bool
open var fit: RiveFit = .contain {
didSet { riveView?.fit = fit }
}
open var alignment: RiveAlignment = .center {
didSet { riveView?.alignment = alignment }
}
/// The scale factor to apply when using the `layout` fit. By default, this value is -1, where Rive will determine
/// the correct scale for your device.To override this default behavior, set this value to a value greater than 0.
/// - Note: If the scale factor <= 0, nothing will be drawn.
open var layoutScaleFactor: Double = layoutScaleFactorAutomatic {
didSet { riveView?.layoutScaleFactor = layoutScaleFactor }
}
/// Sets whether or not the current Rive view should forward Rive listener touch / click events to any next responders.
/// When true, touch / click events will be forwarded to any next responder(s).
/// When false, only the Rive view will handle touch / click events, and will not forward
/// to any next responder(s). Defaults to `false`, as to preserve pre-existing runtime functionality.
/// - Note: On iOS, this is handled separately from `isExclusiveTouch`.
open var forwardsListenerEvents: Bool = false {
didSet { riveView?.forwardsListenerEvents = forwardsListenerEvents }
}
#if os(iOS)
/// Hints to underlying CADisplayLink in RiveView (if created) the preferred FPS to run at
/// For more, see: https://developer.apple.com/documentation/quartzcore/cadisplaylink/1648421-preferredframespersecond
/// - Parameters:
/// - preferredFramesPerSecond: Integer number of seconds to set preferred FPS at
public func setPreferredFramesPerSecond(preferredFramesPerSecond: Int) {
riveView?.setPreferredFramesPerSecond(preferredFramesPerSecond: preferredFramesPerSecond)
}
/// Hints to underlying CADisplayLink in RiveView (if created) the preferred frame rate range
/// For more, see: https://developer.apple.com/documentation/quartzcore/cadisplaylink/3875343-preferredframeraterange
/// - Parameters:
/// - preferredFrameRateRange: Frame rate range to set
@available(iOS 15.0, *)
public func setPreferredFrameRateRange(preferredFrameRateRange: CAFrameRateRange) {
riveView?.setPreferredFrameRateRange(preferredFrameRateRange: preferredFrameRateRange)
}
#endif
/// Starts the active Animation or StateMachine from it's last position. It will start
/// from the beginning if the active Animation has ended or a new one is provided.
/// - Parameters:
/// - animationName: The name of a new Animation to play on the current Artboard
/// - loop: The loop mode for the active Animation
@objc open func play(animationName: String? = nil, loop: RiveLoop = .autoLoop, direction: RiveDirection = .autoDirection) {
if let name = animationName {
try! riveModel?.setAnimation(name)
}
if let animation = riveModel?.animation {
if loop != .autoLoop {
animation.loop(Int32(loop.rawValue))
}
if direction == .forwards {
animation.direction(1)
} else if direction == .backwards {
animation.direction(-1)
}
if animation.hasEnded() {
// Restarts Animation from beginning
animation.setTime(0)
}
}
// 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()
}
// MARK: - RiveModel
/// 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 {
let errorMessage = "Cannot configure nil RiveModel"
RiveLogger.log(viewModel: self, event: .fatalError(errorMessage))
fatalError(errorMessage)
}
model.animation = nil
model.stateMachine = nil
if let name = artboardName {
try model.setArtboard(name)
} else {
// Keep current Artboard if there is one
if model.artboard == nil {
// Set default Artboard if not
try model.setArtboard()
}
}
if let name = stateMachineName {
try model.setStateMachine(name)
}
else if let name = animationName {
try model.setAnimation(name)
}
// Find defaults
else {
// Attempts to set a default StateMachine first
if ((try? model.setStateMachine()) == nil) {
// If it fails, attempts a default Animation
try model.setAnimation()
}
}
}
/// Puts the active Animation or StateMachine back to their starting position
private func resetCurrentModel() {
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(),
animationName: model.animation?.name()
)
}
/// Sets the Artboard, StateMachine or Animation back to the first one given to the RiveViewModel
@objc open func resetToDefaultModel() {
try! configureModel(
artboardName: defaultModel.artboardName,
stateMachineName: defaultModel.stateMachineName,
animationName: defaultModel.animationName
)
}
/// 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()
}
/// Provide the active StateMachine a `Boolean` input
/// - Parameters:
/// - 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()
}
/// Returns the current boolean input by name. Get its value by calling `.value` on the returned object.
/// - Parameter input: The name of the input
/// - Returns: The boolean input if it exists. Returns `nil` if the input cannot be found.
@objc open func boolInput(named name: String) -> RiveSMIBool?
{
guard let input = riveModel?.stateMachine?.getBool(name) else {
RiveLogger.log(viewModel: self, event: .error("Cannot find bool input named \(name)"))
return nil
}
return input
}
/// Provide the active StateMachine a `Number` input
/// - Parameters:
/// - 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()
}
/// Provide the active StateMachine a `Number` input
/// - Parameters:
/// - 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))
}
/// Returns the current number input by name. Get its value by calling `.value` on the returned object.
/// - Parameter input: The name of the input
/// - Returns: The number input if it exists. Returns `nil` if the input cannot be found.
@objc open func numberInput(named name: String) -> RiveSMINumber?
{
guard let input = riveModel?.stateMachine?.getNumber(name) else {
RiveLogger.log(viewModel: self, event: .error("Cannot find number input named \(name)"))
return nil
}
return input
}
/// Provide the specified nested Artboard with a `Trigger` input
/// - Parameters:
/// - 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()
}
/// Provide the specified nested Artboard with a `Boolean` input
/// - Parameters:
/// - inputName: The name of a `Boolean` input on the active StateMachine
/// - 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()
}
/// Provide the specified nested Artboard with a `Number` input
/// - Parameters:
/// - inputName: The name of a `Number` input on the active StateMachine
/// - 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()
}
/// Provide the specified nested Artboard with a `Number` input
/// - Parameters:
/// - inputName: The name of a `Number` input on the active StateMachine
/// - 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)
}
/// Get a text value from a specified text run
/// - Parameters:
/// - textRunName: The name of a `Text Run` on the active Artboard
/// - Returns: String text value of the specified text run if applicable
@objc open func getTextRunValue(_ textRunName: String) -> String? {
if let textRun = riveModel?.artboard?.textRun(textRunName) {
return textRun.text()
}
return nil
}
/// Get a text value from a specified text run
/// - Parameters:
/// - textRunName: The name of a `Text Run` on the active Artboard
/// - path: The path to the nested text run.
/// - Returns: String text value of the specified text run if applicable
@objc open func getTextRunValue(_ textRunName: String, path: String) -> String? {
if let textRun = riveModel?.artboard?.textRun(textRunName, path: path) {
return textRun.text()
}
return nil
}
/// Set a text value for a specified text run
/// - Parameters:
/// - textRunName: The name of a `Text Run` on the active Artboard
/// - 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 {
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)
}
}
/// Set a text value for a specified text run
/// - Parameters:
/// - textRunName: The name of a `Text Run` on the active Artboard
/// - path: The path to the nested text run.
/// - value: A String value for the text run
/// - 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 {
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)
}
}
// TODO: Replace this with a more robust structure of the file's contents
@objc open func artboardNames() -> [String] {
return riveModel?.riveFile.artboardNames() ?? []
}
// MARK: - SwiftUI Helpers
/// Makes a new `RiveView` for the instance property with data from model which will
/// replace any previous `RiveView`. This is called when first drawing a `RiveViewRepresentable`.
/// - Returns: Reference to the new view that the `RiveViewModel` will be maintaining
@objc open func createRiveView() -> RiveView {
let view: RiveView
if let model = riveModel {
view = RiveView(model: model, autoPlay: autoPlay)
} else {
view = RiveView()
}
registerView(view)
return view
}
open func setRiveView(view:RiveView)
{
registerView(view)
}
/// Gives updated layout values to the provided `RiveView`. This is called in
/// the process of re-displaying `RiveViewRepresentable`.
/// - Parameter view: the `RiveView` that will be updated
@objc open func update(view: RiveView) {
view.fit = fit
view.alignment = alignment
view.layoutScaleFactor = layoutScaleFactor
view.forwardsListenerEvents = forwardsListenerEvents
}
/// Assigns the provided `RiveView` to the riveView property. This is called when
/// creating a `RiveViewRepresentable` in the `.view()` method for SwiftUI and when
/// adding to the view hierarchy with `.createRiveView()` in UIKit.
///
/// - Parameter view: the `RiveView` that this `RiveViewModel` will maintain
fileprivate func registerView(_ view: RiveView) {
riveView = view
riveView!.playerDelegate = self
riveView!.stateMachineDelegate = self
riveView!.fit = fit
riveView!.alignment = alignment
riveView!.layoutScaleFactor = layoutScaleFactor
riveView!.forwardsListenerEvents = forwardsListenerEvents
}
/// Stops maintaining a connection to any `RiveView`
open func deregisterView() {
riveView = nil
}
/// This can be added to the body of a SwiftUI `View`
open func view() -> AnyView {
return AnyView(RiveViewRepresentable(viewModel: self))
}
// MARK: - UIKit Helper
/// This can be used to connect with and configure an `RiveView` that was created elsewhere.
/// Does not need to be called when updating an already configured `RiveView`. Useful for
/// attaching views created in a `UIViewController` or Storyboard.
/// - Parameter view: the `RiveView` that this `RiveViewModel` will maintain
@objc open func setView(_ view: RiveView) {
registerView(view)
try! riveView!.setModel(riveModel!, autoPlay: autoPlay)
}
// MARK: - RiveFile Delegate
/// Needed for when resetting to defaults or the RiveViewModel is initialized with a webURL so
/// we are then able make a RiveModel when the RiveFile is finished downloading
private struct RiveModelBuffer {
var artboardName: String?
var stateMachineName: String?
var animationName: String?
}
/// Called by RiveFile when it finishes downloading an asset asynchronously
@objc open func riveFileDidLoad(_ riveFile: RiveFile) throws {
riveModel = RiveModel(riveFile: riveFile)
sharedInit(
artboardName: defaultModel.artboardName,
stateMachineName: defaultModel.stateMachineName,
animationName: defaultModel.animationName
)
}
// MARK: - RivePlayer Delegate
@objc open func player(playedWithModel riveModel: RiveModel?) { }
@objc open func player(pausedWithModel riveModel: RiveModel?) { }
@objc open func player(loopedWithModel riveModel: RiveModel?, type: Int) { }
@objc open func player(stoppedWithModel riveModel: RiveModel?) { }
@objc open func player(didAdvanceby seconds: Double, riveModel: RiveModel?) { }
enum RiveError: Error {
case textValueRunError(_ message: String)
}
}
#if os(iOS)
/// This makes a SwiftUI digestable view from an `RiveViewModel` and its `RiveView`
public struct RiveViewRepresentable: UIViewRepresentable {
let viewModel: RiveViewModel
public init(viewModel: RiveViewModel) {
self.viewModel = viewModel
}
/// Constructs the view
public func makeUIView(context: Context) -> RiveView {
return viewModel.createRiveView()
}
public func updateUIView(_ view: RiveView, context: UIViewRepresentableContext<RiveViewRepresentable>) {
viewModel.update(view: view)
}
public static func dismantleUIView(_ view: RiveView, coordinator: Coordinator) {
view.stop()
if (view == coordinator.viewModel.riveView) {
coordinator.viewModel.deregisterView()
}
}
/// Constructs a coordinator for managing updating state
public func makeCoordinator() -> Coordinator {
return Coordinator(viewModel: viewModel)
}
public class Coordinator: NSObject {
public var viewModel: RiveViewModel
init(viewModel: RiveViewModel) {
self.viewModel = viewModel
}
}
}
#else
public struct RiveViewRepresentable: NSViewRepresentable {
let viewModel: RiveViewModel
public init(viewModel: RiveViewModel) {
self.viewModel = viewModel
}
/// Constructs the view
public func makeNSView(context: Context) -> RiveView {
return viewModel.createRiveView()
}
public func updateNSView(_ view: RiveView, context: NSViewRepresentableContext<RiveViewRepresentable>) {
viewModel.update(view: view)
}
public static func dismantleNSView(_ view: RiveView, coordinator: Coordinator) {
view.stop()
coordinator.viewModel.deregisterView()
}
/// Constructs a coordinator for managing updating state
public func makeCoordinator() -> Coordinator {
return Coordinator(viewModel: viewModel)
}
public class Coordinator: NSObject {
public var viewModel: RiveViewModel
init(viewModel: RiveViewModel) {
self.viewModel = viewModel
}
}
}
#endif