react-native-pdf-jsi 4.1.2 → 4.2.1

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
@@ -409,6 +409,11 @@ MIT License - see [LICENSE](LICENSE) file for details.
409
409
  - **Author**: Punith M ([@126punith](https://github.com/126punith))
410
410
 
411
411
  ## Recent Fixes
412
+ ### iOS Performance - Unnecessary Path Handlers (v4.2.1)
413
+ Use v4.2.1 it contains stable fixes for IOS with unwanted debug logs removed
414
+
415
+ ### iOS Performance - Unnecessary Path Handlers (v4.2.0)
416
+ 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).
412
417
 
413
418
  ### iOS Pinch-to-Zoom (v4.1.1)
414
419
  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.
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,7 +389,16 @@ using namespace facebook::react;
369
389
  _showsHorizontalScrollIndicator = YES;
370
390
  _showsVerticalScrollIndicator = YES;
371
391
  _scrollEnabled = YES;
372
-
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
+
373
402
  // Enhanced properties
374
403
  _enableCaching = YES;
375
404
  _enablePreloading = YES;
@@ -440,13 +469,63 @@ using namespace facebook::react;
440
469
  _changedProps = changedProps;
441
470
 
442
471
  } else {
472
+ // Log all didSetProps calls to understand what's triggering reconfigurations
473
+ RCTLogInfo(@"📥 [iOS Scroll] didSetProps called - changedProps=%@, initialized=%d, currentUsePageVC=%d",
474
+ changedProps, _usePageViewControllerStateInitialized, _currentUsePageViewController);
443
475
 
444
- if ([changedProps containsObject:@"path"]) {
476
+ // Create filtered changedProps array - remove "path" if it hasn't actually changed
477
+ // This prevents unnecessary reconfigurations when path is in changedProps but value unchanged
478
+ NSArray<NSString *> *effectiveChangedProps = changedProps;
479
+ BOOL pathActuallyChanged = NO;
445
480
 
481
+ if ([changedProps containsObject:@"path"]) {
482
+ // CRITICAL FIX: Only reset state if the path actually changed
483
+ // React Native sometimes includes path in changedProps even when only page changes
484
+
485
+ if (_pdfDocument != Nil && _pdfDocument.documentURL != nil) {
486
+ // Compare new path with existing document's path
487
+ NSString *currentPath = _pdfDocument.documentURL.path;
488
+ NSString *newPath = _path;
489
+ // Normalize paths for comparison (remove trailing slashes, resolve symlinks, etc.)
490
+ if (![currentPath isEqualToString:newPath]) {
491
+ pathActuallyChanged = YES;
492
+ }
493
+ } else {
494
+ // No existing document, so this is a new path (or initial load)
495
+ pathActuallyChanged = YES;
496
+ }
497
+
498
+ RCTLogInfo(@"🔄 [iOS Scroll] Path prop in changedProps - hadDocument=%d, pathActuallyChanged=%d",
499
+ (_pdfDocument != Nil), pathActuallyChanged);
500
+
501
+ // Filter out "path" from effectiveChangedProps if it hasn't actually changed
502
+ if (!pathActuallyChanged) {
503
+ RCTLogInfo(@"⏭️ [iOS Scroll] Path value unchanged, filtering out 'path' from effectiveChangedProps");
504
+ NSMutableArray<NSString *> *filtered = [changedProps mutableCopy];
505
+ [filtered removeObject:@"path"];
506
+ effectiveChangedProps = filtered;
507
+ } else {
508
+ // Path actually changed, use changedProps as-is
509
+ effectiveChangedProps = changedProps;
510
+ }
511
+
512
+ if (!pathActuallyChanged) {
513
+ RCTLogInfo(@"⏭️ [iOS Scroll] Path value unchanged, skipping document reload");
514
+ // Skip the rest of path handling
515
+ } else {
516
+ // Reset document load state when path actually changes
517
+ _documentLoaded = NO;
518
+ _previousPage = -1;
519
+ _isNavigating = NO;
446
520
 
521
+ // Release old doc if it exists
447
522
  if (_pdfDocument != Nil) {
448
- //Release old doc
449
523
  _pdfDocument = Nil;
524
+ _usePageViewControllerStateInitialized = NO;
525
+ _currentUsePageViewController = NO;
526
+ RCTLogInfo(@"🔄 [iOS Scroll] Reset usePageViewController state - path changed (hadDocument=YES)");
527
+ } else {
528
+ RCTLogInfo(@"⏭️ [iOS Scroll] No previous document to reset");
450
529
  }
451
530
 
452
531
  if ([_path hasPrefix:@"blob:"]) {
@@ -482,16 +561,31 @@ using namespace facebook::react;
482
561
  }
483
562
 
484
563
  _pdfView.document = _pdfDocument;
564
+ _documentLoaded = YES;
565
+
566
+ // Configure scroll view after document is set
567
+ // PDFKit creates the scroll view lazily, so we need to wait a bit
568
+ dispatch_async(dispatch_get_main_queue(), ^{
569
+ RCTLogInfo(@"🔍 [iOS Scroll] Document set, searching for scroll view");
570
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
571
+
572
+ // Retry after a short delay to catch cases where scroll view is created asynchronously
573
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
574
+ RCTLogInfo(@"🔍 [iOS Scroll] Retry search for scroll view after delay");
575
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
576
+ });
577
+ });
485
578
  } else {
486
579
 
487
580
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"error|Load pdf failed. path=%s",_path.UTF8String]]];
488
581
 
489
582
  _pdfDocument = Nil;
490
583
  return;
584
+ }
491
585
  }
492
586
  }
493
587
 
494
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"spacing"])) {
588
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"spacing"])) {
495
589
  if (_horizontal) {
496
590
  _pdfView.pageBreakMargins = UIEdgeInsetsMake(0,_spacing,0,0);
497
591
  if (_spacing==0) {
@@ -517,11 +611,11 @@ using namespace facebook::react;
517
611
  }
518
612
  }
519
613
 
520
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enableRTL"])) {
614
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"enableRTL"])) {
521
615
  _pdfView.displaysRTL = _enableRTL;
522
616
  }
523
617
 
524
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enableAnnotationRendering"])) {
618
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"enableAnnotationRendering"])) {
525
619
  if (!_enableAnnotationRendering) {
526
620
  for (unsigned long i=0; i<_pdfView.document.pageCount; i++) {
527
621
  PDFPage *pdfPage = [_pdfView.document pageAtIndex:i];
@@ -532,7 +626,7 @@ using namespace facebook::react;
532
626
  }
533
627
  }
534
628
 
535
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"fitPolicy"] || [changedProps containsObject:@"minScale"] || [changedProps containsObject:@"maxScale"])) {
629
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"fitPolicy"] || [changedProps containsObject:@"minScale"] || [changedProps containsObject:@"maxScale"])) {
536
630
 
537
631
  PDFPage *pdfPage = _pdfView.currentPage ? _pdfView.currentPage : [_pdfDocument pageAtIndex:_pdfDocument.pageCount-1];
538
632
  CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
@@ -570,49 +664,94 @@ using namespace facebook::react;
570
664
 
571
665
  }
572
666
 
573
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"scale"])) {
667
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"scale"])) {
574
668
  _pdfView.scaleFactor = _scale * _fixScaleFactor;
575
669
  if (_pdfView.scaleFactor>_pdfView.maxScaleFactor) _pdfView.scaleFactor = _pdfView.maxScaleFactor;
576
670
  if (_pdfView.scaleFactor<_pdfView.minScaleFactor) _pdfView.scaleFactor = _pdfView.minScaleFactor;
577
671
  }
578
672
 
579
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"horizontal"])) {
673
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"horizontal"])) {
580
674
  if (_horizontal) {
581
675
  _pdfView.displayDirection = kPDFDisplayDirectionHorizontal;
582
676
  _pdfView.pageBreakMargins = UIEdgeInsetsMake(0,_spacing,0,0);
677
+ RCTLogInfo(@"➡️ [iOS Scroll] Set display direction to HORIZONTAL (spacing=%d)", _spacing);
583
678
  } else {
584
679
  _pdfView.displayDirection = kPDFDisplayDirectionVertical;
585
680
  _pdfView.pageBreakMargins = UIEdgeInsetsMake(0,0,_spacing,0);
681
+ RCTLogInfo(@"⬇️ [iOS Scroll] Set display direction to VERTICAL (spacing=%d)", _spacing);
586
682
  }
587
683
  }
588
684
 
589
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enablePaging"])) {
590
- if (_enablePaging) {
685
+ // CRITICAL FIX: Only configure usePageViewController when path changes (document loading)
686
+ // This prevents unnecessary reconfigurations during scrolling and layout updates
687
+ // Once configured, usePageViewController doesn't need to be reconfigured unless the document changes
688
+ if (_pdfDocument && [effectiveChangedProps containsObject:@"path"]) {
689
+ // Fix: Disable usePageViewController when horizontal is true, as it conflicts with horizontal scrolling
690
+ // UIPageViewController doesn't work well with horizontal PDFView display direction
691
+ BOOL shouldUsePageViewController = _enablePaging && !_horizontal;
692
+
693
+ RCTLogInfo(@"🔄 [iOS Scroll] Configuring usePageViewController on document load - enablePaging=%d, horizontal=%d, usePageVC=%d",
694
+ _enablePaging, _horizontal, shouldUsePageViewController);
695
+
696
+ // Set state immediately
697
+ _currentUsePageViewController = shouldUsePageViewController;
698
+ _usePageViewControllerStateInitialized = YES;
699
+
700
+ // Configure usePageViewController - this only happens on document load
701
+ if (shouldUsePageViewController) {
702
+ // Only use page view controller for vertical orientation
591
703
  [_pdfView usePageViewController:YES withViewOptions:@{UIPageViewControllerOptionSpineLocationKey:@(UIPageViewControllerSpineLocationMin),UIPageViewControllerOptionInterPageSpacingKey:@(_spacing)}];
704
+ RCTLogInfo(@"✅ [iOS Scroll] Enabled UIPageViewController (vertical paging mode)");
592
705
  } else {
706
+ // For horizontal or when paging is disabled, use regular scrolling
593
707
  [_pdfView usePageViewController:NO withViewOptions:Nil];
708
+ RCTLogInfo(@"✅ [iOS Scroll] Disabled UIPageViewController (using regular scrolling)");
594
709
  }
710
+
711
+ // Reconfigure scroll view after usePageViewController changes
712
+ // PDFView's internal scroll view hierarchy changes when usePageViewController is toggled
713
+ dispatch_async(dispatch_get_main_queue(), ^{
714
+ // Reset scroll view references to allow reconfiguration
715
+ RCTLogInfo(@"🔄 [iOS Scroll] Resetting scroll view references for reconfiguration");
716
+ self->_internalScrollView = nil;
717
+ self->_originalScrollDelegate = nil;
718
+ self->_scrollDelegateProxy = nil;
719
+
720
+ // Reconfigure scroll view after view hierarchy updates
721
+ dispatch_async(dispatch_get_main_queue(), ^{
722
+ RCTLogInfo(@"🔧 [iOS Scroll] Reconfiguring scroll view after usePageViewController change (scrollEnabled=%d)", self->_scrollEnabled);
723
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
724
+ });
725
+ });
595
726
  }
596
727
 
597
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"singlePage"])) {
728
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"singlePage"])) {
598
729
  if (_singlePage) {
599
730
  _pdfView.displayMode = kPDFDisplaySinglePage;
600
731
  _pdfView.userInteractionEnabled = NO;
732
+ RCTLogInfo(@"📄 [iOS Scroll] Set to SINGLE PAGE mode (userInteractionEnabled=NO)");
601
733
  } else {
602
734
  _pdfView.displayMode = kPDFDisplaySinglePageContinuous;
603
735
  _pdfView.userInteractionEnabled = YES;
736
+ RCTLogInfo(@"📄 [iOS Scroll] Set to CONTINUOUS PAGE mode (userInteractionEnabled=YES)");
604
737
  }
605
738
  }
606
739
 
607
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"showsHorizontalScrollIndicator"] || [changedProps containsObject:@"showsVerticalScrollIndicator"])) {
740
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"showsHorizontalScrollIndicator"] || [changedProps containsObject:@"showsVerticalScrollIndicator"])) {
608
741
  [self setScrollIndicators:self horizontal:_showsHorizontalScrollIndicator vertical:_showsVerticalScrollIndicator depth:0];
609
742
  }
610
743
 
611
744
  // Configure scroll view (scrollEnabled)
612
- if (_pdfDocument && ([changedProps containsObject:@"path"] ||
745
+ if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] ||
613
746
  [changedProps containsObject:@"scrollEnabled"])) {
747
+ RCTLogInfo(@"🔧 [iOS Scroll] Configuring scroll enabled=%d (path changed=%d, scrollEnabled changed=%d)",
748
+ _scrollEnabled,
749
+ [effectiveChangedProps containsObject:@"path"],
750
+ [changedProps containsObject:@"scrollEnabled"]);
751
+
614
752
  // If path changed, restore original delegate before reconfiguring
615
- if ([changedProps containsObject:@"path"] && _internalScrollView) {
753
+ if ([effectiveChangedProps containsObject:@"path"] && _internalScrollView) {
754
+ RCTLogInfo(@"🔄 [iOS Scroll] Restoring original scroll delegate (path changed)");
616
755
  if (_originalScrollDelegate) {
617
756
  _internalScrollView.delegate = _originalScrollDelegate;
618
757
  } else {
@@ -626,34 +765,88 @@ using namespace facebook::react;
626
765
  // Use dispatch_async to ensure view hierarchy is fully set up after document load
627
766
  dispatch_async(dispatch_get_main_queue(), ^{
628
767
  // Search within _pdfView's hierarchy for scroll views
768
+ RCTLogInfo(@"🔍 [iOS Scroll] Starting scroll view search in PDFView hierarchy");
629
769
  [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
630
770
  });
631
771
  }
632
772
 
633
- if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enablePaging"] || [changedProps containsObject:@"horizontal"] || [changedProps containsObject:@"page"])) {
634
-
773
+ // Separate page navigation logic - only navigate when page prop actually changes
774
+ // Skip navigation on initial load (when path changes) to avoid conflicts
775
+ BOOL shouldNavigateToPage = _documentLoaded &&
776
+ [changedProps containsObject:@"page"] &&
777
+ !_isNavigating &&
778
+ _page != _previousPage &&
779
+ _page > 0 &&
780
+ _page <= (int)_pdfDocument.pageCount;
781
+
782
+ if (shouldNavigateToPage) {
783
+ _isNavigating = YES;
784
+ PDFPage *pdfPage = [_pdfDocument pageAtIndex:_page-1];
785
+
786
+ if (pdfPage) {
787
+ // Use smooth navigation instead of instant jump to prevent full rerender
788
+ dispatch_async(dispatch_get_main_queue(), ^{
789
+ if (!self->_enablePaging) {
790
+ // For non-paging mode, use animated navigation
791
+ CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
792
+
793
+ // Handle page rotation
794
+ if (pdfPage.rotation == 90 || pdfPage.rotation == 270) {
795
+ pdfPageRect = CGRectMake(0, 0, pdfPageRect.size.height, pdfPageRect.size.width);
796
+ }
797
+
798
+ CGPoint pointLeftTop = CGPointMake(0, pdfPageRect.size.height);
799
+ PDFDestination *pdfDest = [[PDFDestination alloc] initWithPage:pdfPage atPoint:pointLeftTop];
800
+
801
+ // Use goToDestination for smooth navigation
802
+ [self->_pdfView goToDestination:pdfDest];
803
+ self->_pdfView.scaleFactor = self->_fixScaleFactor * self->_scale;
804
+ } else {
805
+ // For paging mode, use goToRect for better page alignment
806
+ if (self->_page == 1) {
807
+ // Special case for first page
808
+ [self->_pdfView goToRect:CGRectMake(0, NSUIntegerMax, 1, 1) onPage:pdfPage];
809
+ } else {
810
+ CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
811
+ if (pdfPage.rotation == 90 || pdfPage.rotation == 270) {
812
+ pdfPageRect = CGRectMake(0, 0, pdfPageRect.size.height, pdfPageRect.size.width);
813
+ }
814
+ CGPoint pointLeftTop = CGPointMake(0, pdfPageRect.size.height);
815
+ PDFDestination *pdfDest = [[PDFDestination alloc] initWithPage:pdfPage atPoint:pointLeftTop];
816
+ [self->_pdfView goToDestination:pdfDest];
817
+ self->_pdfView.scaleFactor = self->_fixScaleFactor * self->_scale;
818
+ }
819
+ }
820
+
821
+ self->_previousPage = self->_page;
822
+ self->_isNavigating = NO;
823
+ });
824
+ } else {
825
+ _isNavigating = NO;
826
+ }
827
+ }
828
+
829
+ // Handle initial page on document load (only when path changes)
830
+ // This handles the case where the document was just loaded and we need to navigate to the initial page
831
+ // Use pathActuallyChanged instead of checking changedProps to ensure we only handle initial page when path actually changed
832
+ if (_pdfDocument && pathActuallyChanged && _documentLoaded) {
635
833
  PDFPage *pdfPage = [_pdfDocument pageAtIndex:_page-1];
636
834
  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.
835
+ // Special case workaround for first page alignment
642
836
  dispatch_async(dispatch_get_main_queue(), ^{
643
837
  [self->_pdfView goToRect:CGRectMake(0, NSUIntegerMax, 1, 1) onPage:pdfPage];
838
+ self->_previousPage = self->_page;
644
839
  });
645
840
  } else if (pdfPage) {
646
841
  CGRect pdfPageRect = [pdfPage boundsForBox:kPDFDisplayBoxCropBox];
647
-
648
- // some pdf with rotation, then adjust it
649
842
  if (pdfPage.rotation == 90 || pdfPage.rotation == 270) {
650
843
  pdfPageRect = CGRectMake(0, 0, pdfPageRect.size.height, pdfPageRect.size.width);
651
844
  }
652
-
653
845
  CGPoint pointLeftTop = CGPointMake(0, pdfPageRect.size.height);
654
846
  PDFDestination *pdfDest = [[PDFDestination alloc] initWithPage:pdfPage atPoint:pointLeftTop];
655
847
  [_pdfView goToDestination:pdfDest];
656
848
  _pdfView.scaleFactor = _fixScaleFactor*_scale;
849
+ _previousPage = _page;
657
850
  }
658
851
  }
659
852
 
@@ -676,6 +869,15 @@ using namespace facebook::react;
676
869
  _initialed = YES;
677
870
 
678
871
  [self didSetProps:mProps];
872
+
873
+ // Configure scroll view after layout to ensure it's found
874
+ // This is important because PDFKit creates the scroll view lazily
875
+ if (_documentLoaded && _pdfDocument) {
876
+ dispatch_async(dispatch_get_main_queue(), ^{
877
+ RCTLogInfo(@"🔍 [iOS Scroll] reactSetFrame called, configuring scroll view after layout");
878
+ [self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
879
+ });
880
+ }
679
881
  }
680
882
 
681
883
 
@@ -849,7 +1051,21 @@ using namespace facebook::react;
849
1051
  unsigned long numberOfPages = _pdfDocument.pageCount;
850
1052
 
851
1053
  // Update current page for preloading
852
- _page = (int)page + 1;
1054
+ int newPage = (int)page + 1;
1055
+
1056
+ // CRITICAL FIX: Update _previousPage to the new page value when page changes from PDFView notifications
1057
+ // This prevents updateProps from triggering programmatic navigation when React Native
1058
+ // receives the pageChanged notification and updates the page prop back to us.
1059
+ // By setting _previousPage = newPage, when updateProps checks _page != _previousPage,
1060
+ // they will be equal (since the page prop will match the new page), and navigation will be skipped.
1061
+ if (newPage != _page) {
1062
+ _previousPage = newPage; // Set to newPage to prevent navigation loop
1063
+ _page = newPage;
1064
+ } else {
1065
+ // If page didn't actually change, just ensure _previousPage matches to prevent navigation
1066
+ _previousPage = _page;
1067
+ }
1068
+
853
1069
  _pageCount = (int)numberOfPages;
854
1070
  if (_enablePreloading) {
855
1071
  [self preloadAdjacentPages:_page];
@@ -1064,48 +1280,118 @@ using namespace facebook::react;
1064
1280
  }
1065
1281
 
1066
1282
  - (void)configureScrollView:(UIView *)view enabled:(BOOL)enabled depth:(int)depth {
1283
+ // Log entry to track all calls
1284
+ if (depth == 0) {
1285
+ RCTLogInfo(@"🚀 [iOS Scroll] configureScrollView called - enabled=%d, view=%@", enabled, NSStringFromClass([view class]));
1286
+ }
1287
+
1067
1288
  // max depth, prevent infinite loop
1068
1289
  if (depth > 10) {
1290
+ RCTLogWarn(@"⚠️ [iOS Scroll] Max depth reached in configureScrollView (depth=%d)", depth);
1069
1291
  return;
1070
1292
  }
1071
1293
 
1072
1294
  if ([view isKindOfClass:[UIScrollView class]]) {
1073
1295
  UIScrollView *scrollView = (UIScrollView *)view;
1296
+ RCTLogInfo(@"📱 [iOS Scroll] Found UIScrollView at depth=%d, frame=%@, contentSize=%@, enabled=%d",
1297
+ depth,
1298
+ NSStringFromCGRect(scrollView.frame),
1299
+ NSStringFromCGSize(scrollView.contentSize),
1300
+ enabled);
1301
+
1074
1302
  // Since we're starting the recursion from _pdfView, all scroll views found are within its hierarchy
1075
1303
  // Configure scroll properties
1304
+ BOOL previousScrollEnabled = scrollView.scrollEnabled;
1076
1305
  scrollView.scrollEnabled = enabled;
1077
1306
 
1078
- // Disable horizontal bouncing to prevent interference with navigation swipe-back
1307
+ if (previousScrollEnabled != enabled) {
1308
+ RCTLogInfo(@"🔄 [iOS Scroll] Changed scrollEnabled: %d -> %d", previousScrollEnabled, enabled);
1309
+ }
1310
+
1311
+ // Conditionally set horizontal bouncing based on scroll direction
1312
+ // Allow horizontal bounce when horizontal scrolling is enabled
1313
+ BOOL previousAlwaysBounceHorizontal = scrollView.alwaysBounceHorizontal;
1314
+ if (_horizontal) {
1315
+ scrollView.alwaysBounceHorizontal = YES;
1316
+ } else {
1317
+ // Disable horizontal bouncing for vertical scrolling to prevent interference with navigation swipe-back
1079
1318
  scrollView.alwaysBounceHorizontal = NO;
1319
+ }
1320
+
1321
+ if (previousAlwaysBounceHorizontal != scrollView.alwaysBounceHorizontal) {
1322
+ RCTLogInfo(@"🔄 [iOS Scroll] Changed alwaysBounceHorizontal: %d -> %d (horizontal=%d)",
1323
+ previousAlwaysBounceHorizontal,
1324
+ scrollView.alwaysBounceHorizontal,
1325
+ _horizontal);
1326
+ }
1327
+
1080
1328
  // Keep vertical bounce enabled for natural scrolling feel
1081
1329
  scrollView.bounces = YES;
1082
-
1330
+
1331
+ RCTLogInfo(@"📊 [iOS Scroll] ScrollView config - scrollEnabled=%d, alwaysBounceHorizontal=%d, bounces=%d, delegate=%@",
1332
+ scrollView.scrollEnabled,
1333
+ scrollView.alwaysBounceHorizontal,
1334
+ scrollView.bounces,
1335
+ scrollView.delegate != nil ? @"set" : @"nil");
1336
+
1083
1337
  // IMPORTANT: PDFKit relies on the scrollView delegate for pinch-zoom (viewForZoomingInScrollView).
1084
1338
  // Install a proxy delegate that forwards to the original delegate, while still letting us observe scroll events.
1085
1339
  if (!_internalScrollView) {
1340
+ RCTLogInfo(@"✅ [iOS Scroll] Setting internal scroll view reference");
1086
1341
  _internalScrollView = scrollView;
1087
1342
  if (scrollView.delegate && scrollView.delegate != self) {
1088
1343
  _originalScrollDelegate = scrollView.delegate;
1344
+ RCTLogInfo(@"📝 [iOS Scroll] Stored original scroll delegate");
1089
1345
  }
1090
1346
  if (_originalScrollDelegate) {
1091
1347
  _scrollDelegateProxy = [[RNPDFScrollViewDelegateProxy alloc] initWithPrimary:_originalScrollDelegate secondary:(id<UIScrollViewDelegate>)self];
1092
1348
  scrollView.delegate = (id<UIScrollViewDelegate>)_scrollDelegateProxy;
1349
+ RCTLogInfo(@"🔗 [iOS Scroll] Installed scroll delegate proxy");
1093
1350
  } else {
1094
1351
  scrollView.delegate = self;
1352
+ RCTLogInfo(@"🔗 [iOS Scroll] Set self as scroll delegate");
1095
1353
  }
1354
+ } else {
1355
+ RCTLogInfo(@"⚠️ [iOS Scroll] Internal scroll view already set, skipping delegate setup");
1096
1356
  }
1097
1357
  }
1098
1358
 
1099
1359
  for (UIView *subview in view.subviews) {
1100
1360
  [self configureScrollView:subview enabled:enabled depth:depth + 1];
1101
1361
  }
1362
+
1363
+ // Log at root level if no scroll view was found
1364
+ if (depth == 0 && !_internalScrollView) {
1365
+ RCTLogWarn(@"⚠️ [iOS Scroll] No UIScrollView found in view hierarchy (view=%@, subviewCount=%lu)",
1366
+ NSStringFromClass([view class]),
1367
+ (unsigned long)[view.subviews count]);
1368
+ }
1102
1369
  }
1103
1370
 
1104
1371
  #pragma mark - UIScrollViewDelegate
1105
1372
 
1106
1373
  - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
1374
+ static int scrollEventCount = 0;
1375
+ scrollEventCount++;
1376
+
1377
+ // Log scroll events periodically (every 10th event to avoid spam)
1378
+ if (scrollEventCount % 10 == 0) {
1379
+ RCTLogInfo(@"📜 [iOS Scroll] scrollViewDidScroll #%d - offset=(%.2f, %.2f), contentSize=(%.2f, %.2f), bounds=(%.2f, %.2f), scrollEnabled=%d",
1380
+ scrollEventCount,
1381
+ scrollView.contentOffset.x,
1382
+ scrollView.contentOffset.y,
1383
+ scrollView.contentSize.width,
1384
+ scrollView.contentSize.height,
1385
+ scrollView.bounds.size.width,
1386
+ scrollView.bounds.size.height,
1387
+ scrollView.scrollEnabled);
1388
+ }
1107
1389
 
1108
1390
  if (!_pdfDocument || _singlePage) {
1391
+ if (scrollEventCount % 10 == 0) {
1392
+ RCTLogInfo(@"⏭️ [iOS Scroll] Skipping scroll handling - pdfDocument=%d, singlePage=%d",
1393
+ _pdfDocument != nil, _singlePage);
1394
+ }
1109
1395
  return;
1110
1396
  }
1111
1397
 
@@ -1126,7 +1412,16 @@ using namespace facebook::react;
1126
1412
 
1127
1413
  // Only update if page actually changed and is valid
1128
1414
  if (newPage != _page && newPage > 0 && newPage <= (int)_pdfDocument.pageCount) {
1415
+ RCTLogInfo(@"📄 [iOS Scroll] Page changed: %d -> %d (from scroll position)", _page, newPage);
1416
+
1417
+ // CRITICAL FIX: Update _previousPage to the new page value when page changes from user scrolling
1418
+ // This prevents updateProps from triggering programmatic navigation when React Native
1419
+ // receives the pageChanged notification and updates the page prop back to us.
1420
+ // By setting _previousPage = newPage, when updateProps checks _page != _previousPage,
1421
+ // they will be equal (since React Native will set _page = newPage), and navigation will be skipped.
1422
+ int oldPage = _page;
1129
1423
  _page = newPage;
1424
+ _previousPage = newPage; // Set to newPage to prevent navigation loop
1130
1425
  _pageCount = (int)_pdfDocument.pageCount;
1131
1426
 
1132
1427
  // Trigger preloading if enabled
@@ -1137,6 +1432,11 @@ using namespace facebook::react;
1137
1432
  // Notify about page change
1138
1433
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"pageChanged|%d|%lu", newPage, _pdfDocument.pageCount]]];
1139
1434
  }
1435
+ } else {
1436
+ if (scrollEventCount % 50 == 0) {
1437
+ RCTLogWarn(@"⚠️ [iOS Scroll] No visible page found for scroll position (%.2f, %.2f)",
1438
+ pdfPoint.x, pdfPoint.y);
1439
+ }
1140
1440
  }
1141
1441
  }
1142
1442
 
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.1",
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",