【问题标题】:UIButton inside a view that has a UITapGestureRecognizer具有 UITapGestureRecognizer 的视图中的 UIButton
【发布时间】:2011-03-21 15:15:47
【问题描述】:

我有一个UITapGestureRecognizer 的视图。因此,当我点击视图时,该视图上方会出现另一个视图。这个新视图有三个按钮。当我现在按下这些按钮之一时,我没有得到按钮动作,我只得到点击手势动作。所以我不能再使用这些按钮了。我该怎么做才能让事件通过这些按钮?奇怪的是按钮仍然突出显示。

我不能在收到点击后删除 UITapGestureRecognizer。因为有了它,新视图也可以被删除。表示我想要像全屏视频控件这样的行为

【问题讨论】:

    标签: ios objective-c iphone cocoa-touch uitapgesturerecognizer


    【解决方案1】:

    您可以将您的控制器或视图(无论哪个创建手势识别器)设置为UITapGestureRecognizer 的委托。然后在委托中你可以实现-gestureRecognizer:shouldReceiveTouch:。在您的实现中,您可以测试触摸是否属于您的新子视图,如果是,则指示手势识别器忽略它。类似于以下内容:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        // test if our control subview is on-screen
        if (self.controlSubview.superview != nil) {
            if ([touch.view isDescendantOfView:self.controlSubview]) {
                // we touched our control surface
                return NO; // ignore the touch
            }
        }
        return YES; // handle the touch
    }
    

    【讨论】:

    • 在我的头文件中,我的视图实现了 UIGestoreRegognizerDelegate,并在我的 .m 中添加了上面的代码。水龙头从不进入这个方法,它直接进入我的处理程序。有什么想法吗?
    • @kmehta 您很可能忘记设置 UIGestureRecognizer 委托属性。
    • 这个答案可以概括为处理所有涉及 IBAction 的情况吗?
    • @Martin IBAction 编译为void,因此它不会在运行时留下任何信息以用于检测。但是您可以做的是向上查看视图层次结构,测试UIControl,如果找到任何控件,则返回NO
    • 谢谢,这对我有帮助。如果您的按钮在 UIImageView 中,请确保 imageView 的 userInteractionEnabled 设置为 YES。
    【解决方案2】:

    作为对 Kevin Ballard 回答的跟进,我遇到了同样的问题并最终使用了以下代码:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIButton class]]){
            return NO;
        }
        return YES;
    }
    

    它具有相同的效果,但这将适用于任何视图深度的任何 UIButton(我的 UIButton 有几个视图深度,并且 UIGestureRecognizer 的委托没有对它的引用。)

    【讨论】:

    • 不想在这里做个混蛋,但这确实是肯定的。请使用 Cocoa 约定。 TRUE/FALSE/NULL 用于 CoreFoundation-Layer。
    • 从我读过的内容来看,YES 和 NO 实际上是为了可读性而创建的。可读性是主观的。它源于并非每个人都过度关注的方法和属性命名约定。如果您想严格遵守命名约定,那么可以对任何 NSWhatever 使用 YES 和 NO。但是,我会说,如果您对开发人员进行调查,您会得到不同的意见,其中哪个更具可读性 [button setHidden:YES]; - 或 - [按钮 setHidden:TRUE];
    • 我想我想说的是,如果命名约定的前提是可读性,那么我认为很难否认它在某些情况下是主观的。如果您是纯粹主义者,那么您将无法理解这种说法。
    • 这可能看起来很主观,但经过一段时间的 Cocoa 编程,你真的会进入语义更准确的代码流......经过多年的 Objective-C,“TRUE”现在似乎真的错了,因为它与“隐藏”不太匹配。
    • 您什么时候可以/是否将轻击手势作为 UITouch 事件内的 Touch Up 传递给 UIButton(后者是通过轻击 UIButton 生成的事件)?为已经有 15 个触摸事件与点击手势相关联的按钮创建点击手势识别器似乎是重复且不正确。我想看代码,而不是猜测。
    【解决方案3】:

    here 找到了另一种方法。它检测触摸是否在每个按钮内。

    (1)pointInside:withEvent: (2)locationInView:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
           shouldReceiveTouch:(UITouch *)touch {
        // Don't recognize taps in the buttons
        return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
                ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
                ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
    }
    

    【讨论】:

    • 我为此尝试了所有其他解决方案,但 -pointInside:withEvent: 方法是唯一对我有用的方法。
    【解决方案4】:

    作为凯西对凯文巴拉德回答的后续跟进:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
            if ([touch.view isKindOfClass:[UIControl class]]) {
                // we touched a button, slider, or other UIControl
                return NO; // ignore the touch
            }
        return YES; // handle the touch
    }
    

    这基本上使按钮、滑块等所有用户输入类型的控件都能正常工作

    【讨论】:

    • 这是一个灵活的解决方案,可以与setCancelsTouchesInView = NO 一起使用,以在与控件交互时不触发点击手势。但是你可以写得更好:return ![touch.view isKindOfClass:[UIControl class]];
    • Swift 4+ 解决方案:return !(touch.view is UIControl)
    【解决方案5】:

    在这里找到这个答案:link

    你也可以使用

    tapRecognizer.cancelsTouchesInView = NO;
    

    这会阻止点击识别器成为唯一捕获所有点击的识别器

    更新 - Michael 提到了描述此属性的文档的链接:cancelsTouchesInView

    【讨论】:

    • 这应该是 IMO 接受的答案。这让每个子视图都可以自己处理水龙头,并防止附加了 tabrecognizer 的视图“劫持”水龙头。如果您想要做的只是使视图可点击(辞职第一响应者/隐藏文本字段上的键盘等),则无需实现委托......太棒了!
    • 这绝对应该是公认的答案。这个答案让我正确地阅读了Apple documentation,这清楚地表明手势识别器将阻止子视图获取识别的事件除非你这样做。
    • 这曾经可以工作,但现在对我不起作用..也许是因为我在按钮下有一个滚动视图?我必须像上面的答案那样实现委托。
    • 经过一些试验,我注意到只有当包含手势识别器的视图是 UIControl 的同级时,这才有效,如果视图是 UIControl 的父级,则不会。
    • 没有真正按预期工作......是的,它可以防止点击识别器吃掉 UIControl 上的事件。但它仍然运行识别器动作,同时运行 2 个动作。
    【解决方案6】:

    您可以通过设置以下布尔值来阻止 UITapGestureRecognizer 取消其他事件(例如点击按钮):

        [tapRecognizer setCancelsTouchesInView:NO];
    

    【讨论】:

      【解决方案7】:

      如果你的场景是这样的:

      您有一个简单的视图和一些 UIButtons、UITextField 控件作为子视图添加到该视图。现在,当您触摸视图上除控件(您添加的子视图)之外的任何其他位置时,您想要关闭键盘

      那么解决方法是:

      将以下方法添加到您的 XYZViewController.m(包含您的视图)

      - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
      {
          [self.view endEditing:YES];
      }
      

      【讨论】:

      • 首先我尝试使用 @cdasher 的答案,但在我的情况下,即使点击按钮,我也会将 touch.view 作为我的“视图” " 在 ViewController 但不是我的 "UIButton" 控件。有人可以说为什么触摸的视图属性没有返回发生点击的原始视图
      • 如果您有滚动视图或其他声称触摸事件的视图,则将无法工作。
      【解决方案8】:

      这些答案不完整。我不得不阅读多篇关于如何使用这个布尔运算的帖子。

      在你的 *.h 文件中添加这个

      @interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>
      

      在你的 *.m 文件中添加这个

      - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
      
          NSLog(@"went here ...");
      
          if ([touch.view isKindOfClass:[UIControl class]])
          {
              // we touched a button, slider, or other UIControl
              return NO; // ignore the touch
          }
          return YES; // handle the touch
      }
      - (void)viewDidLoad
      {
          [super viewDidLoad];
          // Do any additional setup after loading the view, typically from a nib.
      
      
      
          //tap gestrure
          UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
          [tapGestRecog setNumberOfTapsRequired:1];
          [self.view addGestureRecognizer:tapGestRecog];
      
      
      // This line is very important. if You don't add it then your boolean operation will never get called
      tapGestRecog.delegate = self;
      
      }
      
      
      -(IBAction) screenTappedOnce
      {
          NSLog(@"screenTappedOnce ...");
      
      }
      

      【讨论】:

        【解决方案9】:

        在 iOS 6.0 及更高版本中,默认控制操作可防止手势识别器行为重叠。例如,按钮的默认操作是单击。如果您将单击手势识别器附加到按钮的父视图,并且用户点击按钮,则按钮的操作方法会接收触摸事件而不是手势识别器。这仅适用于与控件的默认操作重叠的手势识别,其中包括:.....

        From Apple's API doc

        【讨论】:

          【解决方案10】:

          优化 cdasher 的答案,你得到

          - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
                 shouldReceiveTouch:(UITouch *)touch 
          {
              return ![touch.view isKindOfClass:[UIControl class]];
          }
          

          【讨论】:

            【解决方案11】:

            这是对我有用的Lily Ballard's answer 的 Swift 版本:

            func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
                if (scrollView.superview != nil) {
                    if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
                }
                return true
            }
            

            【讨论】:

              【解决方案12】:

              斯威夫特 5

              带有轻触的超级视图上的按钮

               func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
                  if let _ = touch.view as? UIButton { return false }
                  return true
              }
              

              就我而言,实施 hitTest 对我有用。我有按钮的收藏视图

              该方法通过调用每个子视图的point(inside:with:)方法来遍历视图层次结构,以确定哪个子视图应该接收触摸事件。如果point(inside:with:) 返回true,则子视图的层次结构将类似地遍历,直到找到包含指定点的最前面的视图。

              override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
                  guard isUserInteractionEnabled else { return nil }
              
                  guard !isHidden else { return nil }
              
                  guard alpha >= 0.01 else { return nil }
              
                  guard self.point(inside: point, with: event) else { return nil }
              
                  for eachImageCell in collectionView.visibleCells {
                      for eachImageButton in eachImageCell.subviews {
                          if let crossButton = eachImageButton as? UIButton {
                              if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                                  return crossButton
                              }
                          }
                      }
                  }
                  return super.hitTest(point, with: event)
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2012-09-24
                • 1970-01-01
                • 1970-01-01
                • 2021-05-16
                • 2023-03-27
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多