react-native-pdf-jsi 4.1.0 → 4.1.2

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/PinchZoomView.js CHANGED
@@ -6,120 +6,95 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
 
9
- 'use strict';
10
- import React, {Component} from 'react';
11
- import PropTypes from 'prop-types';
12
- import {
13
- View,
14
- StyleSheet,
15
- PanResponder
16
- } from 'react-native';
17
- import {ViewPropTypes} from 'deprecated-react-native-prop-types';
9
+ "use strict";
10
+ import React, { Component } from "react";
11
+ import PropTypes from "prop-types";
12
+ import { View, StyleSheet, PanResponder } from "react-native";
13
+ import { ViewPropTypes } from "deprecated-react-native-prop-types";
18
14
  export default class PinchZoomView extends Component {
19
-
20
- static propTypes = {
21
- ...ViewPropTypes,
22
- scalable: PropTypes.bool,
23
- onScaleChanged: PropTypes.func,
24
- };
25
-
26
- static defaultProps = {
27
- scalable: true,
28
- onScaleChanged: (scale) => {
29
- },
30
- };
31
-
32
- constructor(props) {
33
-
34
- super(props);
35
- this.state = {};
36
- this.distant = 0;
37
- this.gestureHandlers = PanResponder.create({
38
- onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
39
- onMoveShouldSetResponderCapture: (evt, gestureState) => (true),
40
- onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
41
- onPanResponderGrant: this._handlePanResponderGrant,
42
- onPanResponderMove: this._handlePanResponderMove,
43
- onPanResponderRelease: this._handlePanResponderEnd,
44
- onPanResponderTerminationRequest: evt => false,
45
- onPanResponderTerminate: this._handlePanResponderTerminate,
46
- onShouldBlockNativeResponder: evt => true
47
- });
48
-
15
+ static propTypes = {
16
+ ...ViewPropTypes,
17
+ scalable: PropTypes.bool,
18
+ onScaleChanged: PropTypes.func,
19
+ };
20
+
21
+ static defaultProps = {
22
+ scalable: true,
23
+ onScaleChanged: (scale) => {},
24
+ };
25
+
26
+ constructor(props) {
27
+ super(props);
28
+ this.state = {};
29
+ this.distant = 0;
30
+ this.gestureHandlers = PanResponder.create({
31
+ onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
32
+ onMoveShouldSetResponderCapture: (evt, gestureState) => {
33
+ return this.props.scalable && (evt.nativeEvent.touches.length >= 2 || gestureState.numberActiveTouches >= 2);
34
+ },
35
+ onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
36
+ onPanResponderGrant: this._handlePanResponderGrant,
37
+ onPanResponderMove: this._handlePanResponderMove,
38
+ onPanResponderRelease: this._handlePanResponderEnd,
39
+ onPanResponderTerminationRequest: (evt) => false,
40
+ onPanResponderTerminate: this._handlePanResponderTerminate,
41
+ onShouldBlockNativeResponder: (evt) => true,
42
+ });
43
+ }
44
+
45
+ _handleStartShouldSetPanResponder = (e, gestureState) => {
46
+ // don't respond to single touch to avoid shielding click on child components
47
+ return false;
48
+ };
49
+
50
+ _handleMoveShouldSetPanResponder = (e, gestureState) => {
51
+ return this.props.scalable && (e.nativeEvent.touches.length >= 2 || gestureState.numberActiveTouches >= 2);
52
+ };
53
+
54
+ _handlePanResponderGrant = (e, gestureState) => {
55
+ if (e.nativeEvent.touches.length >= 2 || gestureState.numberActiveTouches >= 2) {
56
+ let dx = Math.abs(e.nativeEvent.touches[0].pageX - e.nativeEvent.touches[1].pageX);
57
+ let dy = Math.abs(e.nativeEvent.touches[0].pageY - e.nativeEvent.touches[1].pageY);
58
+ this.distant = Math.sqrt(dx * dx + dy * dy);
49
59
  }
50
-
51
- _handleStartShouldSetPanResponder = (e, gestureState) => {
52
-
53
- // don't respond to single touch to avoid shielding click on child components
54
- return false;
55
-
56
- };
57
-
58
- _handleMoveShouldSetPanResponder = (e, gestureState) => {
59
-
60
- return this.props.scalable && (e.nativeEvent.changedTouches.length >= 2 || gestureState.numberActiveTouches >= 2);
61
-
62
- };
63
-
64
- _handlePanResponderGrant = (e, gestureState) => {
65
-
66
- if (e.nativeEvent.changedTouches.length >= 2 || gestureState.numberActiveTouches >= 2) {
67
- let dx = Math.abs(e.nativeEvent.touches[0].pageX - e.nativeEvent.touches[1].pageX);
68
- let dy = Math.abs(e.nativeEvent.touches[0].pageY - e.nativeEvent.touches[1].pageY);
69
- this.distant = Math.sqrt(dx * dx + dy * dy);
70
- }
71
-
72
- };
73
-
74
- _handlePanResponderEnd = (e, gestureState) => {
75
-
76
- this.distant = 0;
77
-
78
- };
79
-
80
- _handlePanResponderTerminate = (e, gestureState) => {
81
-
82
- this.distant = 0;
83
-
84
- };
85
-
86
- _handlePanResponderMove = (e, gestureState) => {
87
-
88
- if ((e.nativeEvent.changedTouches.length >= 2 || gestureState.numberActiveTouches >= 2) && this.distant > 100) {
89
-
90
- let dx = Math.abs(e.nativeEvent.touches[0].pageX - e.nativeEvent.touches[1].pageX);
91
- let dy = Math.abs(e.nativeEvent.touches[0].pageY - e.nativeEvent.touches[1].pageY);
92
- let distant = Math.sqrt(dx * dx + dy * dy);
93
- let scale = (distant / this.distant);
94
- let pageX = (e.nativeEvent.touches[0].pageX + e.nativeEvent.touches[1].pageX) / 2;
95
- let pageY = (e.nativeEvent.touches[0].pageY + e.nativeEvent.touches[1].pageY) / 2;
96
- let pinchInfo = {scale: scale, pageX: pageX, pageY: pageY};
97
-
98
- this.props.onScaleChanged(pinchInfo);
99
- this.distant = distant;
100
-
101
- }
102
-
103
- };
104
-
105
- render() {
106
-
107
- return (
108
- <View
109
- {...this.props}
110
- {...this.gestureHandlers?.panHandlers}
111
- style={[styles.container, this.props.style]}>
112
- {this.props.children}
113
- </View>
114
- );
115
-
60
+ };
61
+
62
+ _handlePanResponderEnd = (e, gestureState) => {
63
+ this.distant = 0;
64
+ };
65
+
66
+ _handlePanResponderTerminate = (e, gestureState) => {
67
+ this.distant = 0;
68
+ };
69
+
70
+ _handlePanResponderMove = (e, gestureState) => {
71
+ if ((e.nativeEvent.touches.length >= 2 || gestureState.numberActiveTouches >= 2) && this.distant > 100) {
72
+ let dx = Math.abs(e.nativeEvent.touches[0].pageX - e.nativeEvent.touches[1].pageX);
73
+ let dy = Math.abs(e.nativeEvent.touches[0].pageY - e.nativeEvent.touches[1].pageY);
74
+ let distant = Math.sqrt(dx * dx + dy * dy);
75
+ let scale = distant / this.distant;
76
+ let pageX = (e.nativeEvent.touches[0].pageX + e.nativeEvent.touches[1].pageX) / 2;
77
+ let pageY = (e.nativeEvent.touches[0].pageY + e.nativeEvent.touches[1].pageY) / 2;
78
+ let pinchInfo = { scale: scale, pageX: pageX, pageY: pageY };
79
+
80
+ this.props.onScaleChanged(pinchInfo);
81
+ this.distant = distant;
116
82
  }
83
+ };
84
+
85
+ render() {
86
+ return (
87
+ <View {...this.props} {...this.gestureHandlers?.panHandlers} style={[styles.container, this.props.style]}>
88
+ {this.props.children}
89
+ </View>
90
+ );
91
+ }
117
92
  }
118
93
 
119
94
  const styles = StyleSheet.create({
120
- container: {
121
- flex: 1,
122
- justifyContent: 'center',
123
- alignItems: 'center'
124
- }
95
+ container: {
96
+ flex: 1,
97
+ justifyContent: "center",
98
+ alignItems: "center",
99
+ },
125
100
  });
package/README.md CHANGED
@@ -21,7 +21,7 @@ High-performance React Native PDF viewer with JSI (JavaScript Interface) acceler
21
21
  ### Core Functionality
22
22
  - Read PDFs from URL, blob, local file, or asset with caching support
23
23
  - Horizontal and vertical display modes
24
- - Drag and zoom with double-tap support
24
+ - **Pinch-to-zoom** and drag with double-tap support (iOS & Android)
25
25
  - Password-protected PDF support
26
26
  - Programmatic page navigation
27
27
  - Cross-platform support (iOS, Android, Windows)
@@ -408,6 +408,11 @@ MIT License - see [LICENSE](LICENSE) file for details.
408
408
  - **Issues**: [GitHub Issues](https://github.com/126punith/react-native-pdf-jsi/issues)
409
409
  - **Author**: Punith M ([@126punith](https://github.com/126punith))
410
410
 
411
+ ## Recent Fixes
412
+
413
+ ### iOS Pinch-to-Zoom (v4.1.1)
414
+ 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
+
411
416
  ## Changelog
412
417
 
413
418
  See [CHANGELOG.md](CHANGELOG.md) for a complete list of changes and version history.
@@ -54,6 +54,58 @@
54
54
  const float MAX_SCALE = 3.0f;
55
55
  const float MIN_SCALE = 1.0f;
56
56
 
57
+
58
+ @interface RNPDFScrollViewDelegateProxy : NSObject <UIScrollViewDelegate>
59
+ - (instancetype)initWithPrimary:(id<UIScrollViewDelegate>)primary secondary:(id<UIScrollViewDelegate>)secondary;
60
+ @end
61
+
62
+ @implementation RNPDFScrollViewDelegateProxy {
63
+ __weak id<UIScrollViewDelegate> _primary;
64
+ __weak id<UIScrollViewDelegate> _secondary;
65
+ }
66
+
67
+ - (instancetype)initWithPrimary:(id<UIScrollViewDelegate>)primary secondary:(id<UIScrollViewDelegate>)secondary {
68
+ if (self = [super init]) {
69
+ _primary = primary;
70
+ _secondary = secondary;
71
+ }
72
+ return self;
73
+ }
74
+
75
+ - (BOOL)respondsToSelector:(SEL)aSelector {
76
+ return [super respondsToSelector:aSelector]
77
+ || (_primary && [_primary respondsToSelector:aSelector])
78
+ || (_secondary && [_secondary respondsToSelector:aSelector]);
79
+ }
80
+
81
+ - (id)forwardingTargetForSelector:(SEL)aSelector {
82
+ if (_primary && [_primary respondsToSelector:aSelector]) {
83
+ return _primary;
84
+ }
85
+ if (_secondary && [_secondary respondsToSelector:aSelector]) {
86
+ return _secondary;
87
+ }
88
+ return [super forwardingTargetForSelector:aSelector];
89
+ }
90
+
91
+ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
92
+ if (_primary && [_primary respondsToSelector:@selector(scrollViewDidScroll:)]) {
93
+ [_primary scrollViewDidScroll:scrollView];
94
+ }
95
+ if (_secondary && [_secondary respondsToSelector:@selector(scrollViewDidScroll:)]) {
96
+ [_secondary scrollViewDidScroll:scrollView];
97
+ }
98
+ }
99
+
100
+ - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
101
+ if (_primary && [_primary respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
102
+ return [_primary viewForZoomingInScrollView:scrollView];
103
+ }
104
+ return nil;
105
+ }
106
+
107
+ @end
108
+
57
109
  @interface RNPDFPdfView() <PDFDocumentDelegate, PDFViewDelegate
58
110
  #ifdef RCT_NEW_ARCH_ENABLED
59
111
  , RCTRNPDFPdfViewViewProtocol
@@ -68,13 +120,13 @@ const float MIN_SCALE = 1.0f;
68
120
  PDFView *_pdfView;
69
121
  UIScrollView *_internalScrollView;
70
122
  id<UIScrollViewDelegate> _originalScrollDelegate;
123
+ RNPDFScrollViewDelegateProxy *_scrollDelegateProxy;
71
124
  PDFOutline *root;
72
125
  float _fixScaleFactor;
73
126
  bool _initialed;
74
127
  NSArray<NSString *> *_changedProps;
75
128
  UITapGestureRecognizer *_doubleTapRecognizer;
76
129
  UITapGestureRecognizer *_singleTapRecognizer;
77
- UIPinchGestureRecognizer *_pinchRecognizer;
78
130
  UILongPressGestureRecognizer *_longPressRecognizer;
79
131
  UITapGestureRecognizer *_doubleTapEmptyRecognizer;
80
132
 
@@ -255,7 +307,6 @@ using namespace facebook::react;
255
307
  // remove old recognizers before adding new ones
256
308
  [self removeGestureRecognizer:_doubleTapRecognizer];
257
309
  [self removeGestureRecognizer:_singleTapRecognizer];
258
- [self removeGestureRecognizer:_pinchRecognizer];
259
310
  [self removeGestureRecognizer:_longPressRecognizer];
260
311
  [self removeGestureRecognizer:_doubleTapEmptyRecognizer];
261
312
 
@@ -561,10 +612,15 @@ using namespace facebook::react;
561
612
  if (_pdfDocument && ([changedProps containsObject:@"path"] ||
562
613
  [changedProps containsObject:@"scrollEnabled"])) {
563
614
  // If path changed, restore original delegate before reconfiguring
564
- if ([changedProps containsObject:@"path"] && _internalScrollView && _originalScrollDelegate) {
565
- _internalScrollView.delegate = _originalScrollDelegate;
615
+ if ([changedProps containsObject:@"path"] && _internalScrollView) {
616
+ if (_originalScrollDelegate) {
617
+ _internalScrollView.delegate = _originalScrollDelegate;
618
+ } else {
619
+ _internalScrollView.delegate = nil;
620
+ }
566
621
  _internalScrollView = nil;
567
622
  _originalScrollDelegate = nil;
623
+ _scrollDelegateProxy = nil;
568
624
  }
569
625
 
570
626
  // Use dispatch_async to ensure view hierarchy is fully set up after document load
@@ -655,7 +711,6 @@ using namespace facebook::react;
655
711
 
656
712
  _doubleTapRecognizer = nil;
657
713
  _singleTapRecognizer = nil;
658
- _pinchRecognizer = nil;
659
714
  _longPressRecognizer = nil;
660
715
  _doubleTapEmptyRecognizer = nil;
661
716
  }
@@ -808,10 +863,11 @@ using namespace facebook::react;
808
863
 
809
864
  - (void)onScaleChanged:(NSNotification *)noti
810
865
  {
811
-
812
866
  if (_initialed && _fixScaleFactor>0) {
813
- if (_scale != _pdfView.scaleFactor/_fixScaleFactor) {
814
- _scale = _pdfView.scaleFactor/_fixScaleFactor;
867
+ float newScale = _pdfView.scaleFactor/_fixScaleFactor;
868
+ // Only notify if scale changed significantly (threshold of 0.01 to prevent excessive callbacks)
869
+ if (fabs(_scale - newScale) > 0.01f) {
870
+ _scale = newScale;
815
871
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"scaleChanged|%f", _scale]]];
816
872
  }
817
873
  }
@@ -923,16 +979,6 @@ using namespace facebook::react;
923
979
 
924
980
  }
925
981
 
926
- /**
927
- * Pinch
928
- *
929
- *
930
- * @param sender The pinch gesture recognizer
931
- */
932
- -(void)handlePinch:(UIPinchGestureRecognizer *)sender{
933
- [self onScaleChanged:Nil];
934
- }
935
-
936
982
  /**
937
983
  * Do nothing on long Press
938
984
  *
@@ -971,13 +1017,6 @@ using namespace facebook::react;
971
1017
 
972
1018
  [singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
973
1019
 
974
- UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
975
- action:@selector(handlePinch:)];
976
- [self addGestureRecognizer:pinchRecognizer];
977
- _pinchRecognizer = pinchRecognizer;
978
-
979
- pinchRecognizer.delegate = self;
980
-
981
1020
  UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self
982
1021
  action:@selector(handleLongPress:)];
983
1022
  // Making sure the allowable movement isn not too narrow
@@ -1040,16 +1079,20 @@ using namespace facebook::react;
1040
1079
  scrollView.alwaysBounceHorizontal = NO;
1041
1080
  // Keep vertical bounce enabled for natural scrolling feel
1042
1081
  scrollView.bounces = YES;
1043
-
1044
- // Set delegate for scroll tracking (only once to avoid conflicts)
1045
- // Store original delegate before replacing it to preserve PDFView's internal scrolling
1082
+
1083
+ // IMPORTANT: PDFKit relies on the scrollView delegate for pinch-zoom (viewForZoomingInScrollView).
1084
+ // Install a proxy delegate that forwards to the original delegate, while still letting us observe scroll events.
1046
1085
  if (!_internalScrollView) {
1047
1086
  _internalScrollView = scrollView;
1048
- // Store original delegate if it exists and is not us
1049
1087
  if (scrollView.delegate && scrollView.delegate != self) {
1050
1088
  _originalScrollDelegate = scrollView.delegate;
1051
1089
  }
1052
- scrollView.delegate = self;
1090
+ if (_originalScrollDelegate) {
1091
+ _scrollDelegateProxy = [[RNPDFScrollViewDelegateProxy alloc] initWithPrimary:_originalScrollDelegate secondary:(id<UIScrollViewDelegate>)self];
1092
+ scrollView.delegate = (id<UIScrollViewDelegate>)_scrollDelegateProxy;
1093
+ } else {
1094
+ scrollView.delegate = self;
1095
+ }
1053
1096
  }
1054
1097
  }
1055
1098
 
@@ -1061,11 +1104,7 @@ using namespace facebook::react;
1061
1104
  #pragma mark - UIScrollViewDelegate
1062
1105
 
1063
1106
  - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
1064
- // Forward to original delegate first if it exists (important for PDFView's scrolling)
1065
- if (_originalScrollDelegate && [_originalScrollDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
1066
- [_originalScrollDelegate scrollViewDidScroll:scrollView];
1067
- }
1068
-
1107
+
1069
1108
  if (!_pdfDocument || _singlePage) {
1070
1109
  return;
1071
1110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-pdf-jsi",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
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",
@@ -64,8 +64,7 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "crypto-js": "4.2.0",
67
- "deprecated-react-native-prop-types": "^2.3.0",
68
- "react-native-pdf-jsi": "^2.2.4"
67
+ "deprecated-react-native-prop-types": "^2.3.0"
69
68
  },
70
69
  "devDependencies": {
71
70
  "@babel/core": "^7.20.2",