iOS-通过视图转发所有触摸


141

我有一个视图叠加在许多其他视图之上。我仅使用过分检测屏幕上的某些触摸,但除此之外,我不希望该视图停止其下的其他视图(例如滚动视图)的行为。我如何转发所有触摸此叠加视图?它是UIView的子目录。


2
您解决了问题吗?如果是,那么请接受答案,以便其他人更容易找到解决方案,因为我面临着同一问题。
库舒德赛

Answers:


121

我只需要禁用用户交互即可!

目标C:

myWebView.userInteractionEnabled = NO;

迅速:

myWebView.isUserInteractionEnabled = false

在我有按钮子视图的情况下,此方法有效。我禁用了子视图上的交互,并且按钮起作用了。
simple_code

2
在Swift 4中,myWebView.isUserInteractionEnabled = false
Louis'LYRO'Dupont

117

要将触摸从叠加视图传递到下面的视图,请在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
}

3
我有同样的问题,但是我想要的是如果我正在用手势缩放/旋转/移动视图,那么它应该返回yes,而对于休息事件它应该返回no,我该如何实现?
Minakshi

@Minakshi是一个完全不同的问题,为此提出一个新问题。
Fattie

但是请注意,这种简单的方法不会让您拥有按钮等在所讨论图层的顶部!
Fattie

56

这是一个旧线程,但是在搜索时出现了,因此我想添加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;
}

1
您需要创建一个自定义UIView子类(或任何其他UIView子类的子类,例如UIImageView或其他),并重写此函数。本质上,子类在.h文件中可以具有一个完全为空的'@interface',并且上面的函数是'@implementation'的全部内容。
fresidue 2014年

谢谢,这正是我需要通过UIView的空白区域进行触摸操作,并由后面的视图处理。+100
丹尼

40

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

小费:

假设您有一个大的“ holder”面板,也许后面有一个表格视图。您创建“持有人”面板PassthroughView。现在它将起作用,您可以在“持有人”中滚动表。

但!

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

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

  3. 注意的是有点混乱,在“持有人”本身-您使用的视图PassthroughView上-必须标明用户交互启用ON!这是ON!(否则,将永远不会调用PassthroughView中的代码。)


1
我已经使用了Swift给出的解决方案。它在ios 11上运行,但每次在iOS 10上都崩溃。显示访问错误。任何想法?
Soumen

您节省了我的时间,它可以将触摸传递给下面的UIView。
AaoIi

1
别忘了也要检查$0.isUserInteractionEnabled
Sean Thielen '18

7

我有一个类似的问题与UIStackView(但可以是任何其他视图)。我的配置如下: 具有containerView和StackView的View控制器

在经典情况下,我有一个需要放置在背景中且侧面带有按钮的容器。出于布局目的,我将按钮包括在UIStackView中,但是现在stackView的中间(空)部分截取了触摸:-(

我所做的是创建一个UIStackView的子类,该类具有定义应该可触摸的subView的属性。现在,对侧面按钮(包含在* 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
   }
}

6

如果您要将触摸转发到的视图并非恰好是子视图/超级视图,则可以在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;

4

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

4

我需要通过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
    }
}

所有ArrangementSubviews仍然会收到触摸,但是UIStackView本身的触摸进入了下面的视图(对我来说是mapView)。


2

看起来,即使您在这里有很多答案,也没有我需要的一切。所以我从这里的@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
}

1

我在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
    }
}

0

试试这样的东西...

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

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


6
这种方法AFAIK的问题在于,尝试将触摸转发到UIKit框架的类通常不会产生任何效果。根据Apple的文档,UIKit框架类不适用于接收将view属性设置为其他视图的触摸。因此,这种方法主要适用于UIView的自定义子类。
maciejs 2011年

0

我试图做的情况是使用嵌套的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
}

}


-1

正如@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并使用rect设置active rect。您也可以更改和实现其他逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.