【问题标题】:UISlider and UIScrollViewUISlider 和 UIScrollView
【发布时间】:2011-06-03 18:31:27
【问题描述】:

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

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

【问题讨论】:

    标签: iphone uiscrollview uislider


    【解决方案1】:

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

    【讨论】:

    • 感谢 Sebastian,这正是我想要的!
    【解决方案2】:

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

    【讨论】:

    • 谢谢,最终同意了。然而,另一个奇怪的......这种行为似乎只在模拟器上表现出来。一旦我在设备上安装它,它就可以在没有修复的情况下正常工作。
    • FWIW,我在模拟器(iOS 6)和设备(iOS 5.1)上看到完全相同的行为(如原始问题中所述)。在拖动滑块之前我必须点击并按住片刻,否则我会得到滚动视图拖动。
    【解决方案3】:

    在创建音频播放器时,我遇到了 sliderView 的确切问题,它被添加到 scrollView 中,每当我触摸 sliderView 时,scrollView 用来获得触摸,而不是sliderViewscrollView 移动了。为避免这种情况,当用户触摸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 ;
    
    }
    

    【讨论】:

    • 你在哪里添加了这个方法?
    【解决方案4】:

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

    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 个组件的非子类实例。希望这对某人有帮助!

    【讨论】:

      【解决方案5】:

      我想将此作为评论发布,但我的帐户没有代表,因此我不得不发布完整的答案。 user762391 的答案,包括 hitTest 覆盖工作完美。我只想添加它而不是覆盖hitTest,您可以像这样覆盖pointInside:withEvent:(保留thumbRect方法):

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

      【讨论】:

      • 非常感谢,杰夫。在过去的 2 个小时里,我一直在尝试解决这个问题。你的解决方案对我有用。我只需要为 Swift 5 更新它。 ) -> Bool { return thumbRect.contains(point) }
      【解决方案6】:

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

      【讨论】:

      • 谢谢它实际上为我节省了转换目标 - swift中的c代码
      【解决方案7】:

      您可以继承 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; 属性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-24
        • 2011-01-28
        相关资源
        最近更新 更多