UISlider 和 UIScrollView

发布于 2024-10-10 12:46:34 字数 307 浏览 8 评论 0原文

我有一个 UISlider 作为视图的一部分,该视图加载到启用了分页的 UIScrollView 中。我注意到一个意想不到的行为。如果用户尝试快速使用滑块(即按下并移动),它将“激活”滚动视图,导致页面切换。但是,如果您按住一秒钟,滑块就会“激活”,然后您就可以调整滑块值。这种行为是不可取的。

当加载到 UIScrollView 中时,使 UISlider 做出响应的最佳方法是什么?我考虑过添加一个“拦截器”视图,该视图只会占用滑块下方的触摸事件,但不确定这是否是最好的方法。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

你爱我像她 2024-10-17 12:46:34

无需在 UIScrollView 端进行命中测试。因为延迟是由 UIScrollView 本身设置的。子类化和实现自定义 hitTest:withEvent: 不会有帮助,因为它仍然会延迟触发。

我花了几个小时寻找一个优雅的解决方案,因为我想在 ios 应用程序切换器中模拟苹果自己的volumeslider。

窍门:

yourScrollView.delaysContentTouches = NO;

不幸的是,这会禁用 UISliders 轨道上的事件,因此对于这部分,您的 UIScrollView 不会触发任何触摸事件,因为它们首先被滑块捕获。

要传递除 UISliders 拇指矩形中的触摸事件以外的触摸事件,您必须子类化 UISlider 并添加以下内容:

// 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];
   }
}

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];
   }
}
无畏 2024-10-17 12:46:34

让您的滚动视图检测滑块占据的区域上的触摸(覆盖 hitTest:withEvent:,您可能需要子类 UIScrollView)。如果检测到对所述区域的触摸,请告诉您的滚动视图立即将触摸传递到滑块。

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.

念﹏祤嫣 2024-10-17 12:46:34

在创建音频播放器时,我遇到了 sliderView 的确切问题,并将其添加到 scrollView 中,每当我触摸 sliderView 时, scrollView 用于获取触摸,而不是 sliderView ,而是 scrollView moving 。为了避免这种情况,当用户触摸 sliderView 时,我禁用了 scrollView 的 srcolling,否则启用滚动。

- (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 ;

}

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 ;

}
你是我的挚爱i 2024-10-17 12:46:34

我的问题是这个问题的超集 - 我在 UITableViewCells 中有 UISliders,整个 UITableView 是 UIScrollView 中的一个页面。滑块对与其他两个的交互造成了严重破坏,并且子类化解决方案不起作用。这是我想出的效果很好的方法:当滑块移动时发送通知,并在此期间启用 UITableView 和 UIScrollView 禁用滚动。请注意下图中:我的滑块是水平的,我的 tableview 是垂直的,我的 UIScrollView 有水平页面。

UIScrollView 中 UITableView 中的 UISlider

UITableViewCell 为以编程方式创建的滑块拾取事件:

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];

出于本教程的目的,我们只关心 touchDown 和 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];
}

现在,我们在 UITableView(请注意,tableview 在 VC 中,但我确信如果您子类化的话这会起作用)

- (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;
}

和 UIScrollView(与上面相同,附在 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;
}

我很想听到一个更优雅的解决方案,但这绝对可以完成工作。当您使用滑块时,表格和滚动视图保持静止,当您在滑块外部单击时,表格视图和滚动视图将按预期移动。另请注意,我可以在此解决方案中使用所有 3 个组件的非子类实例。希望这对某人有帮助!

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.

UISlider in a UITableView in a UIScrollView

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!

哽咽笑 2024-10-17 12:46:34

我想将此作为评论发布,但我的帐户没有代表,所以我不得不发布完整的答案。 user762391 的答案包括 hitTest 覆盖,效果非常好。我只是想补充一点,您可以像这样覆盖 pointInside:withEvent: ,而不是覆盖 hitTest: (保留拇指矩形方法):

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent * _Nullable)event {
    return CGRectContainsPoint([self thumbRect], point)
}

或在 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)
}

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)
}
清浅ˋ旧时光 2024-10-17 12:46:34

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)
        }
    }
}

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)
        }
    }
}
萧瑟寒风 2024-10-17 12:46:34

您可以子类化 UIScrollView 并重写方法 - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view 如下:

- (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;
}

同时设置 yourScrollView.滚动视图的delaysContentTouches = NO; 属性。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文