Giter Site home page Giter Site logo

oblador / react-native-pinchable Goto Github PK

View Code? Open in Web Editor NEW
222.0 1.0 23.0 807 KB

Instagram like pinch to zoom for React Native

License: MIT License

JavaScript 3.29% Objective-C 16.38% Ruby 3.80% Starlark 3.77% Java 48.84% Makefile 3.01% C++ 13.73% Objective-C++ 7.19%
react-native gesture pinch-to-zoom

react-native-pinchable's Introduction

React Native Pinchable

Instagram like pinch to zoom for React Native.

Demo

screencast

See Example folder.

Sponsors

If you find the library useful, please consider sponsoring on Github.


Klarna

Klarna aims to make online shopping frictionless and are hiring engineers in Stockholm, Berlin and Milan. Join me to work on one of the largest greenfield React Native apps in the community.

Installation

# Add dependency
yarn add react-native-pinchable
# Link iOS dependency
pod install --project-directory=ios
# Compile project
react-native run-ios # or run-android

Usage

import Pinchable from 'react-native-pinchable';

<Pinchable>
  <Image source={...}>
</Pinchable>

Properties

Prop Description Default
minimumZoomScale The minimum allowed zoom scale. 1
maximumZoomScale The maximum allowed zoom scale. 3

Limitations

On Android it's not possible to receive touch events on the views inside the Pinchable component.

License

MIT License. © Joel Arvidsson 2019 - present

react-native-pinchable's People

Contributors

dein1 avatar oblador avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

react-native-pinchable's Issues

adding onActive event

We needed this feature to be able to prevent click or any other events before pinch zoom completed.

diff --git a/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java b/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java
index 9f22074..b934292 100644
--- a/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java
+++ b/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableView.java
@@ -18,6 +18,11 @@ import android.animation.ValueAnimator;
 import android.view.animation.DecelerateInterpolator;

 import com.facebook.react.views.view.ReactViewGroup;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.Arguments;
+
+import javax.annotation.Nullable;

 public class PinchableView extends ReactViewGroup implements OnTouchListener {
     private final int animationDuration = 400;
@@ -33,6 +38,7 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
     private ValueAnimator currentAnimator = null;
     private ColorDrawable backdrop = null;
     private BitmapDrawable clone = null;
+    private @Nullable RCTEventEmitter mEventEmitter;

     public PinchableView(Context context) {
         super(context);
@@ -122,6 +128,7 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
         }

         active = true;
+        setOnActive(true);

         if (backdrop == null) {
             backdrop = new ColorDrawable(Color.BLACK);
@@ -195,6 +202,7 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
             clone = null;
         }
         setVisibility(View.VISIBLE);
+        setOnActive(false);
     }

     public void setMinimumZoomScale(float minimumZoomScale) {
@@ -204,4 +212,16 @@ public class PinchableView extends ReactViewGroup implements OnTouchListener {
     public void setMaximumZoomScale(float maximumZoomScale) {
         maxScale = maximumZoomScale;
     }
+
+    public void setOnActive(boolean active) {
+        if (mEventEmitter != null) {
+            WritableMap params = Arguments.createMap();
+            params.putBoolean("value", active);
+            mEventEmitter.receiveEvent(getId(), "onActive", params);
+        }
+    }
+
+    public void setEventEmitter(RCTEventEmitter eventEmitter) {
+        mEventEmitter = eventEmitter;
+    }
 }
diff --git a/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableViewManager.java b/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableViewManager.java
index 22457bb..7a7017f 100644
--- a/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableViewManager.java
+++ b/node_modules/react-native-pinchable/android/src/main/java/com/oblador/pinchable/PinchableViewManager.java
@@ -6,8 +6,13 @@ import com.facebook.react.uimanager.ViewGroupManager;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+import com.facebook.react.common.MapBuilder;
+
+import java.util.Map;

 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;

 public class PinchableViewManager extends ViewGroupManager<PinchableView> {
     private static final String REACT_CLASS = "PinchableView";
@@ -28,7 +33,9 @@ public class PinchableViewManager extends ViewGroupManager<PinchableView> {

     @Override
     public @Nonnull PinchableView createViewInstance(@Nonnull ThemedReactContext ctx) {
-        return new PinchableView(ctx);
+        PinchableView pinchableView = new PinchableView(ctx);
+        pinchableView.setEventEmitter(ctx.getJSModule(RCTEventEmitter.class));
+        return pinchableView;
     }

     @ReactProp(name = "minimumZoomScale", defaultFloat = defaultMinimumZoomScale)
@@ -40,4 +47,13 @@ public class PinchableViewManager extends ViewGroupManager<PinchableView> {
     public void setMaximumZoomScale(PinchableView view, float maximumZoomScale) {
         view.setMaximumZoomScale(maximumZoomScale);
     }
+
+    @Override
+    public @Nullable
+    Map getExportedCustomDirectEventTypeConstants() {
+        return MapBuilder.of(
+                "onActive",
+                MapBuilder.of("registrationName", "onActive")
+        );
+    }
 }
diff --git a/node_modules/react-native-pinchable/index.js b/node_modules/react-native-pinchable/index.js
index 2dea3c3..12f2413 100755
--- a/node_modules/react-native-pinchable/index.js
+++ b/node_modules/react-native-pinchable/index.js
@@ -1,3 +1,20 @@
+import React, {Component} from "react";
 import { requireNativeComponent } from 'react-native';
+import PropTypes from 'prop-types';

-export default requireNativeComponent('PinchableView');
+class PinchableView extends Component {
+  static propTypes = {
+    onActive: PropTypes.func,
+    // other props...
+  };
+
+  render() {
+    return <PinchableView {...this.props} />;
+  }
+}
+
+const PinchableViewNative = requireNativeComponent('PinchableView', PinchableView, {
+  nativeOnly: { onActive: true },
+});
+
+export default PinchableViewNative;
diff --git a/node_modules/react-native-pinchable/ios/RNPinchableView.h b/node_modules/react-native-pinchable/ios/RNPinchableView.h
index 6ae6599..5ca103f 100644
--- a/node_modules/react-native-pinchable/ios/RNPinchableView.h
+++ b/node_modules/react-native-pinchable/ios/RNPinchableView.h
@@ -15,6 +15,9 @@ NS_ASSUME_NONNULL_BEGIN

 @property (nonatomic) CGFloat minimumZoomScale;
 @property (nonatomic) CGFloat maximumZoomScale;
+@property (nonatomic, copy) RCTBubblingEventBlock onActive;
+
+- (void)onActive:(BOOL)isActive;

 @end

diff --git a/node_modules/react-native-pinchable/ios/RNPinchableView.m b/node_modules/react-native-pinchable/ios/RNPinchableView.m
index 84429f3..c59bbd8 100644
--- a/node_modules/react-native-pinchable/ios/RNPinchableView.m
+++ b/node_modules/react-native-pinchable/ios/RNPinchableView.m
@@ -40,6 +40,8 @@ UIView *backgroundView;
   initialTouchPoint = CGPointZero;
   lastTouchPoint = CGPointZero;
   backgroundView = nil;
+  [self onActive:NO];
+
 }

 - (void)setupGesture
@@ -74,6 +76,7 @@ UIView *backgroundView;
     isActive = YES;
     initialSuperView = view.superview;
     initialIndex = [initialSuperView.subviews indexOfObject:view];
+    [self onActive:YES];

     CGPoint center = [gestureRecognizer locationInView:view];
     CGPoint absoluteOrigin = [view.superview convertPoint:view.frame.origin toView:window];
@@ -130,4 +133,18 @@ UIView *backgroundView;
   }
 }

+- (void)onActive:(BOOL)isActive {
+    // Implement your onActive logic here with the boolean parameter
+    NSLog(@"ETCView is active: %@", isActive ? @"YES" : @"NO");
+    // You can also emit events or perform other actions as needed.
+
+    if (!self.onActive) {
+        return;
+      }
+
+    self.onActive(@{
+        @"value": @(isActive)
+    });
+}
+
 @end
diff --git a/node_modules/react-native-pinchable/ios/RNPinchableViewManager.m b/node_modules/react-native-pinchable/ios/RNPinchableViewManager.m
index 06764d3..52e8770 100644
--- a/node_modules/react-native-pinchable/ios/RNPinchableViewManager.m
+++ b/node_modules/react-native-pinchable/ios/RNPinchableViewManager.m
@@ -8,6 +8,7 @@

 #import "RNPinchableViewManager.h"
 #import "RNPinchableView.h"
+#import <React/RCTUIManager.h>

 @implementation RNPinchableViewManager

@@ -20,5 +21,6 @@ RCT_EXPORT_MODULE(PinchableView)

 RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat);
 RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat);
+RCT_EXPORT_VIEW_PROPERTY(onActive, RCTBubblingEventBlock)

 @end

usage
<Pinchable onActive={x => setIsPinching(x.nativeEvent.value)}>

Pinchable not working for video element

Sample Code

import Video from 'react-native-video';
import Pinchable from 'react-native-pinchable';

<Pinchable>
            <Video
                        source={{ uri: props.uri }}
                        style={{
                            width: desiredSize.width,
                            height: desiredSize.height,
                            borderRadius: 25
                        }}
                        onLoad={handleVideoLayout}
                        muted={muted}
                        onError={handleError}
                        paused={props.paused}
            />
</Pinchable>

Thankfully it worked for Image & FastImage

Pinch Images Programmatically

Hi. Thanks for the library. I want to ask if there is a way to pinch images programmatically. For example, if I double tap on the image or press a button then the image is automatically larger and zooms to the screen like when I use my fingers. Thank you

Android pinch-zoom always from center

Not like iOS , you can pinch anywhere you like to zoom in&out.

on Android, picture will always zoom from center point.

how to make the behavior looks like iOS?

Thanks.

Updating ScrollView content while view is pinched in makes app unresponsive

Hi. We are displaying array of images in ScrollView and if we push new images while the Pinchable component is pinched in, the app is covered with gray overlay, which can't be discarded and makes the app unresponsive.

Here is simple code for reproduction. Launch the app, zoom in and wait for the timeout in useEffect

import React, { useEffect, useState } from 'react'
import { ScrollView, View, Image, Dimensions } from 'react-native'
import Pinchable from 'react-native-pinchable'

export default function App() {
	const [sources, setSources] = useState(
		new Array(20).fill(0).map((_, index) => 'https://picsum.photos/id/' + index + '/200/300'),
	)

	useEffect(() => {
		setTimeout(() => {
			const newSources = new Array(20)
				.fill(0)
				.map((_, index) => 'https://picsum.photos/id/' + (20 + index) + '/200/300')
			setSources(c => [...c, ...newSources])
		}, 5000)
	}, [])

	return (
		<View style={{ flex: 1 }}>
			<ScrollView pinchGestureEnabled={false}>
				{sources.map((image, index) => (
					<Pinchable key={index} minimumZoomScale={1} maximumZoomScale={3}>
						<Image
							style={{ width: Dimensions.get('window').width, height: 300 }}
							resizeMode="cover"
							source={{ uri: image }}
						/>
					</Pinchable>
				))}
			</ScrollView>
			<View style={{ flex: 1 }} />
		</View>
	)
}

Here is video of the code above:

Simulator.Screen.Recording.-.iPhone.11.Pro.Max.-.2022-05-26.at.11.23.12.mp4

In the place we are using this Pinchable component it behaves a little bit different and if I try to pinch in (after the error happens), the app crashes with following error.

*** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan nan]. Layer: <CALayer:0x600002bfe420; position = CGPoint (206 448); bounds = CGRect (0 0; 0 0); delegate = <RNPinchableView: 0x143cde530; reactTag: 10717; frame = (nan nan; 0 0); anchorPoint = (inf, inf); gestureRecognizers = <NSArray: 0x60000218e1c0>; layer = <CALayer: 0x600002bfe420>>; sublayers = (<CALayer: 0x60000282cb80>); opaque = YES; allowsGroupOpacity = YES; anchorPoint = CGPoint (inf inf); transform = CATransform3D (1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1); borderColor = (null)>'
*** First throw call stack:
(
	0   CoreFoundation                      0x000000010b8c7d70 __exceptionPreprocess + 236
	1   libobjc.A.dylib                     0x0000000108b1614c objc_exception_throw + 56
	2   CoreFoundation                      0x000000010b8c7c40 -[NSException initWithCoder:] + 0
	3   QuartzCore                          0x000000010b517afc -[CALayer setPosition:] + 384
	4   QuartzCore                          0x000000010b5181d8 -[CALayer setFrame:] + 424
	5   UIKitCore                           0x0000000120f7ffa4 -[UIView(Geometry) setFrame:] + 428
	6   ZootMobileApp                       0x0000000103c993f8 -[RNPinchableView handlePinchGesture:] + 1156
	7   UIKitCore                           0x000000012054eee0 -[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 52
	8   UIKitCore                           0x000000012055820c _UIGestureRecognizerSendTargetActions + 112
	9   UIKitCore                           0x000000012055511c _UIGestureRecognizerSendActions + 316
	10  UIKitCore                           0x0000000120554720 -[UIGestureRecognizer _updateGestureForActiveEvents] + 632
	11  UIKitCore                           0x0000000120548d90 _UIGestureEnvironmentUpdate + 2036
	12  UIKitCore                           0x0000000120548134 -[UIGestureEnvironment _updateForEvent:window:] + 736
	13  UIKitCore                           0x0000000120a74350 -[UIWindow sendEvent:] + 4304
	14  UIKitCore                           0x0000000120a4c0fc -[UIApplication sendEvent:] + 784
	15  UIKitCore                           0x0000000120ada59c __dispatchPreprocessedEventFromEventQueue + 7720
	16  UIKitCore                           0x0000000120adc620 __processEventQueue + 6764
	17  UIKitCore                           0x0000000120ad4540 __eventFetcherSourceCallback + 184
	18  CoreFoundation                      0x000000010b836234 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
	19  CoreFoundation                      0x000000010b836134 __CFRunLoopDoSource0 + 204
	20  CoreFoundation                      0x000000010b8354c4 __CFRunLoopDoSources0 + 256
	21  CoreFoundation                      0x000000010b82fa18 __CFRunLoopRun + 744
	22  CoreFoundation                      0x000000010b82f218 CFRunLoopRunSpecific + 572
	23  GraphicsServices                    0x000000011173b60c GSEventRunModal + 160
	24  UIKitCore                           0x0000000120a2da98 -[UIApplication _run] + 992
	25  UIKitCore                           0x0000000120a32634 UIApplicationMain + 112
	26  ZootMobileApp                       0x0000000102d43dd0 main + 104
	27  dyld                                0x00000001076b9cd8 start_sim + 20
	28  ???                                 0x000000010740108c 0x0 + 4416606348
	29  ???                                 0x303d000000000000 0x0 + 3475934487399890944
)
libc++abi: terminating with uncaught exception of type NSException
dyld4 config: DYLD_ROOT_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot DYLD_LIBRARY_PATH=/Users/pavelgric/Library/Developer/Xcode/DerivedData/ZootMobileApp-cwmmojyrqvickmafcfxbhvwcllaw/Build/Products/Debug-iphonesimulator:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libBacktraceRecording.dylib:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libMainThreadChecker.dylib:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib DYLD_FRAMEWORK_PATH=/Users/pavelgric/Library/Developer/Xcode/DerivedData/ZootMobileApp-cwmmojyrqvickmafcfxbhvwcllaw/Build/Products/Debug-iphonesimulator
terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan nan]. Layer: <CALayer:0x600002bfe420; position = CGPoint (206 448); bounds = CGRect (0 0; 0 0); delegate = <RNPinchableView: 0x143cde530; reactTag: 10717; frame = (nan nan; 0 0); anchorPoint = (inf, inf); gestureRecognizers = <NSArray: 0x60000218e1c0>; layer = <CALayer: 0x600002bfe420>>; sublayers = (<CALayer: 0x60000282cb80>); opaque = YES; allowsGroupOpacity = YES; anchorPoint = CGPoint (inf inf); transform = CATransform3D (1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1); borderColor = (null)>'
CoreSimulator 802.6.1 - Device: iPhone 11 Pro Max (98939DCB-055B-4061-8565-431AE3AC31D6) - Runtime: iOS 15.5 (19F70) - DeviceType: iPhone 11 Pro Max

Not an issue but appreciation

Works brilliantly, thanks for the library.

The whole react native ecosystem is looking at a library like this one. Mentioned it everywhere possible.

Pinchable inside a pressable component

Library works awesome: great job!

One minor inconvenience is that if the pinchable component (image) is inside a parent pressable item, upon leaving the pinched image to zoom, it will trigger the pressable component behind.

is there any way that when the pinch is triggered, it disables any "background" event?

Implementation of pinch event

Hello,

I've tested your amazing work with this lib.
I've switch from another (@likashefqet/react-native-image-zoom) to be able to use FastImage and his caching feature.
Following this i've test your lib for the zoom feature and work very great.

But something lack for me, something to trigger event when image is pinch. Do you think it's something you can implement in lib ? Thanks

This is incredible

Thank you so much for making this, this is awesome!

Is there anyway that you can disable when the user does the opposite of a zoom in to only allow for zoom ins and not zoom outs when the scale is 1?

react-native-pinchable and Expo

I get the following error when trying to use this library with expo:
Invariant Violation: requireNativeComponent: "PinchableView" was not found in the UIManager.

more controls over the component

If we can have more controls over the component like,

Background color - when pinch begin. Because at the moment it is set to single semi black color which is being from 0 to 0.5 transperancy.

autoScaleBack - we should have control over the pinch back function. Currently it is scales back to 1.

responder events - events like onPinchBehin, onPinchEnd….

Otherwise the lib working very good.

Disable background touch events, until images gets settled to its original position.

Great library !

It would be great if background touch/scroll etc.. events can be stopped until image gets settled down to its original position.
Because in few case I have found that user tries to scroll away fast (till that time image was not settled properly to its actual position) and since the actual position of that image gets lost, that image get stuck on the screen. Then user needs to restart the app.

Note : I also confirm this issue exists on android

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.