diff --git a/.gitignore b/.gitignore index 138a447b..b8f3f893 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ node_modules/ npm-debug.log yarn-debug.log yarn-error.log +package-lock.json # Expo .expo/* diff --git a/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt b/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt index 87b58d0f..e9d0ace1 100644 --- a/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +++ b/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt @@ -25,6 +25,7 @@ class NestedScrollableHost : FrameLayout { constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) public var initialIndex: Int? = null public var didSetInitialIndex = false + public var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null private var touchSlop = 0 private var initialX = 0f private var initialY = 0f diff --git a/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt index 8ec286a7..19f46363 100644 --- a/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -52,7 +52,7 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa vp.isSaveEnabled = false vp.post { - vp.registerOnPageChangeCallback(object : OnPageChangeCallback() { + val callback = object : OnPageChangeCallback() { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { super.onPageScrolled(position, positionOffset, positionOffsetPixels) UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent( @@ -79,7 +79,9 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa PageScrollStateChangedEvent(host.id, pageScrollState) ) } - }) + } + host.pageChangeCallback = callback + vp.registerOnPageChangeCallback(callback) UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent( PageSelectedEvent(host.id, vp.currentItem) ) @@ -200,6 +202,20 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa } } + override fun onDropViewInstance(view: NestedScrollableHost) { + // Unregister the page change callback to prevent memory leaks + val viewPager = PagerViewViewManagerImpl.getViewPager(view) + view.pageChangeCallback?.let { callback -> + viewPager.unregisterOnPageChangeCallback(callback) + view.pageChangeCallback = null + } + + // Clear the adapter to release references to child views + viewPager.adapter = null + + super.onDropViewInstance(view) + } + override fun getExportedCustomDirectEventTypeConstants(): MutableMap> { return MapBuilder.of( PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"), diff --git a/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt b/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt index fd3530e1..8aab5f66 100644 --- a/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt +++ b/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt @@ -30,6 +30,12 @@ class ViewPagerAdapter() : Adapter() { container.addView(child) } + override fun onViewRecycled(holder: ViewPagerViewHolder) { + super.onViewRecycled(holder) + // Clean up the holder's container to prevent memory leaks + holder.container.removeAllViews() + } + override fun getItemCount(): Int { return childrenViews.size } diff --git a/ios/RNCPagerViewComponentView.mm b/ios/RNCPagerViewComponentView.mm index ccc2595b..f2aa619d 100644 --- a/ios/RNCPagerViewComponentView.mm +++ b/ios/RNCPagerViewComponentView.mm @@ -81,10 +81,32 @@ - (instancetype)initWithFrame:(CGRect)frame return self; } +- (void)dealloc { + // Clean up delegates to prevent memory leaks + if (_nativePageViewController) { + _nativePageViewController.dataSource = nil; + _nativePageViewController.delegate = nil; + } + + if (scrollView) { + scrollView.delegate = nil; + } +} + - (void)willMoveToSuperview:(UIView *)newSuperview { if (newSuperview != nil) { [self initializeNativePageViewController]; [self goTo:_currentIndex animated:NO]; + } else { + // Component is being removed from view hierarchy, clean up delegates + if (_nativePageViewController) { + _nativePageViewController.dataSource = nil; + _nativePageViewController.delegate = nil; + } + + if (scrollView) { + scrollView.delegate = nil; + } } } @@ -124,6 +146,21 @@ -(void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics -(void)prepareForRecycle { [super prepareForRecycle]; + + // Clear delegates to prevent memory leaks + if (_nativePageViewController) { + _nativePageViewController.dataSource = nil; + _nativePageViewController.delegate = nil; + } + + if (scrollView) { + scrollView.delegate = nil; + scrollView = nil; + } + + // Clear view controllers array + [_nativeChildrenViewControllers removeAllObjects]; + _nativePageViewController = nil; _currentIndex = -1; }