Skip to content

Commit ce12692

Browse files
Add tapToSeek prop, fix unintentional scrolling, call onValueChanged when tapping (#233)
* Fixes on iOS for #156, #212 and maybe #147. Fix problem on iOS where slider was unintentionally reacting to scrolling as slider taps (#156). Removing beginTrackingWithTouch which may help us play nicer with gesture responders (#147). Tapping now calls onValueChanged (#212). * Added "tapToSeek" property for iOS (#211). Added property to optionally enable tapToSeek on iOS (#211). Chose to leave default behaviour for Android and Windows as most complaints seem to be about disabling this on iOS. * Fixed a prettier error and added tapToSeek prop top test result snapshots.
1 parent f7b3e6f commit ce12692

File tree

7 files changed

+72
-31
lines changed

7 files changed

+72
-31
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Check out the [example project](example) for more examples.
8282
- [`testID`](#testid)
8383
- [`value`](#value)
8484
- [`inverted`](#inverted)
85+
- [`tapToSeek`](#tapToSeek)
8586
- [`vertical`](#vertical)
8687
- [`thumbTintColor`](#thumbtintcolor)
8788
- [`maximumTrackImage`](#maximumtrackimage)
@@ -214,6 +215,15 @@ _This is not a controlled component_, you don't need to update the value during
214215

215216
---
216217

218+
### `tapToSeek`
219+
Permits tapping on the slider track to set the thumb position. Defaults to false on iOS. No effect on Android or Windows.
220+
221+
| Type | Required | Platform |
222+
| ---- | -------- | -------- |
223+
| bool | No | iOS |
224+
225+
---
226+
217227
### `inverted`
218228
Reverses the direction of the slider. Default value is false.
219229

example/SliderExample.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ export const examples = [
131131
},
132132
},
133133
{
134-
title: 'step: 0.25',
134+
title: 'step: 0.25, tap to seek on iOS',
135135
render(): Element<any> {
136-
return <SliderExample step={0.25} />;
136+
return <SliderExample step={0.25} tapToSeek={true} />;
137137
},
138138
},
139139
{

src/ios/RNCSlider.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
@property (nonatomic, strong) UIImage *minimumTrackImage;
2323
@property (nonatomic, strong) UIImage *maximumTrackImage;
2424
@property (nonatomic, strong) UIImage *thumbImage;
25+
@property (nonatomic, assign) bool tapToSeek;
2526
@property (nonatomic, strong) NSString *accessibilityUnits;
2627
@property (nonatomic, strong) NSArray *accessibilityIncrements;
2728

src/ios/RNCSlider.m

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,11 @@
1010
@implementation RNCSlider
1111
{
1212
float _unclippedValue;
13-
UITapGestureRecognizer * tapGesturer;
1413
}
1514

1615
- (instancetype)initWithFrame:(CGRect)frame
1716
{
18-
self = [super initWithFrame:frame];
19-
if (self) {
20-
tapGesturer = [[UITapGestureRecognizer alloc] initWithTarget: self action:@selector(tapHandler:)];
21-
[tapGesturer setNumberOfTapsRequired: 1];
22-
[self addGestureRecognizer:tapGesturer];
23-
}
24-
return self;
25-
}
26-
27-
- (void)tapHandler:(UITapGestureRecognizer *)gesture {
28-
CGPoint touchPoint = [gesture locationInView:self];
29-
float rangeWidth = self.maximumValue - self.minimumValue;
30-
float sliderPercent = touchPoint.x / self.bounds.size.width;
31-
[self setValue:self.minimumValue + (rangeWidth * sliderPercent) animated: YES];
17+
return [super initWithFrame:frame];
3218
}
3319

3420
- (void)setValue:(float)value
@@ -136,9 +122,4 @@ - (void)setInverted:(BOOL)inverted
136122
}
137123
}
138124

139-
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
140-
{
141-
return YES;
142-
}
143-
144125
@end

src/ios/RNCSliderManager.m

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#import <React/RCTEventDispatcher.h>
1212
#import "RNCSlider.h"
1313
#import <React/UIView+React.h>
14+
#import "os/log.h"
1415

1516
@implementation RNCSliderManager
1617

@@ -27,26 +28,69 @@ - (UIView *)view
2728
forControlEvents:(UIControlEventTouchUpInside |
2829
UIControlEventTouchUpOutside |
2930
UIControlEventTouchCancel)];
31+
32+
UITapGestureRecognizer *tapGesturer;
33+
tapGesturer = [[UITapGestureRecognizer alloc] initWithTarget: self action:@selector(tapHandler:)];
34+
[tapGesturer setNumberOfTapsRequired: 1];
35+
[slider addGestureRecognizer:tapGesturer];
36+
3037
return slider;
3138
}
3239

33-
static void RNCSendSliderEvent(RNCSlider *sender, BOOL continuous, BOOL isSlidingStart)
34-
{
35-
float value = sender.value;
40+
- (void)tapHandler:(UITapGestureRecognizer *)gesture {
41+
// Bail out if the source view of the gesture isn't an RNCSlider.
42+
if ([gesture.view class] != [RNCSlider class]) {
43+
return;
44+
}
45+
RNCSlider *slider = (RNCSlider *)gesture.view;
46+
47+
if (!slider.tapToSeek) {
48+
return;
49+
}
50+
51+
CGPoint touchPoint = [gesture locationInView:slider];
52+
float rangeWidth = slider.maximumValue - slider.minimumValue;
53+
float sliderPercent = touchPoint.x / slider.bounds.size.width;
54+
float value = slider.minimumValue + (rangeWidth * sliderPercent);
55+
56+
[slider setValue:discreteValue(slider, value) animated: YES];
57+
58+
// Trigger onValueChange to address https://github.com/react-native-community/react-native-slider/issues/212
59+
if (slider.onRNCSliderValueChange) {
60+
slider.onRNCSliderValueChange(@{
61+
@"value": @(slider.value),
62+
});
63+
}
3664

37-
if (sender.step > 0 &&
38-
sender.step <= (sender.maximumValue - sender.minimumValue)) {
65+
if (slider.onRNCSliderSlidingComplete) {
66+
slider.onRNCSliderSlidingComplete(@{
67+
@"value": @(slider.value),
68+
});
69+
}
70+
}
3971

40-
value =
72+
static float discreteValue(RNCSlider *sender, float value) {
73+
// If step is set and less than or equal to difference between max and min values,
74+
// pick the closest discrete multiple of step to return.
75+
if (sender.step > 0 && sender.step <= (sender.maximumValue - sender.minimumValue)) {
76+
return
4177
MAX(sender.minimumValue,
4278
MIN(sender.maximumValue,
43-
sender.minimumValue + round((sender.value - sender.minimumValue) / sender.step) * sender.step
79+
sender.minimumValue + round((value - sender.minimumValue) / sender.step) * sender.step
4480
)
4581
);
46-
47-
[sender setValue:value animated:NO];
4882
}
4983

84+
// Otherwise, leave value unchanged.
85+
return value;
86+
}
87+
88+
static void RNCSendSliderEvent(RNCSlider *sender, BOOL continuous, BOOL isSlidingStart)
89+
{
90+
float value = discreteValue(sender, sender.value);
91+
92+
[sender setValue:value animated:NO];
93+
5094
if (continuous) {
5195
if (sender.onRNCSliderValueChange && sender.lastValue != value) {
5296
sender.onRNCSliderValueChange(@{
@@ -99,6 +143,7 @@ - (void)sliderTouchEnd:(RNCSlider *)sender
99143
RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor);
100144
RCT_EXPORT_VIEW_PROPERTY(thumbImage, UIImage);
101145
RCT_EXPORT_VIEW_PROPERTY(inverted, BOOL);
146+
RCT_EXPORT_VIEW_PROPERTY(tapToSeek, BOOL);
102147
RCT_EXPORT_VIEW_PROPERTY(accessibilityUnits, NSString);
103148
RCT_EXPORT_VIEW_PROPERTY(accessibilityIncrements, NSArray);
104149

src/js/Slider.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ SliderWithRef.defaultProps = {
309309
maximumValue: 1,
310310
step: 0,
311311
inverted: false,
312+
tapToSeek: false,
312313
};
313314

314315
let styles;

src/js/__tests__/__snapshots__/Slider.test.js.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ exports[`<Slider /> renders a slider with custom props 1`] = `
2121
"height": 40,
2222
}
2323
}
24+
tapToSeek={false}
2425
thumbImage={null}
2526
thumbTintColor="green"
2627
value={0.5}
@@ -46,6 +47,7 @@ exports[`<Slider /> renders disabled slider 1`] = `
4647
"height": 40,
4748
}
4849
}
50+
tapToSeek={false}
4951
thumbImage={null}
5052
value={0}
5153
/>
@@ -70,6 +72,7 @@ exports[`<Slider /> renders enabled slider 1`] = `
7072
"height": 40,
7173
}
7274
}
75+
tapToSeek={false}
7376
thumbImage={null}
7477
value={0}
7578
/>

0 commit comments

Comments
 (0)