@@ -55,6 +55,13 @@ class RiveReactNativeView: RCTView, RivePlayerDelegate, RiveStateMachineDelegate
55
55
}
56
56
57
57
@objc var artboardName : String ?
58
+
59
+ @objc var assetsHandled : NSDictionary ?
60
+ {
61
+ didSet {
62
+ requiresLocalResourceReconfigure = true ;
63
+ }
64
+ }
58
65
59
66
60
67
@objc var animationName : String ?
@@ -79,7 +86,18 @@ class RiveReactNativeView: RCTView, RivePlayerDelegate, RiveStateMachineDelegate
79
86
// MARK: - React Native Helpers
80
87
81
88
override func removeFromSuperview( ) {
82
- removeReactSubview ( riveView) // TODO: Investigate if this is the optimal place to remove, and if this is necessary.
89
+ cleanupResources ( )
90
+
91
+ super. removeFromSuperview ( )
92
+ }
93
+
94
+ private func cleanupResources( ) {
95
+ removeReactSubview ( riveView)
96
+ riveView? . playerDelegate = nil
97
+ riveView? . stateMachineDelegate = nil
98
+ riveView = nil ;
99
+ viewModel? . deregisterView ( ) ;
100
+ viewModel = nil ;
83
101
}
84
102
85
103
override func layoutSubviews( ) {
@@ -90,7 +108,7 @@ class RiveReactNativeView: RCTView, RivePlayerDelegate, RiveStateMachineDelegate
90
108
}
91
109
92
110
override func didSetProps( _ changedProps: [ String ] ! ) {
93
- if ( changedProps. contains ( " url " ) || changedProps. contains ( " resourceName " ) || changedProps. contains ( " artboardName " ) || changedProps. contains ( " animationName " ) || changedProps. contains ( " stateMachineName " ) ) {
111
+ if ( changedProps. contains ( " url " ) || changedProps. contains ( " resourceName " ) || changedProps. contains ( " artboardName " ) || changedProps. contains ( " animationName " ) || changedProps. contains ( " stateMachineName " ) || changedProps . contains ( " assetsHandled " ) ) {
94
112
reloadView ( )
95
113
}
96
114
@@ -142,11 +160,11 @@ class RiveReactNativeView: RCTView, RivePlayerDelegate, RiveStateMachineDelegate
142
160
143
161
let updatedViewModel : RiveViewModel
144
162
if let smName = stateMachineName {
145
- updatedViewModel = RiveViewModel ( fileName: name, stateMachineName: smName, fit: convertFit ( fit) , alignment: convertAlignment ( alignment) , autoPlay: autoplay, artboardName: artboardName)
163
+ updatedViewModel = RiveViewModel ( fileName: name, stateMachineName: smName, fit: convertFit ( fit) , alignment: convertAlignment ( alignment) , autoPlay: autoplay, artboardName: artboardName, customLoader : customLoader )
146
164
} else if let animName = animationName {
147
- updatedViewModel = RiveViewModel ( fileName: name, animationName: animName, fit: convertFit ( fit) , alignment: convertAlignment ( alignment) , autoPlay: autoplay, artboardName: artboardName)
165
+ updatedViewModel = RiveViewModel ( fileName: name, animationName: animName, fit: convertFit ( fit) , alignment: convertAlignment ( alignment) , autoPlay: autoplay, artboardName: artboardName, customLoader : customLoader )
148
166
} else {
149
- updatedViewModel = RiveViewModel ( fileName: name, fit: convertFit ( fit) , alignment: convertAlignment ( alignment) , autoPlay: autoplay, artboardName: artboardName)
167
+ updatedViewModel = RiveViewModel ( fileName: name, fit: convertFit ( fit) , alignment: convertAlignment ( alignment) , autoPlay: autoplay, artboardName: artboardName, customLoader : customLoader )
150
168
}
151
169
152
170
updatedViewModel. layoutScaleFactor = layoutScaleFactor. doubleValue
@@ -192,8 +210,149 @@ class RiveReactNativeView: RCTView, RivePlayerDelegate, RiveStateMachineDelegate
192
210
} else {
193
211
configureViewModelFromUrl ( ) // TODO: calling viewModel?.configureModel for a URL ViewModel throws. Requires further investigation. Currently recreating the whole ViewModel for certain prop changes.
194
212
}
213
+ }
214
+
215
+ private func customLoader( asset: RiveFileAsset , data: Data , factory: RiveFactory ) -> Bool {
216
+ guard let assetData = assetsHandled ? [ asset. uniqueName ( ) ] as? NSDictionary else {
217
+ return false
218
+ }
195
219
220
+ if let source = assetData [ " source " ] as? NSDictionary {
221
+ loadAsset ( source: source, asset: asset, factory: factory)
222
+ return true
223
+ }
196
224
225
+ return false
226
+ }
227
+
228
+ private func loadAsset( source: NSDictionary , asset: RiveFileAsset , factory: RiveFactory ) {
229
+ let sourceAssetId = source [ " sourceAssetId " ] as? String
230
+ let sourceUrl = source [ " sourceUrl " ] as? String
231
+ let sourceAsset = source [ " sourceAsset " ] as? String
232
+
233
+ if let sourceAssetId = sourceAssetId {
234
+ handleSourceAssetId ( sourceAssetId, asset: asset, factory: factory)
235
+ } else if let sourceUrl = sourceUrl {
236
+ handleSourceUrl ( sourceUrl, asset: asset, factory: factory)
237
+ } else if let sourceAsset = sourceAsset {
238
+ handleSourceAsset ( sourceAsset, path: source [ " path " ] as? String , asset: asset, factory: factory)
239
+ }
240
+ }
241
+
242
+ private func handleSourceAssetId( _ sourceAssetId: String , asset: RiveFileAsset , factory: RiveFactory ) {
243
+ guard URL ( string: sourceAssetId) != nil else {
244
+ return
245
+ }
246
+
247
+ downloadUrlAsset ( url: sourceAssetId) { [ weak self] data in
248
+ self ? . processAssetBytes ( data, asset: asset, factory: factory)
249
+ }
250
+ }
251
+
252
+ private func handleSourceUrl( _ sourceUrl: String , asset: RiveFileAsset , factory: RiveFactory ) {
253
+ downloadUrlAsset ( url: sourceUrl) { [ weak self] data in
254
+ self ? . processAssetBytes ( data, asset: asset, factory: factory)
255
+ }
256
+ }
257
+
258
+ private func handleSourceAsset( _ sourceAsset: String , path: String ? , asset: RiveFileAsset , factory: RiveFactory ) {
259
+ loadResourceAsset ( sourceAsset: sourceAsset, path: path) { [ weak self] data in
260
+ self ? . processAssetBytes ( data, asset: asset, factory: factory)
261
+ }
262
+ }
263
+
264
+ private func processAssetBytes( _ data: Data , asset: RiveFileAsset , factory: RiveFactory ) {
265
+ DispatchQueue . global ( qos: . background) . async {
266
+ switch asset {
267
+ case let imageAsset as RiveImageAsset :
268
+ let decodedImage = factory. decodeImage ( data)
269
+ DispatchQueue . main. async {
270
+ imageAsset. renderImage ( decodedImage)
271
+ }
272
+ case let fontAsset as RiveFontAsset :
273
+ let decodedFont = factory. decodeFont ( data)
274
+ DispatchQueue . main. async {
275
+ fontAsset. font ( decodedFont)
276
+ }
277
+ case let audioAsset as RiveAudioAsset :
278
+ let decodedAudio = factory. decodeAudio ( data)
279
+ DispatchQueue . main. async {
280
+ audioAsset. audio ( decodedAudio)
281
+ }
282
+ default :
283
+ break
284
+ }
285
+ }
286
+ }
287
+
288
+ private func downloadUrlAsset( url: String , listener: @escaping ( Data ) -> Void ) {
289
+ guard isValidUrl ( url) else {
290
+ handleInvalidUrlError ( url: url)
291
+ return
292
+ }
293
+
294
+ let queue = URLSession . shared
295
+ guard let requestUrl = URL ( string: url) else {
296
+ handleInvalidUrlError ( url: url)
297
+ return
298
+ }
299
+
300
+ let request = URLRequest ( url: requestUrl)
301
+ let task = queue. dataTask ( with: request) { [ weak self] data, response, error in
302
+ if error != nil {
303
+ self ? . handleInvalidUrlError ( url: url)
304
+ } else if let data = data {
305
+ listener ( data)
306
+ }
307
+ }
308
+
309
+ task. resume ( )
310
+ }
311
+
312
+ private func isValidUrl( _ url: String ) -> Bool {
313
+ if let url = URL ( string: url) {
314
+ return UIApplication . shared. canOpenURL ( url)
315
+ } else {
316
+ return false
317
+ }
318
+ }
319
+
320
+ private func splitFileNameAndExtension( fileName: String ) -> ( name: String ? , ext: String ? ) ? {
321
+ let components = fileName. split ( separator: " . " )
322
+ guard components. count == 2 else { return nil }
323
+ return ( name: String ( components [ 0 ] ) , ext: String ( components [ 1 ] ) )
324
+ }
325
+
326
+ private func loadResourceAsset( sourceAsset: String , path: String ? , listener: @escaping ( Data ) -> Void ) {
327
+
328
+ guard let splitSourceAssetName = splitFileNameAndExtension ( fileName: sourceAsset) ,
329
+ let name = splitSourceAssetName. name,
330
+ let ext = splitSourceAssetName. ext else {
331
+ handleRiveError ( error: createAssetFileError ( sourceAsset) )
332
+ return
333
+ }
334
+
335
+ guard let folderUrl = Bundle . main. url ( forResource: name, withExtension: ext) else {
336
+ handleRiveError ( error: createAssetFileError ( sourceAsset) )
337
+ return
338
+ }
339
+
340
+ DispatchQueue . global ( qos: . background) . async { [ weak self] in
341
+ do {
342
+ let fileData = try Data ( contentsOf: folderUrl)
343
+ DispatchQueue . main. async {
344
+ listener ( fileData)
345
+ }
346
+ } catch {
347
+ DispatchQueue . main. async {
348
+ self ? . handleRiveError ( error: createAssetFileError ( sourceAsset) )
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ private func handleInvalidUrlError( url: String ) {
355
+ handleRiveError ( error: createIncorrectRiveURL ( url) )
197
356
}
198
357
199
358
// MARK: - Playback Controls
0 commit comments