React-native: scrollview inside of panResponder - ios

I am using a ScrollView inside of a PanResponder. On Android it works fine but on iOS the ScrollView will not scroll. I did some investigation and here are some facts:
If I put a break point in PanResponder.onMoveShouldSetPanResponder(), before I step over, the scrollView will scroll as normal but once I release the break point, the scrollView stops working.
If I modify ScrollResponder.js, and return true in scrollResponderHandleStartShouldSetResponderCapture() - it used to return false at runtime; and return false in scrollResponderHandleTerminationRequest(), the scrollView works OK but of course, since it swallows the event the outer PanResponder will not get the event.
So the questions are:
I want to make the scrollview to work, and not to swallow the event. Any one know what's the approach?
How the responding system works on iOS? The react-native responder system doc does not explain that to me.

To enable scrolling in a ScrollView that is a child of a parent with PanResponder, you have to make sure the ScrollView is the responder to any gesture inside of it. By default, gesture events bubble up from the deepest component to the parent component. In order to capture the event by the ScrollView, you can add a View with a PanResponder inside of it. See the (pseudo) example below, where ChildComponent is a child of a parent with PanResponder.
const ChildComponent = ({ theme }) => {
const panResponder = React.useRef(
PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
})
).current;
return (
<ScrollView style={{ height: 500 }}>
<View {...panResponder.panHandlers}>
...
</View>
</ScrollView>
);
};

Within PanResponder is an event that returns the current touch position. You can use that to compare 2 values to perform a scroll.

I finally solve this by wrap the scrollview inside a view ,and set the style of scrollview a limited height.
import * as React from 'react';
import { Text, View, StyleSheet,PanResponder,Animated,ScrollView,Dimensions} from 'react-native';
import { Constants } from 'expo';
const WINDOW_WIDTH = Dimensions.get("window").width;
const WINDOW_HEIGHT = Dimensions.get("window").height;
// You can import from local files
export default class App extends React.Component {
constructor(props){
super(props);
this.minTop = 100;
this.maxTop = 500;
this.state={
AnimatedTop:new Animated.Value(0),
}
}
componentWillMount(){
let that = this;
this._previousTop = 100;
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder(){
return true;
},
onPanResponderGrant(){
that._previousTop = that.state.AnimatedTop.__getValue();
return true;
},
onPanResponderMove(evt,gestureState){
let currentTop = that._previousTop + gestureState.dy;
that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);
},
onPanResponderRelease(){
}
})
}
render() {
return (
<View style={styles.container}>
<Animated.View
style={[styles.overlay,{top:this.state.AnimatedTop}]}
{...this._panResponder.panHandlers}
>
<View style={{height:200,backgroundColor:"black"}}></View>
<View>
<ScrollView
style={{height:500}}
>
<View style={{backgroundColor:"blue",height:200}}></View>
<View style={{backgroundColor:"yellow",height:200}}></View>
<View style={{backgroundColor:"pink",height:200}}></View>
<View style={{backgroundColor:"red",height:200}}></View>
</ScrollView>
</View>
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
overlay:{
position:"absolute",
width:WINDOW_WIDTH,
height:WINDOW_HEIGHT-100,
}
});
enter link description here
I spent plenty of time to solve this.Hope this will help someone confused with the same problem.

I don't know if it's usefull right now but you can add that line,
return !(gestureState.dx === 0 && gestureState.dy === 0)
in 'onMoveShouldSetPanResponder' property of PanResponder with evt and gestureState as parameters.
Your PanResponder should look like this :
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder(evt, gestureState){
return !(gestureState.dx === 0 && gestureState.dy === 0)
},
onPanResponderGrant(){
that._previousTop = that.state.AnimatedTop.__getValue();
return true;
},
onPanResponderMove(evt,gestureState){
let currentTop = that._previousTop + gestureState.dy;
that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);
},
onPanResponderRelease(){
}
})

I have solved this by adding onPress handlers to all of the contents of the ScrollView - doesn't matter if the handler is a no-op, the scroll view still works fine.

I'm not sure you still need this or not but I'll put to help others as well
If you put the panResponder on a sibling view of the ScrollView, the ScrollView behaves properly. then you can position that sibling view
here is an example of the workaround.

I solved this issue by doing it
onMoveShouldSetPanResponder: (event, gesture) => {
if (gesture?.moveX > gesture?.moveY) {
return false;
}
return true;
},
On my case, I have a horizontal Flatlist inside a PanResponder...

Related

KeyboardAvoidingView overlapping screen on iPhone X

I currently have a KeyboardAvoidingView with a hard-coded keyboardVerticalOffset of 64. This works fine on the iPhone but is about 20px short on the iPhone X.
The component looks like this:
<KeyboardAvoidingView behavior='padding' keyboardVerticalOffset={ 64 }>
<View style={ styles.messageList }>
...
</View>
<View style={ styles.messageInput }>
...
</View>
</KeyboardAvoidingView>
Is there a better way to determine what keyboardVerticalOffset should be than hard coding a value? Is there something else I could be doing differently with component placement? I'm open to any suggestions.
iPhone 8
iPhone X
This is caused by the status bar height being different for iphoneX. (you also get the same issue with other iphones if you toggle the 'in-call' status bar using ⌘Y in the simulator).
You can get the status bar height and use this to set the keyboardVerticalOffset value of the KeyboardAvoidingView. (in our case this was 44 + statusBarHeight)
import React, {Component} from 'react';
import {KeyboardAvoidingView, NativeModules, StatusBarIOS} from 'react-native';
const {StatusBarManager} = NativeModules;
export class IOSKeyboardAvoidingView extends Component {
state = {statusBarHeight: 0};
componentDidMount() {
StatusBarManager.getHeight((statusBarFrameData) => {
this.setState({statusBarHeight: statusBarFrameData.height});
});
this.statusBarListener = StatusBarIOS.addListener('statusBarFrameWillChange', (statusBarData) => {
this.setState({statusBarHeight: statusBarData.frame.height});
});
}
componentWillUnmount() {
this.statusBarListener.remove();
}
render() {
const {style, children} = this.props;
return (
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={44 + this.state.statusBarHeight}
style={style}
>{children}
</KeyboardAvoidingView>
);
}
}
Please refer to : https://stackoverflow.com/a/51169574/10031014 for similar issues
I have used a custom component to overcome this situation.
import React from "react";
import {Animated, Keyboard} from "react-native";
export default class KeyboardAwareComponent extends React.Component {
constructor(props) {
super(props)
this.keyboardHeight = new Animated.Value(0);
}
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
}
keyboardWillShow = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
})
]).start();
};
keyboardWillHide = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: 0,
})
]).start();
};
render(){
const {children, style, ...props} = this.props
return(
<Animated.View style={[{flex:1,alignItems:'center',paddingBottom: this.keyboardHeight},style]} {...props}>
{children}
</Animated.View>
);
}
}
Just use the component "KeyboardAwareComponent" as a root component of any page. It will automatically adjust the view when keyboard will show or hide.
Example:---
YourComponent extends React.Component{
render(){
<KeyboardAwareComponent>
{Your child views}
</KeyboardAwareComponent>
}
}
So I did a quick check, given my understanding of how to do this in native iOS, and it seems like in newer versions of react native, you can do this relatively easily.
There do seem to be a couple of options, depending on your flexibility needs.
First, have you tried using KeyboardAvoidView instead of a standard container View without specifying keyboardVerticalOffset?
Another option that gives you much more control (similar to what I would do in a native iOS app) is to use the Keyboard module to create listeners on the keyboard events.
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillChangeSub = Keyboard.addListener('keyboardWillChangeFrame', this.keyboardWillChange);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillChangeSub.remove();
this.keyboardWillHideSub.remove();
}
This would allow you to get the keyboard height from the event parameter:
keyboardWillShow = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
}),
Animated.timing(this.imageHeight, {
duration: event.duration,
toValue: IMAGE_HEIGHT_SMALL,
}),
]).start();
};
Repeat something similar for keyboardWillChange and keyboardWillHide.
For a better, more detailed explanation of your options, see this page:
https://medium.freecodecamp.org/how-to-make-your-react-native-app-respond-gracefully-when-the-keyboard-pops-up-7442c1535580
I think the best first test would be to try to remove the keyboardVerticalOffset before trying to add code to handle the keboard events.

How to prevent layout from overlapping with iOS status bar

I am working on tutorial for React Native navigation. I found out that all layout starts loading from top of screen instead of below of the status bar. This causes most layouts to overlap with the status bar. I can fix this by adding a padding to the view when loading them. Is this the actual way to do it? I don' think manually adding padding is an actual way to solve it. Is there a more elegant way to fix this?
import React, { Component } from 'react';
import { View, Text, Navigator } from 'react-native';
export default class MyScene extends Component {
static get defaultProps() {
return {
title : 'MyScene'
};
}
render() {
return (
<View style={{padding: 20}}> //padding to prevent overlap
<Text>Hi! My name is {this.props.title}.</Text>
</View>
)
}
}
Below shows the screenshots before and after the padding is added.
Now you can use SafeAreaView which is included in React Navigation:
<SafeAreaView>
... your content ...
</SafeAreaView>
There is a very simple way to fix this. Make a component.
You can create a StatusBar component and call it first after the first view wrapper in your parent components.
Here is the code for the one I use:
'use strict'
import React, {Component} from 'react';
import {View, Text, StyleSheet, Platform} from 'react-native';
class StatusBarBackground extends Component{
render(){
return(
<View style={[styles.statusBarBackground, this.props.style || {}]}> //This part is just so you can change the color of the status bar from the parents by passing it as a prop
</View>
);
}
}
const styles = StyleSheet.create({
statusBarBackground: {
height: (Platform.OS === 'ios') ? 18 : 0, //this is just to test if the platform is iOS to give it a height of 18, else, no height (Android apps have their own status bar)
backgroundColor: "white",
}
})
module.exports= StatusBarBackground
After doing this and exporting it to your main component, call it like this:
import StatusBarBackground from './YourPath/StatusBarBackground'
export default class MyScene extends Component {
render(){
return(
<View>
<StatusBarBackground style={{backgroundColor:'midnightblue'}}/>
</View>
)
}
}
I tried a more simple way for this.
We can get the height of Status Bar on android and use SafeAreaView along with it to make the code work on both platforms.
import { SafeAreaView, StatusBar, Platform } from 'react-native';
If we log out Platform.OS and StatusBar.currentHeight we get the logs,
console.log('Height on: ', Platform.OS, StatusBar.currentHeight);
Height on: android 24 and
Height on: android 24
We can now optionally add margin/padding to our container view using
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
The final code in App.js is below:
export default class App extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<View style={styles.container}>
<Text>Hello World</Text>
</View>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
}
});
#philipheinser solution does work indeed.
However, I would expect that React Native's StatusBar component will handle that for us.
It doesn't, unfortunately, but we can abstract that away quite easily by creating our own component around it:
./StatusBar.js
import React from 'react';
import { View, StatusBar, Platform } from 'react-native';
// here, we add the spacing for iOS
// and pass the rest of the props to React Native's StatusBar
export default function (props) {
const height = (Platform.OS === 'ios') ? 20 : 0;
const { backgroundColor } = props;
return (
<View style={{ height, backgroundColor }}>
<StatusBar { ...props } />
</View>
);
}
./index.js
import React from 'react';
import { View } from 'react-native';
import StatusBar from './StatusBar';
export default function App () {
return (
<View>
<StatusBar backgroundColor="#2EBD6B" barStyle="light-content" />
{ /* rest of our app */ }
</View>
)
}
Before:
After:
The react-navigation docs have a great solution for this. First off, they recommend not to use the SafeAreaView included with React Native because:
While React Native exports a SafeAreaView component, it has some
inherent issues, i.e. if a screen containing safe area is animating,
it causes jumpy behavior. In addition, this component only supports
iOS 10+ with no support for older iOS versions or Android. We
recommend to use the react-native-safe-area-context library to handle
safe areas in a more reliable way.
Instead, they recommend react-native-safe-area-context - with which it would look like this:
import React, { Component } from 'react';
import { View, Text, Navigator } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
export default function MyScene({title = 'MyScene'}) {
const insets = useSafeArea();
return (
<View style={{paddingTop: insets.top}}>
<Text>Hi! My name is {title}.</Text>
</View>
)
}
I would like to note that it's probably a better idea to use the SafeAreaView that this library offers though, since phones these days may also have elements at the bottom that can overlap UI elements. It all depends on your app of course. (For more detail on that, see the react-navigation docs I linked to in the beginning.)
Here is a way that works for iOS:
<View style={{height: 20, backgroundColor: 'white', marginTop: -20, zIndex: 2}}>
<StatusBar barStyle="dark-content"/></View>
You can handle this by adding a padding to you navigation bar component or just ad a view that has the same hight as the statusbar at the top of your view tree with a backgroundcolor like the facebook app does this.
Just Simple User React native Default StatusBar to achieve this funcationality.
<View style={styles.container}>
<StatusBar backgroundColor={Color.TRANSPARENT} translucent={true} />
<MapView
provider={PROVIDER_GOOGLE} // remove if not using Google Maps
style={styles.map}
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
}}
/>
</View>
If you combine SaveAreaView and StatusBar, you get it.
https://reactnative.dev/docs/statusbar
https://reactnative.dev/docs/safeareaview
Just do this:
<SafeAreaView>
<View style={{flex: 1}}>
<StatusBar translucent={false} backgroundColor="#fff" />
// Your dark magic here
</View>
</SafeAreaView>
[This answer is applicable to Android emulators]
Hi, I have imported status bar from "react-native" and called it at the end of block with status bar style set to auto and it worked for me, the code below is for reference:
import { SafeAreaView,Button, StyleSheet, Text, TextInput, View } from 'react-native';
import { StatusBar } from 'react-native';
export default function App() {
return (
<SafeAreaView style={styles.appContainer}>
<View >
<TextInput placeholder='Add your course goal' />
<Button title="Add Goals" />
</View>
<View>
<Text>List of goals..</Text>
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}

How to make a TouchableOpacity or TouchableHighlight clickable on devices that support 3d touch (force touch)

I've got this code:
<View {...this.panGesture.panHandlers}>
<Animated.View>
<View style={{ position: 'absolute' }}>
<TouchableHighlight onPress={ () => {console.log('hello')}>
</TouchableHighlight>
</View>
</Animated.View>
</View>
and I can't for the love of mine get the onPress to work for iPhones 6s and above, when 3d touch is enabled.
I've tried most solutions suggested here, but with no luck.
I'd appreciate any help at all!
I have been able to workaround this specific issue by making sure the parent PanResponder doesn't grab the responder on move, until the touch has actually moved from the origin:
PanResponder.create({
//... other responder callbacks
onMoveShouldSetPanResponder(e, gestureState) {
if (gestureState.dx === 0 || gestureState.dy === 0) {
return false;
}
return true;
}
});
This was back in around React Native 0.10 days, haven't tried it since. Hope it helps!
Ok after fiddling a lot with this, I realised the solution was partly what jevakallio said BUT I can no longer use Touchables when 3D touch is enabled so I have to reproduce their behaviour manually.
My code now looks like this:
The child code:
let touchable;
if (View.forceTouchAvailable === false) { // if 3d touch is disabled
// just use a Touchable and all should be fine.
touchable = (<TouchableHighlight
onPress={ () => {console.log('hello world');}
style={this.props.style}
underlayColor={this.props.highlightBgColor}
>
{this.props.children}
</TouchableHighlight>);
} else { // Otherwise if 3D touch is enabled
// we'll have to use a child view WITH a PanResponder
touchable = (<View
style={[this.props.style, {
backgroundColor: this.state.panViewIsTapped ?
this.props.highlightBgColor
:
bgColor,
}]}
{...this.panResponderChild.panHandlers}
>
{this.props.children}
</View>);
}
return (<View {...this.panResponderParent.panHandlers}>
<Animated.View>
<View style={{ position: 'absolute' }}>
{touchable}
</View>
</Animated.View>
</View>);
The child pan responder code (this.panResponderChild):
this.panResponderChild = PanResponder.create({
// true because we want tapping on this to set it as the responder
onStartShouldSetPanResponder: () => true,
// true because we want this to capture the responder lock from it's parent on start
onStartShouldSetPanResponderCapture: () => true,
// when the pan responder lock is terminated, set the pan view as NOT tapped
onPanResponderTerminate: () => {
this.setState({ panViewIsTapped: false });
},
// true so that the parent can grab our responder lock if he wan'ts to.
onPanResponderTerminationRequest: () => true,
// false because we DON'T want this btn capturing the resp lock from it's parent on move
onMoveShouldSetPanResponderCapture: () => false,
// false because we DON'T want moving the finger on this to set it as the responder
onMoveShouldSetPanResponder: () => false,
onPanResponderGrant: () => {
this.setState({ panViewIsTapped: true });
},
onPanResponderRelease: () => {
this.setState({ panViewIsTapped: false });
console.log('hello world');
},
})
The parent pan responder code (this.panResponderParent):
this.panResponderParent = PanResponder.create({
// true because we want tapping on the cal, to set it as a responder
onStartShouldSetPanResponder: () => true,
// false because we DON'T want to grab the responder lock from our children on start
onStartShouldSetPanResponderCapture: () => false,
/*
onMoveShouldSetPanResponderCapture:
false because we don't want to accidentally grab the responder lock from
our children on movement.
That's because sometimes even a small tap contains movement,
and thus a big percentage of taps will not work.
Keeping that flag false does not nessecarily mean that our children will
always capture the responder lock on movement, (they can if they want),
we're just not strict enough to grab it from them.
*/
onMoveShouldSetPanResponderCapture: () => false,
/*
onMoveShouldSetPanResponder:
We DO want moving the finter on the cal, to set it as a responder,
BUT, we don't always want moving the finger on an appointment setting this parent
as the responder.
Logic:
So, when the dx AND dy of the pan are 0, we return false, because we don't
want to grab the responder from our appointment children.
For anything other than that we just allow this parent to become the responder.
(dx, dy: accumulated distance of the gesture since the touch started)
*/
onMoveShouldSetPanResponder: (e, gestureState) =>
!(gestureState.dx === 0 || gestureState.dy === 0),
});

How to get content height of ScrollView component for contentOffset prop in React Native?

For my current project, I'm creating a Diary/Calendar type component that shows the current day (Diary view) or current month (Calendar view) centered when the user clicks to see that view.
I'm using a ScrollView to hold my content:
_getInitialOffset(h) {
const FIX = 75; //TODO: Make it less arbitrary
let percentToScroll = (this.props.viewedDate.month()+1)/12; //12 = number of months per year
let { height } = this.props;
let centerFix = height/2;
let contentHeight = h;
let yPos = contentHeight*percentToScroll - centerFix - FIX;
return (yPos > 0) ? yPos : 0;
}
render() {
var year = this.props.viewedDate.year();
var cal = Calendar.get(year);
var contentHeight = this.contentHeight;
return (
<View>
<View style={styles.daysOfWeek}>
{
Calendar.days().map(function(day, i) {
return <Text key={i} style={styles.dayHeader}>{day}</Text>;
})
}
</View>
<ScrollView
ref={ref => {this._scrollView = ref}}
style={{
height: this.props.height,
paddingTop: 15
}}
onContentSizeChange={(w, h) => {
this._scrollView.scrollTo({y: this._getInitialOffset(h), animated: false});
}}>
<Year year={year} calendar={cal}/>
</ScrollView>
</View>
);
}
I'm trying to have it center upon render on the current month, but because my current method (using OnContentSizeChange) occurs after render, there's a frame where the user sees it uncentered, which is bad user experience.
Is there a way to get the content height of a ScrollView component before/during render, or delay component visibility until after the onContentSizeChange method has fired?
onContentSizeChange is internally using onLayout which is triggered as soon as the layout has been computed.
This event is fired immediately once the layout has been calculated, but the new layout may not yet be reflected on the screen at the time the event is received, especially if a layout animation is in progress.
There's no way to get the size before that.
So what you can do is set the opacity of your scrollview to 0 until the first onContentSizeChange is triggered.
I advise you to use Animated.Value to do the opacity change so it doesn't re-render your whole component.
My solution is as follows:
Create a parent container, with scrollOffset animation that tracks the amount of scroll.
Get the height of the ScrollView container ("containerHeight")
Get the height of the contents within the ScrollView ("contentHeight")
Use the ScrollView's "onScroll" event to get the scroll offset. Use it to update the scrollOffset animation.
Create a ScrollBar component based on the scrollOffset animation value, containerHeight, and contentHeight
The following is extracted from my code. It is incomplete and untested, but should be enough to for one to get started.
class CustomScrollView extends React.Component {
constructor(props) {
super(props);
this.state = {
scrollOffsetAnim: new Animated.Value(0)
};
}
renderScrollBar() {
const {
scrollOffsetAnim,
contentHeight,
containerHeight,
} = this.state;
//...
}
render() {
return (
<View>
<ScrollView
scrollEventThrottle={16}
onScroll={(evt) => {
const {contentOffset} = evt.nativeEvent;
const {y} = contentOffset;
this.state.scrollOffsetAnim.setValue(y);
}}
onLayout={(evt) => {
const {height} = evt.nativeEvent.layout;
const {containerHeight} = this.state;
if(!containerHeight || containerHeight !== height) {
this.setState({containerHeight: height})
}
}}
>
<View
onLayout={(evt) => {
const {height} = evt.nativeEvent.layout;
const {contentHeight} = this.state;
if(!contentHeight || contentHeight !== height) {
this.setState({contentHeight: height})
}
}}
>
{this.props.children}
</View>
</ScrollView>
{this.renderScrollBar()}
</View>
);
}
}
If you are trying to get the total height of the insides of a scrollview, just add a generic View with the onLayout prop inside it:
<ScrollView style={{flex:1}} >
<View style ={{flex:1}} onLayout={e=>console.log('e',e.nativeEvent.layout)}>
{YOUR VIEWS HERE}
</View>
</ScrollView>
Every time the component rerenders, it will print the console log ('e') with the informations you probably wishes

How can I create a drag and drop action in React native?

Say I have two Views, A and B. I want to have the ability to trigger a 'dragAndDropStart' event on touching View A and then enable a drag and drop from A to B... displaying feedback to the user throughout (i.e. showing a line appearing between View A and the user's finger). On drop (releasing the drag gesture) I want to trigger another 'dragAndDropEnd' event, this time on View B.
The touchStart and touchEnd handlers are too limited as they don't appear to permit handoff of the gesture from one View to another. They also don't seem to enable the in-between 'drag' state.
The React native docs on using the gesture handlers is a little cryptic and I haven't seen any examples that demonstrate their use.
Any ideas?
export default class Viewport extends Component{
constructor(props){
super(props);
this.state = {
showDraggable : true,
dropZoneValues : null,
pan : new Animated.ValueXY()
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder : () => true,
onPanResponderMove : Animated.event([null,{
dx : this.state.pan.x,
dy : this.state.pan.y
}]),
onPanResponderRelease : (e, gesture) => {
if(this.isDropZone(gesture)){
this.setState({
showDraggable : false
});
}else{
Animated.spring(
this.state.pan,
{toValue:{x:0,y:0}}
).start();
}
}
});
}
isDropZone(gesture){
var dz = this.state.dropZoneValues;
return gesture.moveY > dz.y && gesture.moveY < dz.y + dz.height;
}
setDropZoneValues(event){
this.setState({
dropZoneValues : event.nativeEvent.layout
});
}
render(){
return (
<View style={styles.mainContainer}>
<View
onLayout={this.setDropZoneValues.bind(this)}
style={styles.dropZone}>
<Text style={styles.text}>Drop me here!</Text>
</View>
{this.renderDraggable()}
</View>
);
}
renderDraggable(){
if(this.state.showDraggable){
return (
<View style={styles.draggableContainer}>
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circle]}>
<Text style={styles.text}>Drag me!</Text>
</Animated.View>
</View>
);
}
}
}
source https://moduscreate.com/blog/drag-and-drop-react-native/
https://github.com/crysfel/DragAndDrop
you have to see the current View Rectangle to other view Rectangle if one of your view rectangle intersect each other at some point it will return true then you get notify that the A view was drag to B view here is my example code may be it will help you .
-(void)moveViewWithGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer{
// your current View touch location suppose View A
CGPoint touchLocation = [panGestureRecognizer locationInView:self.contentView];
CGRect movingAViewRect = CGRectMake(touchLocation.x, touchLocation.y, self.aView.width, self.aView.height);
// NSLog(#"Point not Matched first => %# and second => %#",NSStringFromCGPoint(touchLocation),NSStringFromCGPoint(self.bView.frame.origins));
self.aView.center = touchLocation;
if(panGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
//All fingers are lifted.
if(CGRectIntersectsRect(movingAViewRect,self.bView.frame)){
NSLog(#"Point Matched first => %# and second => %#",NSStringFromCGRect(movingAViewRect),NSStringFromCGRect (self.bView.frame ));
// and here you can perform some action for this
}else{
NSLog(#"aView is not drag on bView please drag aView to bView ");
}
}
}

Resources