Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOS: Not receiving PanResponder events #2332

Closed
longnk1301 opened this issue Jul 5, 2024 · 5 comments
Closed

IOS: Not receiving PanResponder events #2332

longnk1301 opened this issue Jul 5, 2024 · 5 comments

Comments

@longnk1301
Copy link

longnk1301 commented Jul 5, 2024

Question

Guys, this is my ArcSlider component, it's working on Android, but when I run in IOS it's not working, I can not move my Circle, I try many ways but it still does not work, please help me!

import React, {PureComponent} from 'react';
import {PanResponder, StyleSheet, View} from 'react-native';
import Svg, {Circle, Defs, LinearGradient, Path, Stop} from 'react-native-svg';
import Colors from '../constants/Colors';

export class ArcSlider extends PureComponent {
  static defaultProps = {
    radius: 100,
    strokeWidth: 20,
    openingRadian: Math.PI / 4,
    backgroundTrackColor: Colors.backgroundTrackColor,
    linearGradient: [
      {stop: '0%', color: Colors.arcSlider},
      {stop: '100%', color: Colors.arcSlider},
    ],
    min: 0,
    max: 100,
    buttonRadius: 12,
    buttonBorderColor: Colors.white,
    buttonStrokeWidth: 1,
  };

  constructor(props) {
    super(props);
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponder: () => false,
      onPanResponderGrant: this._handlePanResponderGrant,
      onPanResponderMove: this._handlePanResponderMove,
      onPanResponderRelease: this._handlePanResponderEnd,
      onPanResponderTerminationRequest: () => false,
      onPanResponderTerminate: this._handlePanResponderEnd,
    });

    this.state = {
      value: props.defaultValue || props.min,
      afterStepMoveValue: null,
    };

    this._containerRef = React.createRef();

    this._sliderRef = React.createRef();
    this._sliderRef.current = {
      increase: () => {
        this._increase();
      },
      decrease: () => {
        this._decrease();
      },
      setValue: _value => {
        this._setValue(_value);
      },
    };

    this.props.getSliderRef(this._sliderRef.current);
  }

  _setValue = value => {
    this.setState({value});
  };

  _increase = () => {
    this.setState(
      ({value}) => {
        if (value < this.props.max) {
          return {value: value + this.props.step};
        }
        return {value};
      },
      () => {
        this._fireChangeEvent('onComplete');
      },
    );
  };
  _decrease = () => {
    this.setState(
      ({value}) => {
        if (value > this.props.min) {
          return {value: value - this.props.step};
        }
        return {value};
      },
      () => {
        this._fireChangeEvent('onComplete');
      },
    );
  };

  _handlePanResponderGrant = () => {
    const {value} = this.state;
    this._moveStartValue = value;
    this._moveStartRadian = this.getRadianByValue(value);
    this._startCartesian = this.polarToCartesian(this._moveStartRadian);
  };

  _handlePanResponderMove = (e, gestureState) => {
    if (this.props.disabled) {
      return;
    }
    const {min, max, step, openingRadian} = this.props;
    let {x, y} = this._startCartesian;
    x += gestureState.dx;
    y += gestureState.dy;
    const radian = this.cartesianToPolar(x, y);
    const ratio =
      (this._moveStartRadian - radian) / ((Math.PI - openingRadian) * 2);
    const diff = max - min;

    let value;
    let afterStepMoveValue = null;
    if (step) {
      afterStepMoveValue =
        this._moveStartValue + Math.round((ratio * diff) / step) * step;
      afterStepMoveValue = Math.max(min, Math.min(max, afterStepMoveValue));
    }

    value = this._moveStartValue + ratio * diff;
    value = Math.max(min, Math.min(max, value));

    this.setState(
      ({value: curValue}) => {
        value = Math.abs(value - curValue) > diff / 4 ? curValue : value;
        return {
          value: Math.round(value),
          afterStepMoveValue,
        };
      },
      () => {
        this._fireChangeEvent('onChange');
      },
    );
  };

  _handlePanResponderEnd = (e, gestureState) => {
    if (this.props.disabled) {
      return;
    }
    this.setState(
      prev => {
        if (prev.afterStepMoveValue !== null) {
          return {value: prev.afterStepMoveValue, afterStepMoveValue: null};
        }
        return prev;
      },
      () => {
        this._fireChangeEvent('onComplete');
      },
    );
  };

  _fireChangeEvent = event => {
    if (this.props[event]) {
      this.props[event](this.state.value);
    }
  };

  polarToCartesian(radian) {
    const {radius} = this.props;
    const distance = radius + this._getExtraSize() / 2;
    const x = distance + radius * Math.sin(radian);
    const y = distance + radius * Math.cos(radian);
    return {x, y};
  }

  cartesianToPolar(x, y) {
    const {radius} = this.props;
    const distance = radius + this._getExtraSize() / 2;
    if (x === distance) {
      return y > distance ? 0 : Math.PI / 2;
    }
    const a = Math.atan((y - distance) / (x - distance));
    return (x < distance ? (Math.PI * 3) / 2 : Math.PI / 2) - a;
  }

  getCurrentRadian() {
    return this.getRadianByValue(this.state.value);
  }

  getRadianByValue(value) {
    const {openingRadian, min, max} = this.props;
    return (
      ((Math.PI - openingRadian) * 2 * (max - value)) / (max - min) +
      openingRadian
    );
  }

  _getExtraSize() {
    const {strokeWidth, buttonRadius, buttonStrokeWidth, borderTrackWidth} =
      this.props;
    return Math.max(
      strokeWidth + borderTrackWidth * 2,
      (buttonRadius + buttonStrokeWidth) * 2,
    );
  }

  _onLayout = () => {
    const ref = this._containerRef.current;
    if (ref) {
      ref.measure((x, y, width, height, pageX, pageY) => {});
    }
  };

  render() {
    const {
      radius,
      strokeWidth,
      backgroundTrackColor,
      borderTrackColor = 'white',
      borderTrackWidth = 0,
      openingRadian,
      linearGradient,
      buttonRadius,
      buttonBorderColor,
      buttonFillColor,
      buttonStrokeWidth,
      style,
      contentContainerStyle,
      children,
    } = this.props;
    const svgSize = radius * 2 + this._getExtraSize();
    const startRadian = 2 * Math.PI - openingRadian;
    const startPoint = this.polarToCartesian(startRadian);
    const endPoint = this.polarToCartesian(openingRadian);
    const currentRadian = this.getCurrentRadian();
    const curPoint = this.polarToCartesian(currentRadian);
    const contentStyle = [
      {width: svgSize, height: svgSize},
      contentContainerStyle,
    ];
    const centerTopPoint = this.polarToCartesian(this.getRadianByValue(50));

    return (
      <View
        onLayout={this._onLayout}
        ref={this._containerRef}
        style={[styles.container, style]}>
        <Svg width={svgSize} height={svgSize}>
          <Defs>
            <LinearGradient x1="0%" y1="100%" x2="100%" y2="0%" id="gradient">
              {linearGradient.map(item => (
                <Stop
                  key={`${JSON.stringify(item)}`}
                  offset={item.stop}
                  stopColor={item.color}
                />
              ))}
            </LinearGradient>
          </Defs>
          {!this.props.disabled && (
            <Path
              strokeWidth={strokeWidth + borderTrackWidth * 2}
              stroke={borderTrackColor}
              fill="none"
              strokeLinecap="round"
              strokeLinejoin="round"
              d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${
                startRadian - openingRadian >= Math.PI ? '1' : '0'
              },1,${endPoint.x},${endPoint.y}`}
            />
          )}
          <Path
            strokeWidth={strokeWidth}
            stroke={backgroundTrackColor}
            fill="none"
            strokeLinecap="round"
            strokeLinejoin="round"
            d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${
              startRadian - openingRadian >= Math.PI ? '1' : '0'
            },1,${endPoint.x},${endPoint.y}`}
          />
          <Path
            strokeWidth={strokeWidth}
            stroke={
              this.props.disabled ? backgroundTrackColor : 'url(#gradient)'
            }
            fill="none"
            strokeLinecap="round"
            strokeLinejoin="round"
            d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${
              startRadian - currentRadian >= Math.PI ? '1' : '0'
            },1,${curPoint.x},${curPoint.y}`}
          />
          {!this.props.disabled && (
            <Circle
              cx={curPoint.x}
              cy={curPoint.y}
              r={buttonRadius}
              fill={buttonFillColor || buttonBorderColor}
              stroke={buttonBorderColor}
              strokeWidth={buttonStrokeWidth}
              {...this._panResponder.panHandlers}
            />
          )}
          <Circle
            cx={startPoint.x - Math.sqrt(Math.pow(buttonRadius / 2, 2) / 2)}
            cy={startPoint.y + Math.sqrt(Math.pow(buttonRadius / 2, 2) / 2)}
            r={2}
            fill={
              this.state.value < 0 || this.props.disabled
                ? Colors.dotDisable
                : Colors.white
            }
            stroke="none"
          />
          <Circle
            cx={endPoint.x + Math.sqrt(Math.pow(buttonRadius / 2, 2) / 2)}
            cy={endPoint.y + Math.sqrt(Math.pow(buttonRadius / 2, 2) / 2)}
            r={2}
            fill={
              this.state.value < 100 || this.props.disabled
                ? Colors.dotDisable
                : Colors.white
            }
            stroke="none"
          />
          <Circle
            cx={centerTopPoint.x}
            cy={centerTopPoint.y - buttonRadius / 2}
            r={2}
            fill={
              this.state.value < 50 || this.props.disabled
                ? Colors.dotDisable
                : Colors.white
            }
            stroke="none"
          />
        </Svg>
        <View style={[StyleSheet.absoluteFill, styles.container]}>
          <View style={contentStyle} pointerEvents="box-none">
            {children}
          </View>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
  },
});
@github-actions github-actions bot added the Missing info The user didn't precise the problem enough label Jul 5, 2024
Copy link

github-actions bot commented Jul 5, 2024

Hey! 👋

It looks like you've omitted a few important sections from the issue template.

Please complete Description, Steps to reproduce, Snack or a link to a repository, SVG version, React Native version and Platforms sections.

Copy link

github-actions bot commented Jul 5, 2024

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

@github-actions github-actions bot added the Missing repro This issue need minimum repro scenario label Jul 5, 2024
@bohdanprog bohdanprog added Repro provided Question and removed Missing repro This issue need minimum repro scenario Missing info The user didn't precise the problem enough labels Jul 7, 2024
@bohdanprog
Copy link
Member

Hello @longnk1301,
I could reproduce that example.
Could you provide a repo with an example of how we can reproduce that?
Thank you.

@longnk1301
Copy link
Author

@bohdanprog Thanks, this issue for my View, I already fixed

@ShaoGongBra
Copy link

谢谢,这个问题是我的观点,我已经解决了

How to solve it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants