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 +36 -8
- package/README.md +5 -0
- package/index.js +1 -0
- package/ios/RNPDFPdf/RNPDFPdfView.mm +328 -28
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 && ([
|
|
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 && ([
|
|
614
|
+
if (_pdfDocument && ([effectiveChangedProps containsObject:@"path"] || [changedProps containsObject:@"enableRTL"])) {
|
|
521
615
|
_pdfView.displaysRTL = _enableRTL;
|
|
522
616
|
}
|
|
523
617
|
|
|
524
|
-
if (_pdfDocument && ([
|
|
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 && ([
|
|
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 && ([
|
|
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 && ([
|
|
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
|
-
|
|
590
|
-
|
|
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 && ([
|
|
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 && ([
|
|
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 && ([
|
|
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 ([
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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",
|