Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ To use this library you need to ensure you are using the correct version of Reac
| `testID` | Used to locate this view in UI automation tests. | string | |
| `value` | Write-only property representing the value of the slider. Can be used to programmatically control the position of the thumb. Entered once at the beginning still acts as an initial value. Changing the value programmatically does not trigger any event.<br/>The value should be between minimumValue and maximumValue, which default to 0 and 1 respectively. Default value is 0.<br/>_This is not a controlled component_, you don't need to update the value during dragging. | number | |
| `tapToSeek` | Permits tapping on the slider track to set the thumb position.<br/>Defaults to false on iOS. No effect on Android or Windows. | bool | iOS |
| `swipeToSeek` | Permits swiping on the slider track to set the thumb position.<br/>Defaults to false on iOS. On Android this is the default behaviour. | bool | iOS |
| `inverted` | Reverses the direction of the slider.<br/>Default value is false. | bool | |
| `vertical` | Changes the orientation of the slider to vertical, if set to `true`.<br/>Default value is false. | bool | Windows |
| `thumbTintColor` | Color of the foreground switch grip.<br/>**NOTE:** This prop will override the `thumbImage` prop set, meaning that if both `thumbImage` and `thumbTintColor` will be set, image used for the thumb may not be displayed correctly! | [color](https://reactnative.dev/docs/colors) | Android |
Expand Down
6 changes: 6 additions & 0 deletions example/src/Examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,12 @@ export const examples: Props[] = [
return <SliderExample step={0.25} tapToSeek={true} />;
},
},
{
title: 'step: 0.25, tap & swipe to seek on iOS',
render(): React.ReactElement {
return <SliderExample step={0.25} tapToSeek={true} swipeToSeek={true} />;
},
},
{
title: 'Limit on positive values [30, 80]',
render() {
Expand Down
6 changes: 6 additions & 0 deletions example/src/Props.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ export const propsExamples: Props[] = [
return <SliderExample tapToSeek={true} />;
},
},
{
title: 'swipeToSeek',
render(): React.ReactElement {
return <SliderExample swipeToSeek={true} />;
},
},
{
title: 'inverted',
render() {
Expand Down
76 changes: 76 additions & 0 deletions package/ios/RNCSliderComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ @implementation RNCSliderComponentView
RNCSlider *slider;
UIImage *_image;
BOOL _isSliding;
BOOL _swipeGestureEnabled;
}

+ (ComponentDescriptorProvider)componentDescriptorProvider
Expand Down Expand Up @@ -202,6 +203,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
if (oldScreenProps.tapToSeek != newScreenProps.tapToSeek) {
slider.tapToSeek = newScreenProps.tapToSeek;
}
if (oldScreenProps.swipeToSeek != newScreenProps.swipeToSeek) {
[self setSwipeToSeek:newScreenProps.swipeToSeek];
}
if (oldScreenProps.minimumValue != newScreenProps.minimumValue) {
[slider setMinimumValue:newScreenProps.minimumValue];
}
Expand Down Expand Up @@ -298,6 +302,78 @@ - (void)setInverted:(BOOL)inverted
}
}

#pragma mark - Swipe to seek

- (void)setSwipeToSeek:(BOOL)swipeToSeek
{
if (swipeToSeek && !_swipeGestureEnabled) {
UIPanGestureRecognizer *panGesturer;
panGesturer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandler:)];
[slider addGestureRecognizer:panGesturer];
_swipeGestureEnabled = YES;
}
}

- (void)panHandler:(UIPanGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:slider];

switch (gesture.state) {

case UIGestureRecognizerStateBegan: {
[self updateSliderToLocation:location];
std::dynamic_pointer_cast<const RNCSliderEventEmitter>(_eventEmitter)
->onRNCSliderSlidingStart(RNCSliderEventEmitter::OnRNCSliderSlidingStart{.value = static_cast<Float>(slider.lastValue)});
break;
}

case UIGestureRecognizerStateChanged: {
[self updateSliderToLocation:location];
break;
}

case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed: {
std::dynamic_pointer_cast<const RNCSliderEventEmitter>(_eventEmitter)
->onRNCSliderSlidingComplete(RNCSliderEventEmitter::OnRNCSliderSlidingComplete{.value = static_cast<Float>(slider.value)});
break;
}

default:
break;
}
}

- (void)updateSliderToLocation:(CGPoint)location {
float newValue = [self calculateSliderValueFromLocation:location];
float discreteValue = [slider discreteValue:newValue];

[slider setValue:newValue animated:NO];

if (discreteValue != slider.lastValue) {
std::dynamic_pointer_cast<const RNCSliderEventEmitter>(_eventEmitter)
->onRNCSliderValueChange(RNCSliderEventEmitter::OnRNCSliderValueChange{.value = static_cast<Float>(slider.value)});
}

slider.lastValue = discreteValue;
}

- (float)calculateSliderValueFromLocation:(CGPoint)point {
CGFloat sliderWidth = slider.bounds.size.width;

if (sliderWidth <= 0) {
return slider.value;
}

CGFloat percentage = point.x / sliderWidth;
percentage = MIN(1.0, MAX(0.0, percentage));

CGFloat range = slider.maximumValue - slider.minimumValue;
float newValue = slider.minimumValue + (percentage * range);

return newValue;
}

@end

Class<RCTComponentViewProtocol> RNCSliderCls(void)
Expand Down
1 change: 1 addition & 0 deletions package/src/RNCSliderNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface NativeProps extends ViewProps {
inverted?: WithDefault<boolean, false>;
vertical?: WithDefault<boolean, false>;
tapToSeek?: WithDefault<boolean, false>;
swipeToSeek?: WithDefault<boolean, false>;
maximumTrackImage?: ImageSource;
maximumTrackTintColor?: ColorValue;
maximumValue?: Double;
Expand Down
8 changes: 8 additions & 0 deletions package/src/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ type IOSProps = Readonly<{
* Defaults to false on iOS. No effect on Android or Windows.
*/
tapToSeek?: boolean;

/**
* Permits swiping on the slider track to set the thumb position.
* Defaults to false on iOS. This is the default behaviour on Android.
*/
swipeToSeek?: boolean;
}>;

type Props = ViewProps &
Expand Down Expand Up @@ -211,6 +217,7 @@ const SliderComponent = (
step = 0,
inverted = false,
tapToSeek = false,
swipeToSeek = false,
lowerLimit = Platform.select({
web: minimumValue,
default: constants.LIMIT_MIN_VALUE,
Expand Down Expand Up @@ -310,6 +317,7 @@ const SliderComponent = (
step={step}
inverted={inverted}
tapToSeek={tapToSeek}
swipeToSeek={swipeToSeek}
value={passedValue}
lowerLimit={lowerLimit}
upperLimit={upperLimit}
Expand Down
6 changes: 6 additions & 0 deletions package/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export interface SliderPropsIOS extends ReactNative.ViewProps {
*/
tapToSeek?: boolean;

/**
* Permits swiping on the slider track to set the thumb position.
* Defaults to false on iOS. This is the default behaviour on Android.
*/
swipeToSeek?: boolean;

/**
* Sets an image for the thumb. Only static images are supported.
*/
Expand Down