software-mansion / react-native-svg

SVG library for React Native, React Native Web, and plain React web projects.
MIT License
7.35k stars 1.11k forks source link

IOS: Not receiving PanResponder events #2332

Open longnk1301 opened 4 days ago

longnk1301 commented 4 days ago

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[bot] commented 4 days ago

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.

github-actions[bot] commented 4 days ago

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?