diff --git a/ios/PagerScrollDelegate.swift b/ios/PagerScrollDelegate.swift index 5b09e032..e9cb1be4 100644 --- a/ios/PagerScrollDelegate.swift +++ b/ios/PagerScrollDelegate.swift @@ -7,6 +7,8 @@ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDeleg weak var originalDelegate: UICollectionViewDelegate? weak var delegate: PagerViewProviderDelegate? var orientation: UICollectionView.ScrollDirection = .horizontal + var lastSelectedPage: Int = -1 + var isProgrammaticScroll: Bool = false private let handledSelectors: Set = [ #selector(scrollViewDidScroll(_:)), @@ -61,21 +63,48 @@ class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDeleg } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + emitPageSelectedIfNeeded(scrollView) + delegate?.onPageScrollStateChanged(state: .idle) emitIdleWithCleanOffset(scrollView) originalDelegate?.scrollViewDidEndDecelerating?(scrollView) } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + emitPageSelectedIfNeeded(scrollView) + delegate?.onPageScrollStateChanged(state: .idle) emitIdleWithCleanOffset(scrollView) originalDelegate?.scrollViewDidEndScrollingAnimation?(scrollView) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { + emitPageSelectedIfNeeded(scrollView) + delegate?.onPageScrollStateChanged(state: .idle) emitIdleWithCleanOffset(scrollView) } originalDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) } + + private func emitPageSelectedIfNeeded(_ scrollView: UIScrollView) { + // Skip during programmatic navigation — .onChange handles that + if isProgrammaticScroll { + isProgrammaticScroll = false + return + } + + let isHorizontal = orientation == .horizontal + let pageSize = isHorizontal ? scrollView.frame.width : scrollView.frame.height + let contentOffset = isHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y + + guard pageSize > 0 else { return } + + let page = Int(round(contentOffset / pageSize)) + + if page != lastSelectedPage { + lastSelectedPage = page + delegate?.onPageSelected(position: page) + } + } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { originalDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath) diff --git a/ios/PagerView.swift b/ios/PagerView.swift index 7b296a0e..40235f10 100644 --- a/ios/PagerView.swift +++ b/ios/PagerView.swift @@ -54,7 +54,11 @@ struct PagerView: View { } } .onChange(of: props.currentPage) { newValue in - delegate?.onPageSelected(position: newValue) + if scrollDelegate.lastSelectedPage != newValue { + scrollDelegate.lastSelectedPage = newValue + scrollDelegate.isProgrammaticScroll = true + delegate?.onPageSelected(position: newValue) + } } .onChange(of: props.scrollEnabled) { newValue in collectionView?.isScrollEnabled = newValue diff --git a/ios/RNCPagerViewComponentView.mm b/ios/RNCPagerViewComponentView.mm index eeeecde6..2ade1cae 100644 --- a/ios/RNCPagerViewComponentView.mm +++ b/ios/RNCPagerViewComponentView.mm @@ -67,6 +67,12 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(cons if (_pagerViewProvider.currentPage == -1) { _pagerViewProvider.currentPage = newScreenProps.initialPage; + // Defer initial onPageSelected until event emitter is available + if (newScreenProps.initialPage > 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self onPageSelectedWithPosition:newScreenProps.initialPage]; + }); + } } if (oldScreenProps.scrollEnabled != newScreenProps.scrollEnabled) { @@ -103,16 +109,19 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(cons - (void)onPageScrollWithData:(OnPageScrollEventData *)data { const auto eventEmitter = [self pagerEventEmitter]; + if (!eventEmitter) return; [self sendScrollEventsForPosition:data.position offset:data.offset]; } - (void)onPageSelectedWithPosition:(NSInteger)position { const auto eventEmitter = [self pagerEventEmitter]; + if (!eventEmitter) return; eventEmitter->onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(position)}); } - (void)onPageScrollStateChangedWithState:(enum PageScrollState)state { const auto eventEmitter = [self pagerEventEmitter]; + if (!eventEmitter) return; RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState scrollState;