【问题标题】:iOS - forward all touches through a viewiOS - 通过视图转发所有触摸
【发布时间】:2011-04-19 13:22:23
【问题描述】:

我有一个视图覆盖在许多其他视图之上。我只是使用 overaly 来检测屏幕上的一些触摸,但除此之外,我不希望视图停止下面其他视图的行为,例如滚动视图等。我怎样才能转发所有的触摸通过这个叠加视图?它是 UIView 的子类。

【问题讨论】:

  • 您的问题解决了吗?如果是,那么请接受答案,以便其他人更容易找到解决方案,因为我面临同样的问题。

标签: events uiview touch


【解决方案1】:

我只需要禁用用户交互!

目标-C:

myWebView.userInteractionEnabled = NO;

斯威夫特:

myWebView.isUserInteractionEnabled = false

【讨论】:

  • 这适用于我有按钮子视图的情况。我禁用了子视图上的交互并且按钮起作用了。
  • 在 Swift 4 中,myWebView.isUserInteractionEnabled = false
【解决方案2】:

为了将触摸从覆盖视图传递到下面的视图,请在 UIView 中实现以下方法:

目标-C:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

斯威夫特 5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}

【讨论】:

  • 我有同样的问题,但我想要的是如果我用手势缩放/旋转/移动视图,那么它应该返回是,而对于休息事件它应该返回否,我该如何实现呢?
  • @Minakshi 这是一个完全不同的问题,为此提出一个新问题。
  • 但请注意,这种简单的方法不会让您在相关图层的顶部拥有按钮等!
  • @Minakshi 嗨,您找到问题的答案了吗?
【解决方案3】:

这是一个旧线程,但它出现在搜索中,所以我想我会添加我的 2c。我有一个带有子视图的覆盖 UIView,并且只想拦截触及其中一个子视图的触摸,所以我修改了 PixelCloudSt 的答案

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}

【讨论】:

  • 您需要创建一个自定义 UIView 子类(或任何其他 UIView 子类的子类,例如 UIImageView 或其他)并覆盖此函数。本质上,子类在.h文件中可以有一个完全空的'@interface',上面的函数就是'@implementation'的全部内容。
  • 谢谢,这正是我需要通过 UIView 的空白空间传递触摸,由后面的视图处理。 +100
  • 我只是想在我猜 2 年后再次投票给这个......
【解决方案4】:

@fresidue 答案的改进版本。您可以将此UIView 子类用作透明视图,将触摸传递到其子视图之外。 Objective-C 中的实现:

@interface PassthroughView : UIView

@end

@implementation PassthroughView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

.

Swift 中:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

提示:

假设你有一个大的“支架”面板,后面可能有一个表格视图。您制作了“持有人”面板PassthroughView。它现在可以工作了,您可以“通过”“持有人”滚动表格。

但是!

  1. 在“支架”面板的顶部有一些标签或图标。别忘了,当然这些必须简单地标记为用户交互启用关闭!

  2. 在“持有人”面板的顶部有一些按钮。不要忘记,当然这些必须简单地标记为启用用户交互!

  3. 注意有点令人困惑的是,“持有人”本身——您在上面使用PassthroughView 的视图——必须标记为启用用户交互ON!那是开启!! (否则,PassthroughView 中的代码将永远不会被调用。)

【讨论】:

  • 我已经使用了为 Swift 提供的解决方案。它在 ios 11 上运行,但每次在 iOS 10 上都会崩溃。显示访问错误。有什么想法吗?
  • 你救了我的命,它成功地将触摸传递到下面的 UIViews。
  • 别忘了检查$0.isUserInteractionEnabled
【解决方案5】:

我需要通过 UIStackView 传递触摸。里面的 UIView 是透明的,但 UIStackView 消耗了所有的触摸。这对我有用:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

所有排列的子视图仍会收到触摸,但 UIStackView 本身的触摸会传递到下面的视图(对我来说是 mapView)。

【讨论】:

  • 只是想补充一下,您也可以通过扩展 UIView 来应用它。这是一篇使用完全相同方法的文章,其中更详细地介绍了其他用例:medium.com/@nguyenminhphuc/…
  • UIScrollview 可以吗
【解决方案6】:

我在 UIStackView 上遇到了类似的问题(但可能是任何其他视图)。 我的配置如下:

这是一个经典案例,我有一个需要放置在背景中的容器,侧面有按钮。出于布局目的,我将按钮包含在 UIStackView 中,但现在 stackView 的中间(空)部分拦截了触摸:-(

我所做的是创建 UIStackView 的子类,其中包含定义应该可触摸的子视图的属性。 现在,对侧边按钮(包含在 *viewsWithActiveTouch* 数组中)的任何触摸都将被赋予按钮,而对除了这些视图之外的任何位置的堆栈视图的任何触摸都不会被拦截,因此将传递给堆栈下方的任何内容查看。

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}

【讨论】:

    【解决方案7】:

    如果你想转发触摸的视图不是子视图/超级视图,你可以像这样在你的 UIView 子类中设置一个自定义属性:

    @interface SomeViewSubclass : UIView {
    
        id forwardableTouchee;
    
    }
    @property (retain) id forwardableTouchee;
    

    确保在你的 .m 中合成它:

    @synthesize forwardableTouchee;
    

    然后在您的任何 UIResponder 方法中包含以下内容,例如:

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

    无论您在何处实例化您的 UIView,请将 forwardableTouchee 属性设置为您希望将事件转发到的任何视图:

        SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
        view.forwardableTouchee = someOtherView;
    

    【讨论】:

      【解决方案8】:

      在 Swift 5 中

      class ThroughView: UIView {
          override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
              guard let slideView = subviews.first else {
                  return false
              }
      
              return slideView.hitTest(convert(point, to: slideView), with: event) != nil
          }
      }
      

      【讨论】:

        【解决方案9】:

        看起来即使你在这里也有很多答案,没有一个我需要快速干净的人。 所以我在这里从@fresidue 那里得到了答案,并将其转换为 swift,因为它是现在大多数开发人员想要在这里使用的。

        它解决了我的问题,我有一些带有按钮的透明工具栏,但我希望工具栏对用户不可见,并且触摸事件应该通过。

        isUserInteractionEnabled = false 正如某些人所说,根据我的测试,这不是一个选项。

         override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            for subview in subviews {
                if subview.hitTest(convert(point, to: subview), with: event) != nil {
                    return true
                }
            }
            return false
        }
        

        【讨论】:

          【解决方案10】:

          我在 StackView 中有几个标签,但上面的解决方案并没有取得多大成功,而是使用以下代码解决了我的问题:

              let item = ResponsiveLabel()
          
              // Configure label
          
              stackView.addArrangedSubview(item)
          

          子类化 UIStackView:

          class PassThrouStackView:UIStackView{
              override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
                  for subview in self.arrangedSubviews {
                      let convertedPoint = convert(point, to: subview)
                      let labelPoint = subview.point(inside: convertedPoint, with: event)
                      if (labelPoint){
                          return subview
                      }
          
                  }
                  return nil
              }
          
          }
          

          然后你可以这样做:

          class ResponsiveLabel:UILabel{
              override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
                  // Respond to touch
              }
          }
          

          【讨论】:

            【解决方案11】:

            试试这样的...

            for (UIView *view in subviews)
              [view touchesBegan:touches withEvent:event];
            

            上面的代码,例如在您的 touchesBegan 方法中,会将触摸传递给视图的所有子视图。

            【讨论】:

            • 这种方法 AFAIK 的问题是尝试将触摸转发到 UIKit 框架的类通常没有效果。根据 Apple 的文档,UIKit 框架类并非旨在接收将视图属性设置为其他视图的触摸。所以这种方法主要适用于 UIView 的自定义子类。
            【解决方案12】:

            我试图做的情况是使用嵌套 UIStackView 中的控件构建一个控制面板。一些控件具有 UITextField 的其他控件具有 UIButton 的。此外,还有用于识别控件的标签。我想做的是在控制面板后面放一个“不可见”的大按钮,这样如果用户点击按钮或文本字段之外的区域,我就可以捕捉到并采取行动——主要是在出现文本时关闭任何键盘字段处于活动状态(resignFirstResponder)。但是,点击控制面板中的标签或其他空白区域不会通过。上述讨论有助于我在下面给出答案。

            基本上,我将 UIStackView 子类化并重写了“point(inside:with)”例程,以查找需要触摸的控件类型,并“忽略”我想忽略的标签之类的东西。它还检查 UIStackView 内部,以便可以递归到控制面板结构中。

            代码可能比它应该的更冗长。但它对调试很有帮助,并希望能更清楚地说明例程正在做什么。只需确保在 Interface Builder 中将 UIStackView 的类更改为 PassThruStack。

            class PassThruStack: UIStackView {
            
            override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            
                for view in self.subviews {
                    if !view.isHidden {
                        let isStack = view is UIStackView
                        let isButton = view is UIButton
                        let isText = view is UITextField
                        if isStack || isButton || isText {
                            let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                            if pointInside {
                                return true
                            }
                        }
                    }
                }
                return false
            }
            

            }

            【讨论】:

              【解决方案13】:

              正如@PixelCloudStv 所建议的,如果您想将触摸从一个视图投射到另一个视图,但对这个过程有一些额外的控制 - 子类 UIView

              //标题

              @interface TouchView : UIView
              
              @property (assign, nonatomic) CGRect activeRect;
              
              @end
              

              //实现

              #import "TouchView.h"
              
              @implementation TouchView
              
              #pragma mark - Ovverride
              
              - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
              {
                  BOOL moveTouch = YES;
                  if (CGRectContainsPoint(self.activeRect, point)) {
                      moveTouch = NO;
                  }
                  return moveTouch;
              }
              
              @end
              

              在 interfaceBuilder 中,只需将 View 类设置为 TouchView 并使用您的矩形设置活动矩形。你也可以改变和实现其他逻辑。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-07-08
                • 1970-01-01
                相关资源
                最近更新 更多