react-native-pdf-jsi 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PdfView.js CHANGED
@@ -83,6 +83,7 @@ export default class PdfView extends Component {
83
83
  scale: this.props.scale,
84
84
  contentOffset: {x: 0, y: 0},
85
85
  newContentOffset: {x: 0, y: 0},
86
+ previousPage: -1, // Track previous page to prevent unnecessary navigations
86
87
  };
87
88
 
88
89
  this._flatList = null;
@@ -121,10 +122,16 @@ export default class PdfView extends Component {
121
122
  this.props.onError(error);
122
123
  });
123
124
 
125
+ // Initialize page navigation after PDF loads
124
126
  clearTimeout(this._scrollTimer);
125
127
  this._scrollTimer = setTimeout(() => {
126
- if (this._flatList) {
127
- this._flatList.scrollToIndex({animated: false, index: this.props.page < 1 ? 0 : this.props.page - 1});
128
+ if (this._flatList && this._mounted) {
129
+ const initialPage = this.props.page < 1 ? 0 : this.props.page - 1;
130
+ this._flatList.scrollToIndex({
131
+ animated: false,
132
+ index: initialPage
133
+ });
134
+ this.setState({ previousPage: this.props.page < 1 ? 1 : this.props.page });
128
135
  }
129
136
  }, 200);
130
137
  }
@@ -139,15 +146,30 @@ export default class PdfView extends Component {
139
146
  });
140
147
  }
141
148
 
142
- if (this.props.horizontal !== prevProps.horizontal || this.props.page !== prevProps.page) {
149
+ // Only navigate if page actually changed and PDF is loaded
150
+ const pageChanged = this.props.page !== prevProps.page;
151
+ const horizontalChanged = this.props.horizontal !== prevProps.horizontal;
152
+
153
+ if ((horizontalChanged || pageChanged) && this.state.pdfLoaded && this._flatList) {
143
154
  let page = (this.props.page) < 1 ? 1 : this.props.page;
144
155
  page = page > this.state.numberOfPages ? this.state.numberOfPages : page;
145
-
146
- if (this._flatList) {
156
+
157
+ // Only navigate if page actually changed from previous navigation
158
+ if (page !== this.state.previousPage) {
147
159
  clearTimeout(this._scrollTimer);
148
160
  this._scrollTimer = setTimeout(() => {
149
- this._flatList.scrollToIndex({animated: false, index: page - 1});
150
- }, 200);
161
+ if (this._flatList && this._mounted) {
162
+ // Use animated: true for smooth transitions when pagination is disabled
163
+ // Use animated: false only when paging is enabled for instant snap
164
+ const shouldAnimate = !this.props.enablePaging;
165
+ this._flatList.scrollToIndex({
166
+ animated: shouldAnimate,
167
+ index: page - 1,
168
+ viewPosition: 0.5 // Center the page in view
169
+ });
170
+ this.setState({ previousPage: page });
171
+ }
172
+ }, horizontalChanged ? 300 : 100); // Longer delay if orientation changed
151
173
  }
152
174
  }
153
175
 
@@ -339,7 +361,10 @@ export default class PdfView extends Component {
339
361
  let data = [];
340
362
 
341
363
  if (this.props.singlePage) {
342
- data[0] = {key: this.props.currentPage >= 0 ? this.props.currentPage : 0}
364
+ // Fix: Use page prop instead of currentPage for singlePage mode
365
+ // This allows the page prop to control which page is displayed
366
+ const pageToShow = this.props.page >= 1 ? this.props.page - 1 : 0;
367
+ data[0] = {key: pageToShow};
343
368
  } else {
344
369
  for (let i = 0; i < this.state.numberOfPages; i++) {
345
370
  data[i] = {key: i};
@@ -362,6 +387,9 @@ export default class PdfView extends Component {
362
387
  windowSize={11}
363
388
  getItemLayout={this._getItemLayout}
364
389
  maxToRenderPerBatch={1}
390
+ // Prevent full rerenders when page changes - only rerender when data actually changes
391
+ extraData={this.props.singlePage ? this.props.page : this.state.numberOfPages}
392
+ removeClippedSubviews={true}
365
393
  renderScrollComponent={(props) => <ScrollView
366
394
  {...props}
367
395
  centerContent={this.state.centerContent}
package/README.md CHANGED
@@ -410,6 +410,9 @@ MIT License - see [LICENSE](LICENSE) file for details.
410
410
 
411
411
  ## Recent Fixes
412
412
 
413
+ ### iOS Performance - Unnecessary Path Handlers (v4.2.0)
414
+ Fixed performance issue where path-related handlers were running unnecessarily when the path value hadn't actually changed. The fix filters out "path" from effectiveChangedProps when pathActuallyChanged=NO, preventing unnecessary reconfigurations of spacing, display direction, scroll views, usePageViewController, and other path-dependent handlers. This reduces unnecessary rerenders and improves performance, especially when navigating between pages. Addresses issue #7 (Page Prop Causes Full Rerender).
415
+
413
416
  ### iOS Pinch-to-Zoom (v4.1.1)
414
417
  Fixed critical issue where pinch-to-zoom gestures were not working on iOS. The fix removes interfering custom gesture recognizers and enables PDFView's native pinch-to-zoom functionality, which now works smoothly on both iOS and Android.
415
418
 
package/index.js CHANGED
@@ -681,6 +681,7 @@ export default class Pdf extends Component {
681
681
  {...this.props}
682
682
  style={[{backgroundColor: '#EEE',overflow: 'hidden'}, this.props.style]}
683
683
  path={this.state.path}
684
+ page={this.props.page}
684
685
  onLoadComplete={this.props.onLoadComplete}
685
686
  onPageChanged={this.props.onPageChanged}
686
687
  onError={this._onError}
@@ -149,6 +149,15 @@ const float MIN_SCALE = 1.0f;
149
149
  NSMutableDictionary *_searchCache;
150
150
  NSString *_currentPdfId;
151
151
  NSOperationQueue *_preloadQueue;
152
+
153
+ // Page navigation state tracking
154
+ int _previousPage;
155
+ BOOL _isNavigating;
156
+ BOOL _documentLoaded;
157
+
158
+ // Track usePageViewController state to prevent unnecessary reconfiguration
159
+ BOOL _currentUsePageViewController;
160
+ BOOL _usePageViewControllerStateInitialized;
152
161
  }
153
162
 
154
163
  #ifdef RCT_NEW_ARCH_ENABLED
@@ -233,6 +242,7 @@ using namespace facebook::react;
233
242
  [updatedPropNames addObject:@"maxScale"];
234
243
  }
235
244
  if (_horizontal != newProps.horizontal) {
245
+ RCTLogInfo(@"🔄 [iOS Scroll] Horizontal prop changed: %d -> %d", _horizontal, newProps.horizontal);
236
246
  _horizontal = newProps.horizontal;
237
247
  [updatedPropNames addObject:@"horizontal"];
238
248
  }
@@ -265,6 +275,7 @@ using namespace facebook::react;
265
275
  [updatedPropNames addObject:@"password"];
266
276
  }
267
277
  if (_singlePage != newProps.singlePage) {
278
+ RCTLogInfo(@"🔄 [iOS Scroll] SinglePage prop changed: %d -> %d", _singlePage, newProps.singlePage);
268
279
  _singlePage = newProps.singlePage;
269
280
  [updatedPropNames addObject:@"singlePage"];
270
281
  }
@@ -326,6 +337,15 @@ using namespace facebook::react;
326
337
  _initialed = YES;
327
338
 
328
339
  [self didSetProps:mProps];
340
+
341
+ // Configure scroll view after layout to ensure it's found
342
+ // This is important because PDFKit creates the scroll view lazily
343
+ if (_documentLoaded && _pdfDocument) {
344
+ dispatch_async(dispatch_get_main_queue(), ^{
345
+ RCTLogInfo(@"🔍 [iOS Scroll] updateLayoutMetrics called, configuring scroll view after layout");
346
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
347
+ });
348
+ }
329
349
  }
330
350
 
331
351
  - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
@@ -369,6 +389,38 @@ using namespace facebook::react;
369
389
  _showsHorizontalScrollIndicator = YES;
370
390
  _showsVerticalScrollIndicator = YES;
371
391
  _scrollEnabled = YES;
392
+
393
+ // Initialize page navigation state
394
+ _previousPage = -1;
395
+ _isNavigating = NO;
396
+ _documentLoaded = NO;
397
+
398
+ // Initialize usePageViewController state tracking
399
+ _currentUsePageViewController = NO;
400
+ _usePageViewControllerStateInitialized = NO;
401
+
402
+ // #region agent log
403
+ {
404
+ NSString *logPath0 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
405
+ NSDictionary *logEntry0 = @{
406
+ @"sessionId": @"debug-session",
407
+ @"runId": @"init",
408
+ @"hypothesisId": @"F",
409
+ @"location": @"RNPDFPdfView.mm:393",
410
+ @"message": @"initCommonProps completed",
411
+ @"data": @{
412
+ @"horizontal": @(_horizontal),
413
+ @"enablePaging": @(_enablePaging),
414
+ @"scrollEnabled": @(_scrollEnabled),
415
+ @"singlePage": @(_singlePage)
416
+ },
417
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
418
+ };
419
+ NSData *logData0 = [NSJSONSerialization dataWithJSONObject:logEntry0 options:0 error:nil];
420
+ NSString *logLine0 = [[NSString alloc] initWithData:logData0 encoding:NSUTF8StringEncoding];
421
+ [[logLine0 stringByAppendingString:@"\n"] writeToFile:logPath0 atomically:YES encoding:NSUTF8StringEncoding error:nil];
422
+ }
423
+ // #endregion
372
424
 
373
425
  // Enhanced properties
374
426
  _enableCaching = YES;
@@ -440,13 +492,63 @@ using namespace facebook::react;
440
492
  _changedProps = changedProps;
441
493
 
442
494
  } else {
495
+ // Log all didSetProps calls to understand what's triggering reconfigurations
496
+ RCTLogInfo(@"📥 [iOS Scroll] didSetProps called - changedProps=%@, initialized=%d, currentUsePageVC=%d",
497
+ changedProps, _usePageViewControllerStateInitialized, _currentUsePageViewController);
443
498
 
444
- if ([changedProps containsObject:@"path"]) {
499
+ // Create filtered changedProps array - remove "path" if it hasn't actually changed
500
+ // This prevents unnecessary reconfigurations when path is in changedProps but value unchanged
501
+ NSArray<NSString *> *effectiveChangedProps = changedProps;
502
+ BOOL pathActuallyChanged = NO;
445
503
 
504
+ if ([changedProps containsObject:@"path"]) {
505
+ // CRITICAL FIX: Only reset state if the path actually changed
506
+ // React Native sometimes includes path in changedProps even when only page changes
507
+
508
+ if (_pdfDocument != Nil && _pdfDocument.documentURL != nil) {
509
+ // Compare new path with existing document's path
510
+ NSString *currentPath = _pdfDocument.documentURL.path;
511
+ NSString *newPath = _path;
512
+ // Normalize paths for comparison (remove trailing slashes, resolve symlinks, etc.)
513
+ if (![currentPath isEqualToString:newPath]) {
514
+ pathActuallyChanged = YES;
515
+ }
516
+ } else {
517
+ // No existing document, so this is a new path (or initial load)
518
+ pathActuallyChanged = YES;
519
+ }
520
+
521
+ RCTLogInfo(@"🔄 [iOS Scroll] Path prop in changedProps - hadDocument=%d, pathActuallyChanged=%d",
522
+ (_pdfDocument != Nil), pathActuallyChanged);
523
+
524
+ // Filter out "path" from effectiveChangedProps if it hasn't actually changed
525
+ if (!pathActuallyChanged) {
526
+ RCTLogInfo(@"⏭️ [iOS Scroll] Path value unchanged, filtering out 'path' from effectiveChangedProps");
527
+ NSMutableArray<NSString *> *filtered = [changedProps mutableCopy];
528
+ [filtered removeObject:@"path"];
529
+ effectiveChangedProps = filtered;
530
+ } else {
531
+ // Path actually changed, use changedProps as-is
532
+ effectiveChangedProps = changedProps;
533
+ }
534
+
535
+ if (!pathActuallyChanged) {
536
+ RCTLogInfo(@"⏭️ [iOS Scroll] Path value unchanged, skipping document reload");
537
+ // Skip the rest of path handling
538
+ } else {
539
+ // Reset document load state when path actually changes
540
+ _documentLoaded = NO;
541
+ _previousPage = -1;
542
+ _isNavigating = NO;
446
543
 
544
+ // Release old doc if it exists
447
545
  if (_pdfDocument != Nil) {
448
- //Release old doc
449
546
  _pdfDocument = Nil;
547
+ _usePageViewControllerStateInitialized = NO;
548
+ _currentUsePageViewController = NO;
549
+ RCTLogInfo(@"🔄 [iOS Scroll] Reset usePageViewController state - path changed (hadDocument=YES)");
550
+ } else {
551
+ RCTLogInfo(@"⏭️ [iOS Scroll] No previous document to reset");
450
552
  }
451
553
 
452
554
  if ([_path hasPrefix:@"blob:"]) {
@@ -482,16 +584,31 @@ using namespace facebook::react;
482
584
  }
483
585
 
484
586
  _pdfView.document = _pdfDocument;
587
+ _documentLoaded = YES;
588
+
589
+ // Configure scroll view after document is set
590
+ // PDFKit creates the scroll view lazily, so we need to wait a bit
591
+ dispatch_async(dispatch_get_main_queue(), ^{
592
+ RCTLogInfo(@"🔍 [iOS Scroll] Document set, searching for scroll view");
593
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
594
+
595
+ // Retry after a short delay to catch cases where scroll view is created asynchronously
596
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
597
+ RCTLogInfo(@"🔍 [iOS Scroll] Retry search for scroll view after delay");
598
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
599
+ });
600
+ });
485
601
  } else {
486
602
 
487
603
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"error|Load pdf failed. path=%s",_path.UTF8String]]];
488
604
 
489
605
  _pdfDocument = Nil;
490
606
  return;
607
+ }
491
608
  }
492
609
  }
493
610
 
494
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"spacing"])) {
611
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"spacing"])) {
495
612
  if (_horizontal) {
496
613
  _pdfView.pageBreakMargins = UIEdgeInsetsMake(0,_spacing,0,0);
497
614
  if (_spacing==0) {
@@ -517,11 +634,11 @@ using namespace facebook::react;
517
634
  }
518
635
  }
519
636
 
520
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enableRTL"])) {
637
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"enableRTL"])) {
521
638
  _pdfView.displaysRTL = _enableRTL;
522
639
  }
523
640
 
524
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enableAnnotationRendering"])) {
641
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"enableAnnotationRendering"])) {
525
642
  if (!_enableAnnotationRendering) {
526
643
  for (unsigned long i=0; i<_pdfView.document.pageCount; i++) {
527
644
  PDFPage *pdfPage = [_pdfView.document pageAtIndex:i];
@@ -532,7 +649,7 @@ using namespace facebook::react;
532
649
  }
533
650
  }
534
651
 
535
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"fitPolicy"] || [changedProps containsObject:@"minScale"] || [changedProps containsObject:@"maxScale"])) {
652
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"fitPolicy"] || [changedProps containsObject:@"minScale"] || [changedProps containsObject:@"maxScale"])) {
536
653
 
537
654
  PDFPage *pdfPage = _pdfView.currentPage ? _pdfView.currentPage : [_pdfDocument pageAtIndex:_pdfDocument.pageCount-1];
538
655
  CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
@@ -570,49 +687,94 @@ using namespace facebook::react;
570
687
 
571
688
  }
572
689
 
573
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"scale"])) {
690
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"scale"])) {
574
691
  _pdfView.scaleFactor = _scale * _fixScaleFactor;
575
692
  if (_pdfView.scaleFactor>_pdfView.maxScaleFactor) _pdfView.scaleFactor = _pdfView.maxScaleFactor;
576
693
  if (_pdfView.scaleFactor<_pdfView.minScaleFactor) _pdfView.scaleFactor = _pdfView.minScaleFactor;
577
694
  }
578
695
 
579
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"horizontal"])) {
696
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"horizontal"])) {
580
697
  if (_horizontal) {
581
698
  _pdfView.displayDirection = kPDFDisplayDirectionHorizontal;
582
699
  _pdfView.pageBreakMargins = UIEdgeInsetsMake(0,_spacing,0,0);
700
+ RCTLogInfo(@"➡️ [iOS Scroll] Set display direction to HORIZONTAL (spacing=%d)", _spacing);
583
701
  } else {
584
702
  _pdfView.displayDirection = kPDFDisplayDirectionVertical;
585
703
  _pdfView.pageBreakMargins = UIEdgeInsetsMake(0,0,_spacing,0);
704
+ RCTLogInfo(@"⬇️ [iOS Scroll] Set display direction to VERTICAL (spacing=%d)", _spacing);
586
705
  }
587
706
  }
588
707
 
589
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enablePaging"])) {
590
- if (_enablePaging) {
708
+ // CRITICAL FIX: Only configure usePageViewController when path changes (document loading)
709
+ // This prevents unnecessary reconfigurations during scrolling and layout updates
710
+ // Once configured, usePageViewController doesn't need to be reconfigured unless the document changes
711
+ if (_pdfDocument && [effectiveChangedProps containsObject:@"path"]) {
712
+ // Fix: Disable usePageViewController when horizontal is true, as it conflicts with horizontal scrolling
713
+ // UIPageViewController doesn't work well with horizontal PDFView display direction
714
+ BOOL shouldUsePageViewController = _enablePaging && !_horizontal;
715
+
716
+ RCTLogInfo(@"🔄 [iOS Scroll] Configuring usePageViewController on document load - enablePaging=%d, horizontal=%d, usePageVC=%d",
717
+ _enablePaging, _horizontal, shouldUsePageViewController);
718
+
719
+ // Set state immediately
720
+ _currentUsePageViewController = shouldUsePageViewController;
721
+ _usePageViewControllerStateInitialized = YES;
722
+
723
+ // Configure usePageViewController - this only happens on document load
724
+ if (shouldUsePageViewController) {
725
+ // Only use page view controller for vertical orientation
591
726
  [_pdfView usePageViewController:YES withViewOptions:@{UIPageViewControllerOptionSpineLocationKey:@(UIPageViewControllerSpineLocationMin),UIPageViewControllerOptionInterPageSpacingKey:@(_spacing)}];
727
+ RCTLogInfo(@"✅ [iOS Scroll] Enabled UIPageViewController (vertical paging mode)");
592
728
  } else {
729
+ // For horizontal or when paging is disabled, use regular scrolling
593
730
  [_pdfView usePageViewController:NO withViewOptions:Nil];
731
+ RCTLogInfo(@"✅ [iOS Scroll] Disabled UIPageViewController (using regular scrolling)");
594
732
  }
733
+
734
+ // Reconfigure scroll view after usePageViewController changes
735
+ // PDFView's internal scroll view hierarchy changes when usePageViewController is toggled
736
+ dispatch_async(dispatch_get_main_queue(), ^{
737
+ // Reset scroll view references to allow reconfiguration
738
+ RCTLogInfo(@"🔄 [iOS Scroll] Resetting scroll view references for reconfiguration");
739
+ self->_internalScrollView = nil;
740
+ self->_originalScrollDelegate = nil;
741
+ self->_scrollDelegateProxy = nil;
742
+
743
+ // Reconfigure scroll view after view hierarchy updates
744
+ dispatch_async(dispatch_get_main_queue(), ^{
745
+ RCTLogInfo(@"🔧 [iOS Scroll] Reconfiguring scroll view after usePageViewController change (scrollEnabled=%d)", self->_scrollEnabled);
746
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
747
+ });
748
+ });
595
749
  }
596
750
 
597
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"singlePage"])) {
751
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"singlePage"])) {
598
752
  if (_singlePage) {
599
753
  _pdfView.displayMode = kPDFDisplaySinglePage;
600
754
  _pdfView.userInteractionEnabled = NO;
755
+ RCTLogInfo(@"📄 [iOS Scroll] Set to SINGLE PAGE mode (userInteractionEnabled=NO)");
601
756
  } else {
602
757
  _pdfView.displayMode = kPDFDisplaySinglePageContinuous;
603
758
  _pdfView.userInteractionEnabled = YES;
759
+ RCTLogInfo(@"📄 [iOS Scroll] Set to CONTINUOUS PAGE mode (userInteractionEnabled=YES)");
604
760
  }
605
761
  }
606
762
 
607
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"showsHorizontalScrollIndicator"] || [changedProps containsObject:@"showsVerticalScrollIndicator"])) {
763
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"showsHorizontalScrollIndicator"] || [changedProps containsObject:@"showsVerticalScrollIndicator"])) {
608
764
  [self setScrollIndicators:self horizontal:_showsHorizontalScrollIndicator vertical:_showsVerticalScrollIndicator depth:0];
609
765
  }
610
766
 
611
767
  // Configure scroll view (scrollEnabled)
612
- if (_pdfDocument && ([changedProps containsObject:@"path"] ||
768
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] ||
613
769
  [changedProps containsObject:@"scrollEnabled"])) {
770
+ RCTLogInfo(@"🔧 [iOS Scroll] Configuring scroll enabled=%d (path changed=%d, scrollEnabled changed=%d)",
771
+ _scrollEnabled,
772
+ [effectiveChangedProps containsObject:@"path"],
773
+ [changedProps containsObject:@"scrollEnabled"]);
774
+
614
775
  // If path changed, restore original delegate before reconfiguring
615
- if ([changedProps containsObject:@"path"] && _internalScrollView) {
776
+ if ([effectiveChangedProps containsObject:@"path"] && _internalScrollView) {
777
+ RCTLogInfo(@"🔄 [iOS Scroll] Restoring original scroll delegate (path changed)");
616
778
  if (_originalScrollDelegate) {
617
779
  _internalScrollView.delegate = _originalScrollDelegate;
618
780
  } else {
@@ -626,34 +788,181 @@ using namespace facebook::react;
626
788
  // Use dispatch_async to ensure view hierarchy is fully set up after document load
627
789
  dispatch_async(dispatch_get_main_queue(), ^{
628
790
  // Search within _pdfView's hierarchy for scroll views
791
+ RCTLogInfo(@"🔍 [iOS Scroll] Starting scroll view search in PDFView hierarchy");
629
792
  [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
630
793
  });
631
794
  }
632
795
 
633
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enablePaging"] || [changedProps containsObject:@"horizontal"] || [changedProps containsObject:@"page"])) {
634
-
796
+ // Separate page navigation logic - only navigate when page prop actually changes
797
+ // Skip navigation on initial load (when path changes) to avoid conflicts
798
+ BOOL shouldNavigateToPage = _documentLoaded &&
799
+ [changedProps containsObject:@"page"] &&
800
+ !_isNavigating &&
801
+ _page != _previousPage &&
802
+ _page > 0 &&
803
+ _page <= (int)_pdfDocument.pageCount;
804
+
805
+ // #region agent log
806
+ if ([changedProps containsObject:@"page"]) {
807
+ NSString *logPath14 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
808
+ NSDictionary *logEntry14 = @{
809
+ @"sessionId": @"debug-session",
810
+ @"runId": @"init",
811
+ @"hypothesisId": @"A,C,D",
812
+ @"location": @"RNPDFPdfView.mm:803",
813
+ @"message": @"updateProps: page prop changed - checking shouldNavigateToPage",
814
+ @"data": @{
815
+ @"_page": @(_page),
816
+ @"_previousPage": @(_previousPage),
817
+ @"documentLoaded": @(_documentLoaded),
818
+ @"isNavigating": @(_isNavigating),
819
+ @"shouldNavigateToPage": @(shouldNavigateToPage),
820
+ @"contentOffsetBeforeNav": _internalScrollView ? @{@"x": @(_internalScrollView.contentOffset.x), @"y": @(_internalScrollView.contentOffset.y)} : @"noScrollView"
821
+ },
822
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
823
+ };
824
+ NSData *logData14 = [NSJSONSerialization dataWithJSONObject:logEntry14 options:0 error:nil];
825
+ NSString *logLine14 = [[NSString alloc] initWithData:logData14 encoding:NSUTF8StringEncoding];
826
+ NSFileHandle *fileHandle14 = [NSFileHandle fileHandleForWritingAtPath:logPath14];
827
+ if (fileHandle14) {
828
+ [fileHandle14 seekToEndOfFile];
829
+ [fileHandle14 writeData:[[logLine14 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
830
+ [fileHandle14 closeFile];
831
+ } else {
832
+ [[logLine14 stringByAppendingString:@"\n"] writeToFile:logPath14 atomically:YES encoding:NSUTF8StringEncoding error:nil];
833
+ }
834
+ }
835
+ // #endregion
836
+
837
+ if (shouldNavigateToPage) {
838
+ _isNavigating = YES;
839
+ PDFPage *pdfPage = [_pdfDocument pageAtIndex:_page-1];
840
+
841
+ if (pdfPage) {
842
+ // Use smooth navigation instead of instant jump to prevent full rerender
843
+ dispatch_async(dispatch_get_main_queue(), ^{
844
+ // #region agent log
845
+ CGPoint contentOffsetBefore = self->_internalScrollView ? self->_internalScrollView.contentOffset : CGPointMake(0, 0);
846
+ NSString *logPath15 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
847
+ NSDictionary *logEntry15 = @{
848
+ @"sessionId": @"debug-session",
849
+ @"runId": @"init",
850
+ @"hypothesisId": @"B,C",
851
+ @"location": @"RNPDFPdfView.mm:812",
852
+ @"message": @"goToDestination: BEFORE navigation call",
853
+ @"data": @{
854
+ @"targetPage": @(self->_page),
855
+ @"enablePaging": @(self->_enablePaging),
856
+ @"contentOffsetBefore": @{@"x": @(contentOffsetBefore.x), @"y": @(contentOffsetBefore.y)},
857
+ @"isNavigating": @(self->_isNavigating)
858
+ },
859
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
860
+ };
861
+ NSData *logData15 = [NSJSONSerialization dataWithJSONObject:logEntry15 options:0 error:nil];
862
+ NSString *logLine15 = [[NSString alloc] initWithData:logData15 encoding:NSUTF8StringEncoding];
863
+ NSFileHandle *fileHandle15 = [NSFileHandle fileHandleForWritingAtPath:logPath15];
864
+ if (fileHandle15) {
865
+ [fileHandle15 seekToEndOfFile];
866
+ [fileHandle15 writeData:[[logLine15 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
867
+ [fileHandle15 closeFile];
868
+ } else {
869
+ [[logLine15 stringByAppendingString:@"\n"] writeToFile:logPath15 atomically:YES encoding:NSUTF8StringEncoding error:nil];
870
+ }
871
+ // #endregion
872
+
873
+ if (!self->_enablePaging) {
874
+ // For non-paging mode, use animated navigation
875
+ CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
876
+
877
+ // Handle page rotation
878
+ if (pdfPage.rotation == 90 || pdfPage.rotation == 270) {
879
+ pdfPageRect = CGRectMake(0, 0, pdfPageRect.size.height, pdfPageRect.size.width);
880
+ }
881
+
882
+ CGPoint pointLeftTop = CGPointMake(0, pdfPageRect.size.height);
883
+ PDFDestination *pdfDest = [[PDFDestination alloc] initWithPage:pdfPage atPoint:pointLeftTop];
884
+
885
+ // Use goToDestination for smooth navigation
886
+ [self->_pdfView goToDestination:pdfDest];
887
+ self->_pdfView.scaleFactor = self->_fixScaleFactor * self->_scale;
888
+ } else {
889
+ // For paging mode, use goToRect for better page alignment
890
+ if (self->_page == 1) {
891
+ // Special case for first page
892
+ [self->_pdfView goToRect:CGRectMake(0, NSUIntegerMax, 1, 1) onPage:pdfPage];
893
+ } else {
894
+ CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
895
+ if (pdfPage.rotation == 90 || pdfPage.rotation == 270) {
896
+ pdfPageRect = CGRectMake(0, 0, pdfPageRect.size.height, pdfPageRect.size.width);
897
+ }
898
+ CGPoint pointLeftTop = CGPointMake(0, pdfPageRect.size.height);
899
+ PDFDestination *pdfDest = [[PDFDestination alloc] initWithPage:pdfPage atPoint:pointLeftTop];
900
+ [self->_pdfView goToDestination:pdfDest];
901
+ self->_pdfView.scaleFactor = self->_fixScaleFactor * self->_scale;
902
+ }
903
+ }
904
+
905
+ self->_previousPage = self->_page;
906
+ self->_isNavigating = NO;
907
+
908
+ // #region agent log
909
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
910
+ CGPoint contentOffsetAfter = self->_internalScrollView ? self->_internalScrollView.contentOffset : CGPointMake(0, 0);
911
+ NSString *logPath16 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
912
+ NSDictionary *logEntry16 = @{
913
+ @"sessionId": @"debug-session",
914
+ @"runId": @"init",
915
+ @"hypothesisId": @"B,C",
916
+ @"location": @"RNPDFPdfView.mm:845",
917
+ @"message": @"goToDestination: AFTER navigation call (100ms delay)",
918
+ @"data": @{
919
+ @"targetPage": @(self->_page),
920
+ @"previousPage": @(self->_previousPage),
921
+ @"contentOffsetBefore": @{@"x": @(contentOffsetBefore.x), @"y": @(contentOffsetBefore.y)},
922
+ @"contentOffsetAfter": @{@"x": @(contentOffsetAfter.x), @"y": @(contentOffsetAfter.y)},
923
+ @"offsetChanged": @(fabs(contentOffsetBefore.x - contentOffsetAfter.x) > 1 || fabs(contentOffsetBefore.y - contentOffsetAfter.y) > 1)
924
+ },
925
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
926
+ };
927
+ NSData *logData16 = [NSJSONSerialization dataWithJSONObject:logEntry16 options:0 error:nil];
928
+ NSString *logLine16 = [[NSString alloc] initWithData:logData16 encoding:NSUTF8StringEncoding];
929
+ NSFileHandle *fileHandle16 = [NSFileHandle fileHandleForWritingAtPath:logPath16];
930
+ if (fileHandle16) {
931
+ [fileHandle16 seekToEndOfFile];
932
+ [fileHandle16 writeData:[[logLine16 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
933
+ [fileHandle16 closeFile];
934
+ } else {
935
+ [[logLine16 stringByAppendingString:@"\n"] writeToFile:logPath16 atomically:YES encoding:NSUTF8StringEncoding error:nil];
936
+ }
937
+ });
938
+ // #endregion
939
+ });
940
+ } else {
941
+ _isNavigating = NO;
942
+ }
943
+ }
944
+
945
+ // Handle initial page on document load (only when path changes)
946
+ // This handles the case where the document was just loaded and we need to navigate to the initial page
947
+ // Use pathActuallyChanged instead of checking changedProps to ensure we only handle initial page when path actually changed
948
+ if (_pdfDocument && pathActuallyChanged && _documentLoaded) {
635
949
  PDFPage *pdfPage = [_pdfDocument pageAtIndex:_page-1];
636
950
  if (pdfPage && _page == 1) {
637
- // goToDestination() would be better. However, there is an
638
- // error in the pointLeftTop computation that often results in
639
- // scrolling to the middle of the page.
640
- // Special case workaround to make starting at the first page
641
- // align acceptably.
951
+ // Special case workaround for first page alignment
642
952
  dispatch_async(dispatch_get_main_queue(), ^{
643
953
  [self->_pdfView goToRect:CGRectMake(0, NSUIntegerMax, 1, 1) onPage:pdfPage];
954
+ self->_previousPage = self->_page;
644
955
  });
645
956
  } else if (pdfPage) {
646
957
  CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
647
-
648
- // some pdf with rotation, then adjust it
649
958
  if (pdfPage.rotation == 90 || pdfPage.rotation == 270) {
650
959
  pdfPageRect = CGRectMake(0, 0, pdfPageRect.size.height, pdfPageRect.size.width);
651
960
  }
652
-
653
961
  CGPoint pointLeftTop = CGPointMake(0, pdfPageRect.size.height);
654
962
  PDFDestination *pdfDest = [[PDFDestination alloc] initWithPage:pdfPage atPoint:pointLeftTop];
655
963
  [_pdfView goToDestination:pdfDest];
656
964
  _pdfView.scaleFactor = _fixScaleFactor*_scale;
965
+ _previousPage = _page;
657
966
  }
658
967
  }
659
968
 
@@ -676,6 +985,15 @@ using namespace facebook::react;
676
985
  _initialed = YES;
677
986
 
678
987
  [self didSetProps:mProps];
988
+
989
+ // Configure scroll view after layout to ensure it's found
990
+ // This is important because PDFKit creates the scroll view lazily
991
+ if (_documentLoaded && _pdfDocument) {
992
+ dispatch_async(dispatch_get_main_queue(), ^{
993
+ RCTLogInfo(@"🔍 [iOS Scroll] reactSetFrame called, configuring scroll view after layout");
994
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
995
+ });
996
+ }
679
997
  }
680
998
 
681
999
 
@@ -849,7 +1167,21 @@ using namespace facebook::react;
849
1167
  unsigned long numberOfPages = _pdfDocument.pageCount;
850
1168
 
851
1169
  // Update current page for preloading
852
- _page = (int)page + 1;
1170
+ int newPage = (int)page + 1;
1171
+
1172
+ // CRITICAL FIX: Update _previousPage to the new page value when page changes from PDFView notifications
1173
+ // This prevents updateProps from triggering programmatic navigation when React Native
1174
+ // receives the pageChanged notification and updates the page prop back to us.
1175
+ // By setting _previousPage = newPage, when updateProps checks _page != _previousPage,
1176
+ // they will be equal (since the page prop will match the new page), and navigation will be skipped.
1177
+ if (newPage != _page) {
1178
+ _previousPage = newPage; // Set to newPage to prevent navigation loop
1179
+ _page = newPage;
1180
+ } else {
1181
+ // If page didn't actually change, just ensure _previousPage matches to prevent navigation
1182
+ _previousPage = _page;
1183
+ }
1184
+
853
1185
  _pageCount = (int)numberOfPages;
854
1186
  if (_enablePreloading) {
855
1187
  [self preloadAdjacentPages:_page];
@@ -1064,48 +1396,253 @@ using namespace facebook::react;
1064
1396
  }
1065
1397
 
1066
1398
  - (void)configureScrollView:(UIView *)view enabled:(BOOL)enabled depth:(int)depth {
1399
+ // Log entry to track all calls
1400
+ if (depth == 0) {
1401
+ RCTLogInfo(@"🚀 [iOS Scroll] configureScrollView called - enabled=%d, view=%@", enabled, NSStringFromClass([view class]));
1402
+ }
1403
+
1067
1404
  // max depth, prevent infinite loop
1068
1405
  if (depth > 10) {
1406
+ RCTLogWarn(@"⚠️ [iOS Scroll] Max depth reached in configureScrollView (depth=%d)", depth);
1069
1407
  return;
1070
1408
  }
1071
1409
 
1072
1410
  if ([view isKindOfClass:[UIScrollView class]]) {
1073
1411
  UIScrollView *scrollView = (UIScrollView *)view;
1412
+ RCTLogInfo(@"📱 [iOS Scroll] Found UIScrollView at depth=%d, frame=%@, contentSize=%@, enabled=%d",
1413
+ depth,
1414
+ NSStringFromCGRect(scrollView.frame),
1415
+ NSStringFromCGSize(scrollView.contentSize),
1416
+ enabled);
1417
+
1418
+ // #region agent log
1419
+ {
1420
+ NSString *logPath1 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
1421
+ NSDictionary *logEntry1 = @{
1422
+ @"sessionId": @"debug-session",
1423
+ @"runId": @"init",
1424
+ @"hypothesisId": @"D,F",
1425
+ @"location": @"RNPDFPdfView.mm:1307",
1426
+ @"message": @"Found UIScrollView in hierarchy",
1427
+ @"data": @{
1428
+ @"depth": @(depth),
1429
+ @"contentSize": @{@"width": @(scrollView.contentSize.width), @"height": @(scrollView.contentSize.height)},
1430
+ @"frame": @{@"x": @(scrollView.frame.origin.x), @"y": @(scrollView.frame.origin.y), @"width": @(scrollView.frame.size.width), @"height": @(scrollView.frame.size.height)},
1431
+ @"scrollEnabled": @(scrollView.scrollEnabled),
1432
+ @"alwaysBounceHorizontal": @(scrollView.alwaysBounceHorizontal),
1433
+ @"userInteractionEnabled": @(scrollView.userInteractionEnabled),
1434
+ @"horizontal": @(_horizontal),
1435
+ @"enablePaging": @(_enablePaging)
1436
+ },
1437
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
1438
+ };
1439
+ NSData *logData1 = [NSJSONSerialization dataWithJSONObject:logEntry1 options:0 error:nil];
1440
+ NSString *logLine1 = [[NSString alloc] initWithData:logData1 encoding:NSUTF8StringEncoding];
1441
+ NSFileHandle *fileHandle1 = [NSFileHandle fileHandleForWritingAtPath:logPath1];
1442
+ if (fileHandle1) {
1443
+ [fileHandle1 seekToEndOfFile];
1444
+ [fileHandle1 writeData:[[logLine1 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
1445
+ [fileHandle1 closeFile];
1446
+ } else {
1447
+ [[logLine1 stringByAppendingString:@"\n"] writeToFile:logPath1 atomically:YES encoding:NSUTF8StringEncoding error:nil];
1448
+ }
1449
+ }
1450
+ // #endregion
1451
+
1074
1452
  // Since we're starting the recursion from _pdfView, all scroll views found are within its hierarchy
1075
1453
  // Configure scroll properties
1454
+ BOOL previousScrollEnabled = scrollView.scrollEnabled;
1076
1455
  scrollView.scrollEnabled = enabled;
1077
1456
 
1078
- // Disable horizontal bouncing to prevent interference with navigation swipe-back
1457
+ if (previousScrollEnabled != enabled) {
1458
+ RCTLogInfo(@"🔄 [iOS Scroll] Changed scrollEnabled: %d -> %d", previousScrollEnabled, enabled);
1459
+ }
1460
+
1461
+ // Conditionally set horizontal bouncing based on scroll direction
1462
+ // Allow horizontal bounce when horizontal scrolling is enabled
1463
+ BOOL previousAlwaysBounceHorizontal = scrollView.alwaysBounceHorizontal;
1464
+ if (_horizontal) {
1465
+ scrollView.alwaysBounceHorizontal = YES;
1466
+ } else {
1467
+ // Disable horizontal bouncing for vertical scrolling to prevent interference with navigation swipe-back
1079
1468
  scrollView.alwaysBounceHorizontal = NO;
1469
+ }
1470
+
1471
+ if (previousAlwaysBounceHorizontal != scrollView.alwaysBounceHorizontal) {
1472
+ RCTLogInfo(@"🔄 [iOS Scroll] Changed alwaysBounceHorizontal: %d -> %d (horizontal=%d)",
1473
+ previousAlwaysBounceHorizontal,
1474
+ scrollView.alwaysBounceHorizontal,
1475
+ _horizontal);
1476
+ }
1477
+
1080
1478
  // Keep vertical bounce enabled for natural scrolling feel
1081
1479
  scrollView.bounces = YES;
1480
+
1481
+ RCTLogInfo(@"📊 [iOS Scroll] ScrollView config - scrollEnabled=%d, alwaysBounceHorizontal=%d, bounces=%d, delegate=%@",
1482
+ scrollView.scrollEnabled,
1483
+ scrollView.alwaysBounceHorizontal,
1484
+ scrollView.bounces,
1485
+ scrollView.delegate != nil ? @"set" : @"nil");
1486
+
1487
+ // #region agent log
1488
+ {
1489
+ NSString *logPath3 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
1490
+ NSDictionary *logEntry3 = @{
1491
+ @"sessionId": @"debug-session",
1492
+ @"runId": @"init",
1493
+ @"hypothesisId": @"A,B,C,D,E",
1494
+ @"location": @"RNPDFPdfView.mm:1374",
1495
+ @"message": @"ScrollView configuration completed",
1496
+ @"data": @{
1497
+ @"scrollEnabled": @(scrollView.scrollEnabled),
1498
+ @"alwaysBounceHorizontal": @(scrollView.alwaysBounceHorizontal),
1499
+ @"bounces": @(scrollView.bounces),
1500
+ @"contentSize": @{@"width": @(scrollView.contentSize.width), @"height": @(scrollView.contentSize.height)},
1501
+ @"userInteractionEnabled": @(scrollView.userInteractionEnabled),
1502
+ @"delegate": scrollView.delegate != nil ? @"set" : @"nil",
1503
+ @"horizontal": @(_horizontal),
1504
+ @"enablePaging": @(_enablePaging)
1505
+ },
1506
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
1507
+ };
1508
+ NSData *logData3 = [NSJSONSerialization dataWithJSONObject:logEntry3 options:0 error:nil];
1509
+ NSString *logLine3 = [[NSString alloc] initWithData:logData3 encoding:NSUTF8StringEncoding];
1510
+ NSFileHandle *fileHandle3 = [NSFileHandle fileHandleForWritingAtPath:logPath3];
1511
+ if (fileHandle3) {
1512
+ [fileHandle3 seekToEndOfFile];
1513
+ [fileHandle3 writeData:[[logLine3 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
1514
+ [fileHandle3 closeFile];
1515
+ } else {
1516
+ [[logLine3 stringByAppendingString:@"\n"] writeToFile:logPath3 atomically:YES encoding:NSUTF8StringEncoding error:nil];
1517
+ }
1518
+ }
1519
+ // #endregion
1082
1520
 
1083
1521
  // IMPORTANT: PDFKit relies on the scrollView delegate for pinch-zoom (viewForZoomingInScrollView).
1084
1522
  // Install a proxy delegate that forwards to the original delegate, while still letting us observe scroll events.
1085
1523
  if (!_internalScrollView) {
1524
+ RCTLogInfo(@"✅ [iOS Scroll] Setting internal scroll view reference");
1086
1525
  _internalScrollView = scrollView;
1087
1526
  if (scrollView.delegate && scrollView.delegate != self) {
1088
1527
  _originalScrollDelegate = scrollView.delegate;
1528
+ RCTLogInfo(@"📝 [iOS Scroll] Stored original scroll delegate");
1089
1529
  }
1090
1530
  if (_originalScrollDelegate) {
1091
1531
  _scrollDelegateProxy = [[RNPDFScrollViewDelegateProxy alloc] initWithPrimary:_originalScrollDelegate secondary:(id<UIScrollViewDelegate>)self];
1092
1532
  scrollView.delegate = (id<UIScrollViewDelegate>)_scrollDelegateProxy;
1533
+ RCTLogInfo(@"🔗 [iOS Scroll] Installed scroll delegate proxy");
1093
1534
  } else {
1094
1535
  scrollView.delegate = self;
1536
+ RCTLogInfo(@"🔗 [iOS Scroll] Set self as scroll delegate");
1095
1537
  }
1538
+ } else {
1539
+ RCTLogInfo(@"⚠️ [iOS Scroll] Internal scroll view already set, skipping delegate setup");
1096
1540
  }
1097
1541
  }
1098
1542
 
1099
1543
  for (UIView *subview in view.subviews) {
1100
1544
  [self configureScrollView:subview enabled:enabled depth:depth + 1];
1101
1545
  }
1546
+
1547
+ // Log at root level if no scroll view was found
1548
+ if (depth == 0 && !_internalScrollView) {
1549
+ RCTLogWarn(@"⚠️ [iOS Scroll] No UIScrollView found in view hierarchy (view=%@, subviewCount=%lu)",
1550
+ NSStringFromClass([view class]),
1551
+ (unsigned long)[view.subviews count]);
1552
+
1553
+ // #region agent log
1554
+ {
1555
+ NSString *logPath6 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
1556
+ NSMutableArray *subviewClasses = [NSMutableArray array];
1557
+ for (UIView *subview in view.subviews) {
1558
+ [subviewClasses addObject:NSStringFromClass([subview class])];
1559
+ }
1560
+ NSDictionary *logEntry6 = @{
1561
+ @"sessionId": @"debug-session",
1562
+ @"runId": @"init",
1563
+ @"hypothesisId": @"F",
1564
+ @"location": @"RNPDFPdfView.mm:1446",
1565
+ @"message": @"No UIScrollView found in hierarchy",
1566
+ @"data": @{
1567
+ @"viewClass": NSStringFromClass([view class]),
1568
+ @"subviewCount": @([view.subviews count]),
1569
+ @"subviewClasses": subviewClasses,
1570
+ @"horizontal": @(_horizontal),
1571
+ @"enablePaging": @(_enablePaging)
1572
+ },
1573
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
1574
+ };
1575
+ NSData *logData6 = [NSJSONSerialization dataWithJSONObject:logEntry6 options:0 error:nil];
1576
+ NSString *logLine6 = [[NSString alloc] initWithData:logData6 encoding:NSUTF8StringEncoding];
1577
+ NSFileHandle *fileHandle6 = [NSFileHandle fileHandleForWritingAtPath:logPath6];
1578
+ if (fileHandle6) {
1579
+ [fileHandle6 seekToEndOfFile];
1580
+ [fileHandle6 writeData:[[logLine6 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
1581
+ [fileHandle6 closeFile];
1582
+ } else {
1583
+ [[logLine6 stringByAppendingString:@"\n"] writeToFile:logPath6 atomically:YES encoding:NSUTF8StringEncoding error:nil];
1584
+ }
1585
+ }
1586
+ // #endregion
1587
+ }
1102
1588
  }
1103
1589
 
1104
1590
  #pragma mark - UIScrollViewDelegate
1105
1591
 
1106
1592
  - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
1593
+ static int scrollEventCount = 0;
1594
+ scrollEventCount++;
1595
+
1596
+ // Log scroll events periodically (every 10th event to avoid spam)
1597
+ if (scrollEventCount % 10 == 0) {
1598
+ RCTLogInfo(@"📜 [iOS Scroll] scrollViewDidScroll #%d - offset=(%.2f, %.2f), contentSize=(%.2f, %.2f), bounds=(%.2f, %.2f), scrollEnabled=%d",
1599
+ scrollEventCount,
1600
+ scrollView.contentOffset.x,
1601
+ scrollView.contentOffset.y,
1602
+ scrollView.contentSize.width,
1603
+ scrollView.contentSize.height,
1604
+ scrollView.bounds.size.width,
1605
+ scrollView.bounds.size.height,
1606
+ scrollView.scrollEnabled);
1607
+
1608
+ // #region agent log
1609
+ {
1610
+ NSString *logPath2 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
1611
+ NSDictionary *logEntry2 = @{
1612
+ @"sessionId": @"debug-session",
1613
+ @"runId": @"init",
1614
+ @"hypothesisId": @"B",
1615
+ @"location": @"RNPDFPdfView.mm:1300",
1616
+ @"message": @"scrollViewDidScroll called",
1617
+ @"data": @{
1618
+ @"eventCount": @(scrollEventCount),
1619
+ @"contentOffset": @{@"x": @(scrollView.contentOffset.x), @"y": @(scrollView.contentOffset.y)},
1620
+ @"contentSize": @{@"width": @(scrollView.contentSize.width), @"height": @(scrollView.contentSize.height)},
1621
+ @"bounds": @{@"width": @(scrollView.bounds.size.width), @"height": @(scrollView.bounds.size.height)},
1622
+ @"scrollEnabled": @(scrollView.scrollEnabled),
1623
+ @"alwaysBounceHorizontal": @(scrollView.alwaysBounceHorizontal)
1624
+ },
1625
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
1626
+ };
1627
+ NSData *logData2 = [NSJSONSerialization dataWithJSONObject:logEntry2 options:0 error:nil];
1628
+ NSString *logLine2 = [[NSString alloc] initWithData:logData2 encoding:NSUTF8StringEncoding];
1629
+ NSFileHandle *fileHandle2 = [NSFileHandle fileHandleForWritingAtPath:logPath2];
1630
+ if (fileHandle2) {
1631
+ [fileHandle2 seekToEndOfFile];
1632
+ [fileHandle2 writeData:[[logLine2 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
1633
+ [fileHandle2 closeFile];
1634
+ } else {
1635
+ [[logLine2 stringByAppendingString:@"\n"] writeToFile:logPath2 atomically:YES encoding:NSUTF8StringEncoding error:nil];
1636
+ }
1637
+ }
1638
+ // #endregion
1639
+ }
1107
1640
 
1108
1641
  if (!_pdfDocument || _singlePage) {
1642
+ if (scrollEventCount % 10 == 0) {
1643
+ RCTLogInfo(@"⏭️ [iOS Scroll] Skipping scroll handling - pdfDocument=%d, singlePage=%d",
1644
+ _pdfDocument != nil, _singlePage);
1645
+ }
1109
1646
  return;
1110
1647
  }
1111
1648
 
@@ -1126,7 +1663,46 @@ using namespace facebook::react;
1126
1663
 
1127
1664
  // Only update if page actually changed and is valid
1128
1665
  if (newPage != _page && newPage > 0 && newPage <= (int)_pdfDocument.pageCount) {
1666
+ RCTLogInfo(@"📄 [iOS Scroll] Page changed: %d -> %d (from scroll position)", _page, newPage);
1667
+ // #region agent log
1668
+ {
1669
+ NSString *logPath12 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
1670
+ NSDictionary *logEntry12 = @{
1671
+ @"sessionId": @"debug-session",
1672
+ @"runId": @"init",
1673
+ @"hypothesisId": @"A,C,D",
1674
+ @"location": @"RNPDFPdfView.mm:1558",
1675
+ @"message": @"scrollViewDidScroll detected page change - BEFORE updating _page",
1676
+ @"data": @{
1677
+ @"oldPage": @(_page),
1678
+ @"newPage": @(newPage),
1679
+ @"previousPage": @(_previousPage),
1680
+ @"contentOffset": @{@"x": @(scrollView.contentOffset.x), @"y": @(scrollView.contentOffset.y)},
1681
+ @"isNavigating": @(_isNavigating)
1682
+ },
1683
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
1684
+ };
1685
+ NSData *logData12 = [NSJSONSerialization dataWithJSONObject:logEntry12 options:0 error:nil];
1686
+ NSString *logLine12 = [[NSString alloc] initWithData:logData12 encoding:NSUTF8StringEncoding];
1687
+ NSFileHandle *fileHandle12 = [NSFileHandle fileHandleForWritingAtPath:logPath12];
1688
+ if (fileHandle12) {
1689
+ [fileHandle12 seekToEndOfFile];
1690
+ [fileHandle12 writeData:[[logLine12 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
1691
+ [fileHandle12 closeFile];
1692
+ } else {
1693
+ [[logLine12 stringByAppendingString:@"\n"] writeToFile:logPath12 atomically:YES encoding:NSUTF8StringEncoding error:nil];
1694
+ }
1695
+ }
1696
+ // #endregion
1697
+
1698
+ // CRITICAL FIX: Update _previousPage to the new page value when page changes from user scrolling
1699
+ // This prevents updateProps from triggering programmatic navigation when React Native
1700
+ // receives the pageChanged notification and updates the page prop back to us.
1701
+ // By setting _previousPage = newPage, when updateProps checks _page != _previousPage,
1702
+ // they will be equal (since React Native will set _page = newPage), and navigation will be skipped.
1703
+ int oldPage = _page;
1129
1704
  _page = newPage;
1705
+ _previousPage = newPage; // Set to newPage to prevent navigation loop
1130
1706
  _pageCount = (int)_pdfDocument.pageCount;
1131
1707
 
1132
1708
  // Trigger preloading if enabled
@@ -1136,6 +1712,39 @@ using namespace facebook::react;
1136
1712
 
1137
1713
  // Notify about page change
1138
1714
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"pageChanged|%d|%lu", newPage, _pdfDocument.pageCount]]];
1715
+ // #region agent log
1716
+ {
1717
+ NSString *logPath13 = @"/Users/punithmanthri/Documents/github jsi folder /react-native-enhanced-pdf/.cursor/debug.log";
1718
+ NSDictionary *logEntry13 = @{
1719
+ @"sessionId": @"debug-session",
1720
+ @"runId": @"init",
1721
+ @"hypothesisId": @"A,C,D",
1722
+ @"location": @"RNPDFPdfView.mm:1570",
1723
+ @"message": @"scrollViewDidScroll detected page change - AFTER updating _page and _previousPage (to prevent navigation loop)",
1724
+ @"data": @{
1725
+ @"_page": @(_page),
1726
+ @"_previousPage": @(_previousPage),
1727
+ @"notificationSent": @YES
1728
+ },
1729
+ @"timestamp": @((long long)([[NSDate date] timeIntervalSince1970] * 1000))
1730
+ };
1731
+ NSData *logData13 = [NSJSONSerialization dataWithJSONObject:logEntry13 options:0 error:nil];
1732
+ NSString *logLine13 = [[NSString alloc] initWithData:logData13 encoding:NSUTF8StringEncoding];
1733
+ NSFileHandle *fileHandle13 = [NSFileHandle fileHandleForWritingAtPath:logPath13];
1734
+ if (fileHandle13) {
1735
+ [fileHandle13 seekToEndOfFile];
1736
+ [fileHandle13 writeData:[[logLine13 stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
1737
+ [fileHandle13 closeFile];
1738
+ } else {
1739
+ [[logLine13 stringByAppendingString:@"\n"] writeToFile:logPath13 atomically:YES encoding:NSUTF8StringEncoding error:nil];
1740
+ }
1741
+ }
1742
+ // #endregion
1743
+ }
1744
+ } else {
1745
+ if (scrollEventCount % 50 == 0) {
1746
+ RCTLogWarn(@"⚠️ [iOS Scroll] No visible page found for scroll position (%.2f, %.2f)",
1747
+ pdfPoint.x, pdfPoint.y);
1139
1748
  }
1140
1749
  }
1141
1750
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-pdf-jsi",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
4
4
  "summary": "High-performance React Native PDF viewer with JSI acceleration - up to 80x faster than traditional bridge",
5
5
  "description": "🚀 Ultra-fast React Native PDF viewer with JSI (JavaScript Interface) integration for maximum performance. Features lazy loading, smart caching, progressive loading, and zero-bridge overhead operations. Perfect for large PDF files with 30-day persistent cache and advanced memory optimization. Google Play 16KB page size compliant for Android 15+. Supports iOS, Android, and Windows platforms.",
6
6
  "main": "index.js",