可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a UISlider
as part of a view that is loaded into a UIScrollView
with paging enabled. I've noticed an unexpected behavior. If the user tries to use the slider quickly (i.e. press and move) it "activates" the scroll view, causing the page to switch. However, if your press and hold for a second the slider "activates" and you can then adjust the slider value. This behavior is undesirable.
What is the best way to make the UISlider
responsive when loaded into a UIScrollView
? I've thought about adding a "blocker" view that just eats up touch events that is placed under the slider, but not sure if this is the best way to go about it.
回答1:
Have your scroll view detect touches over the region occupied by your slider (override hitTest:withEvent:
, you may need to subclass UIScrollView
). If a touch over said region is detected, tell your scroll view to immediately pass the touch to the slider.
回答2:
there's no need for a hit test on the UIScrollView
side. since the delay is set by the UIScrollView
itself. subclassing and implementing a custom hitTest:withEvent:
won't help since it's still triggered with delay.
i searched hours for an elegant solution to this, since i wanted to simulate apple's own volumeslider in the ios application switcher.
the trick:
yourScrollView.delaysContentTouches = NO;
unfortunately this disables events along the UISliders
track, so for this part your UIScrollView
won't trigger any touchevents because they are caught by the slider first.
to pass touchevents other than those which are in the UISliders
thumb rect you have to subclass UISlider
and add the following:
// get the location of the thumb
- (CGRect)thumbRect
{
CGRect trackRect = [self trackRectForBounds:self.bounds];
CGRect thumbRect = [self thumbRectForBounds:self.bounds
trackRect:trackRect
value:self.value];
return thumbRect;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect thumbFrame = [self thumbRect];
// check if the point is within the thumb
if (CGRectContainsPoint(thumbFrame, point))
{
// if so trigger the method of the super class
NSLog(@"inside thumb");
return [super hitTest:point withEvent:event];
}
else
{
// if not just pass the event on to your superview
NSLog(@"outside thumb");
return [[self superview] hitTest:point withEvent:event];
}
}
回答3:
While creating an audio player I had the exact problem with a sliderView
and it is added to a scrollView
, and whenever I touched the sliderView
, the scrollView
used to get the touch and instead of the sliderView
, the scrollView
moved . To avoid this , I disabled the srcolling of the scrollView
when the user touched the sliderView
and otherwise the scrolling is enabled .
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = [super hitTest:point withEvent:event] ;
if (result == sliderView)
{
scrollView.scrollEnabled = NO ;
}
else
{
scrollView.scrollEnabled = YES ;
}
return result ;
}
回答4:
most upvoted comment code in Swift 3 :)
import Foundation
class UISliderForScrollView:UISlider {
var thumbRect:CGRect {
let trackRect = self.trackRect(forBounds: bounds)
return thumbRect(forBounds: bounds, trackRect: trackRect, value: value)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if thumbRect.contains(point) {
return super.hitTest(point, with: event)
} else {
return superview?.hitTest(point, with: event)
}
}
}
回答5:
My problem was a superset of this issue - I've got UISliders inside UITableViewCells and the whole UITableView is a page inside a UIScrollView. The sliders were wreaking havoc on the interactions with the other two and the subclassing solutions did not work. Here's what I came up with that's working great: send notifications when the sliders move and have the UITableView and UIScrollView disableScrolling on during this time. Note in the picture below: my sliders are horizontal, my tableview is vertical, and my UIScrollView has horizontal pages.

The UITableViewCell picks up events for the programmatically-created slider:
self.numberSlider = [[UISlider alloc] init];
[self.numberSlider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[self.numberSlider addTarget:self action:@selector(sliderTouchDown:) forControlEvents:UIControlEventTouchDown];
[self.numberSlider addTarget:self action:@selector(sliderTouchUp:) forControlEvents:UIControlEventTouchUpInside];
[self.numberSlider addTarget:self action:@selector(sliderTouchUp:) forControlEvents:UIControlEventTouchUpOutside];
For the purposes of this tutorial, we only care about touchDown and Up:
- (void)sliderTouchDown:(UISlider *)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFY_SLIDER_TOUCH_BEGAN object:nil];
}
- (void)sliderTouchUp:(UISlider *)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFY_SLIDER_TOUCH_ENDED object:nil];
}
Now, we catch in these notifications in both the UITableView (note that the tableview is in a VC but I'm sure this would work if you subclassed):
- (void)viewDidLoad
{
// other stuff
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sliderTouchDown:) name:NOTIFY_SLIDER_TOUCH_BEGAN object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sliderTouchUp:) name:NOTIFY_SLIDER_TOUCH_ENDED object:nil];
}
- (void)sliderTouchDown:(NSNotification *)notify
{
self.treatmentTableView.scrollEnabled = NO;
}
- (void)sliderTouchUp:(NSNotification *)notify
{
self.treatmentTableView.scrollEnabled = YES;
}
and the UIScrollView (same as above, enclosed in a VC):
- (void)viewDidLoad
{
// other stuff
// Register for slider notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(disableScrolling:) name:NOTIFY_SLIDER_TOUCH_BEGAN object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enableScrolling:) name:NOTIFY_SLIDER_TOUCH_ENDED object:nil];
}
- (void)disableScrolling:(NSNotification *)notify
{
self.scrollView.scrollEnabled = NO;
}
- (void)enableScrolling:(NSNotification *)notify
{
self.scrollView.scrollEnabled = YES;
}
I'd love to hear a more elegant solution, but this one definitely gets the job done. When you use a slider, the table and scrollviews hold still and when you click outside the sliders, the tableview and scrollviews move as expected. Also - notice that I could use non-subclassed instances of all 3 components in this solution. Hope this helps someone!
回答6:
I wanted to post this as a comment, but my account has no rep so I had to post a whole answer. user762391's answer including the hitTest override works perfectly. I just want to add that instead of overriding hitTest, you could instead override pointInside:withEvent: like this (keeping the thumbRect method):
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent * _Nullable)event {
return CGRectContainsPoint([self thumbRect], point)
}
or in Swift:
var thumbRect: CGRect {
return thumbRectForBounds(bounds, trackRect: trackRectForBounds(bounds), value: value)
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return thumbRect.contains(point)
}
回答7:
You can subclass UIScrollView and override the method - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
as follows:
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
if ([view isKindOfClass:[UISlider class]]) {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:view];
CGRect thumbRect;
UISlider *mySlide = (UISlider*) view;
CGRect trackRect = [mySlide trackRectForBounds:mySlide.bounds];
thumbRect = [mySlide thumbRectForBounds:mySlide.bounds trackRect:trackRect value:mySlide.value];
if (CGRectContainsPoint(thumbRect, location))
return YES;
}
return NO;
}
Also Set yourScrollView.delaysContentTouches = NO;
property for the scrollview.