callstack / react-native-paper

Material Design for React Native (Android & iOS)
https://reactnativepaper.com
MIT License
12.9k stars 2.09k forks source link

How do I enforce android looking Switch in iOS app? #4232

Closed gidrokolbaska closed 10 months ago

gidrokolbaska commented 10 months ago

As the title says, how do I enforce material ui looking Switch in iOS app? I mean, what's the point of the library if on iOS it still looks like an iOS Switch? :)

lukewalczak commented 10 months ago

There is no option to force the Android switch on iOS. Regarding the docs to the MD2, there is a suggestion to keep the platform specific controls in terms of checkboxes or switches – you can read more there

image
LeulAria commented 3 months ago

As per guidline yeah any way for some one looking solution ios-switch on both android and ios

import * as React from 'react';
import {
  Animated,
  Easing,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';
import PropTypes from 'prop-types';

const ToggleComponent = (props: any) => {
  const animatedValue = new Animated.Value(0);

  const moveToggle = animatedValue.interpolate({
    inputRange: [0, 1],
    outputRange: [0, 22],
  });

  const { isOn, onColor, offColor, style, onToggle, labelStyle, label } = props;

  const color = isOn ? onColor : offColor;

  animatedValue.setValue(isOn ? 0 : 1);

  Animated.timing(animatedValue, {
    toValue: isOn ? 1 : 0,
    duration: 200,
    easing: Easing.elastic(0.9),
    useNativeDriver: false,
  }).start();

  return (
    <View style={styles.container}>
      {!!label && <Text style={[styles.label, labelStyle]}>{label}</Text>}

      <TouchableOpacity onPress={typeof onToggle === "function" && onToggle}>
        <View
          style={[styles.toggleContainer, style, { backgroundColor: color }]}
        >
          <Animated.View
            style={[
              styles.toggleWheelStyle,
              {
                marginLeft: moveToggle,
              },
            ]}
          />
        </View>
      </TouchableOpacity>
    </View>
  );
};

ToggleComponent.propTypes = {
  onColor: PropTypes.string,
  offColor: PropTypes.string,
  label: PropTypes.string,
  onToggle: PropTypes.func,
  style: PropTypes.object,
  isOn: PropTypes.bool.isRequired,
  labelStyle: PropTypes.object,
};

ToggleComponent.defaultProps = {
  onColor: '#000',
  offColor: '#ecf0f1',
  label: '',
  onToggle: () => {},
  style: {},
  isOn: false,
  labelStyle: {},
};

export default ToggleComponent;

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    alignItems: "center",
  },
  toggleContainer: {
    width: 50,
    height: 30,
    marginLeft: 3,
    borderRadius: 15,
    justifyContent: "center",
  },
  label: {
    marginRight: 2,
  },
  toggleWheelStyle: {
    width: 25,
    height: 25,
    backgroundColor: "white",
    borderRadius: 12.5,
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.2,
    shadowRadius: 2.5,
    elevation: 1.5,
  },
});
YahyaBagia commented 2 months ago

Thank you, @LeulAria, for the ToggleComponent.

Here’s a modified version with the following changes and improvements:

import React, { useEffect, useRef, useMemo } from "react";
import {
  Animated,
  Easing,
  StyleSheet,
  View,
  TouchableWithoutFeedback,
} from "react-native";
import { useTheme } from "react-native-paper";

interface ToggleProps {
  value: boolean;
  onValueChange?: (value: boolean) => void;
  disabled?: boolean;
}

const Toggle: React.FC<ToggleProps> = ({
  value = false,
  onValueChange = () => {},
  disabled = false,
}) => {
  const animatedValue = useRef(new Animated.Value(value ? 1 : 0)).current;

  const moveToggle = useMemo(
    () =>
      animatedValue.interpolate({
        inputRange: [0, 1],
        outputRange: [TOGGLE_LEFT_MARGIN, TOGGLE_RIGHT_MARGIN],
      }),
    [animatedValue]
  );

  const { primary: trackColorOn, surfaceDisabled: trackColorOff } =
    useTheme().colors;

  const trackColor = value ? trackColorOn : trackColorOff;
  const opacity = disabled ? 0.5 : 1;

  useEffect(() => {
    Animated.timing(animatedValue, {
      toValue: value ? 1 : 0,
      duration: 200,
      easing: Easing.elastic(0.9),
      useNativeDriver: false,
    }).start();
  }, [value]);

  return (
    <View style={styles.container}>
      <TouchableWithoutFeedback
        onPress={disabled ? undefined : () => onValueChange(!value)}
      >
        <View
          style={[
            styles.toggleContainer,
            { backgroundColor: trackColor, opacity },
          ]}
        >
          <Animated.View
            style={[styles.toggleWheelStyle, { marginLeft: moveToggle }]}
          />
        </View>
      </TouchableWithoutFeedback>
    </View>
  );
};

export default Toggle;

const TOGGLE_LEFT_MARGIN = 3;
const TOGGLE_RIGHT_MARGIN = 22;

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    alignItems: "center",
  },
  toggleContainer: {
    width: 50,
    height: 30,
    marginLeft: TOGGLE_LEFT_MARGIN,
    borderRadius: 15,
    justifyContent: "center",
  },
  toggleWheelStyle: {
    width: 25,
    height: 25,
    borderRadius: 12.5,
    shadowColor: "#000",
    backgroundColor: "white",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.2,
    shadowRadius: 2.5,
    elevation: 1.5,
  },
});