diff --git a/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt b/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt index bb3316e5..f9e975fe 100644 --- a/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt +++ b/android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt @@ -51,6 +51,12 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() { private const val TAG = "HybridRiveView" } + //region Lifecycle + override fun dispose() { + view.dispose() + } + //endregion + //region State override val view: RiveReactNativeView = RiveReactNativeView(context) private var needsReload = false diff --git a/ios/HybridRiveFileFactory.swift b/ios/HybridRiveFileFactory.swift index 3b7cb736..154f1d02 100644 --- a/ios/HybridRiveFileFactory.swift +++ b/ios/HybridRiveFileFactory.swift @@ -36,11 +36,9 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl let referencedAssetCache = SendableRef(ReferencedAssetCache()) let factoryCache: SendableRef = .init(nil) - let fileRef: SendableRef = .init(nil) let customLoader = self.assetLoader.createCustomLoader( referencedAssets: referencedAssets, cache: referencedAssetCache, - factory: factoryCache, - fileRef: fileRef + factory: factoryCache ) let riveFile = @@ -49,7 +47,7 @@ final class HybridRiveFileFactory: HybridRiveFileFactorySpec, @unchecked Sendabl } else { try file(prepared) } - fileRef.value = riveFile + self.assetLoader.setFileRef(riveFile) let result = ( file: riveFile, cache: referencedAssetCache.value, factory: factoryCache.value, diff --git a/ios/HybridRiveView.swift b/ios/HybridRiveView.swift index 2067e618..b8c470a7 100644 --- a/ios/HybridRiveView.swift +++ b/ios/HybridRiveView.swift @@ -197,6 +197,13 @@ class HybridRiveView: HybridRiveViewSpec { } } + func dispose() { + let riveView = view as? RiveReactNativeView + DispatchQueue.main.async { + riveView?.cleanup() + } + } + // MARK: Internal State private var needsReload = false private var dataBindingChanged = false diff --git a/ios/ReferencedAssetLoader.swift b/ios/ReferencedAssetLoader.swift index 052805a2..b46e3caf 100644 --- a/ios/ReferencedAssetLoader.swift +++ b/ios/ReferencedAssetLoader.swift @@ -11,6 +11,26 @@ func createAssetFileError(_ assetName: String) -> NitroRiveError { } final class ReferencedAssetLoader { + private var activeLoadCount = 0 + private var activeFileRef: RiveFile? + + func setFileRef(_ file: RiveFile) { + activeFileRef = file + } + + private func retainFile() { + activeLoadCount += 1 + } + + private func releaseFile() { + dispatchPrecondition(condition: .onQueue(.main)) + activeLoadCount -= 1 + if activeLoadCount <= 0 { + activeLoadCount = 0 + activeFileRef = nil + } + } + private func handleRiveError(error: Error) { RCTLogError("\(error)") } @@ -115,8 +135,7 @@ final class ReferencedAssetLoader { func createCustomLoader( referencedAssets: ReferencedAssetsType?, cache: SendableRef, - factory factoryOut: SendableRef, - fileRef: SendableRef + factory factoryOut: SendableRef ) -> LoadAsset? { @@ -124,7 +143,7 @@ final class ReferencedAssetLoader { else { return nil } - return { (asset: RiveFileAsset, _: Data, factory: RiveFactory) -> Bool in + return { [weak self] (asset: RiveFileAsset, _: Data, factory: RiveFactory) -> Bool in let assetByUniqueName = referencedAssets[asset.uniqueName()] guard let assetData = assetByUniqueName ?? referencedAssets[asset.name()] else { return false @@ -133,10 +152,11 @@ final class ReferencedAssetLoader { cache.value[asset.uniqueName()] = asset factoryOut.value = factory - self.loadAssetInternal( + self?.retainFile() + self?.loadAssetInternal( source: assetData, asset: asset, factory: factory, - completion: { - withExtendedLifetime(fileRef) {} + completion: { [weak self] in + self?.releaseFile() }) return true diff --git a/ios/RiveReactNativeView.swift b/ios/RiveReactNativeView.swift index a74ba50d..324688f4 100644 --- a/ios/RiveReactNativeView.swift +++ b/ios/RiveReactNativeView.swift @@ -242,7 +242,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate { } } - private func cleanup() { + func cleanup() { riveView?.removeFromSuperview() riveView?.stateMachineDelegate = nil riveView = nil diff --git a/src/core/RiveView.tsx b/src/core/RiveView.tsx index ab09ab89..4023e4df 100644 --- a/src/core/RiveView.tsx +++ b/src/core/RiveView.tsx @@ -1,6 +1,8 @@ -import type { ComponentProps } from 'react'; +import { useEffect, useRef, type ComponentProps } from 'react'; import { NitroRiveView } from './NitroRiveViewComponent'; import { RiveErrorType, type RiveError } from './Errors'; +import { callDispose } from './callDispose'; +import type { RiveViewRef } from '../index'; export interface RiveViewProps extends Omit, 'onError'> { @@ -41,8 +43,31 @@ const defaultOnError = (error: RiveError) => * - pause(): Pauses the Rive graphic */ export function RiveView(props: RiveViewProps) { - const { onError, ...rest } = props; + const { onError, hybridRef: userHybridRef, ...rest } = props; const wrappedOnError = onError ?? defaultOnError; + const viewRef = useRef(null); - return ; + useEffect(() => { + return () => { + if (viewRef.current) { + callDispose(viewRef.current); + viewRef.current = null; + } + }; + }, []); + + const setRef = (ref: RiveViewRef) => { + viewRef.current = ref; + if (userHybridRef?.f) { + userHybridRef.f(ref); + } + }; + + return ( + + ); }