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

发布于 2024-09-25 14:36:14 字数 117 浏览 0 评论 0原文

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

I have a view overlayed on top of many other views. I am only using the overaly to detect some number of touches on the screen, but other than that I don't want the view to stop the behavior of other views underneath, which are scrollviews, etc. How can I forward all the touches through this overlay view? It is a subclass of UIView.

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

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

发布评论

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

评论(13

若沐 2024-10-02 14:36:14

我所需要的就是禁用用户交互!

Objective-C:

myWebView.userInteractionEnabled = NO;

斯威夫特:

myWebView.isUserInteractionEnabled = false

Disabling user interaction was all I needed!

Objective-C:

myWebView.userInteractionEnabled = NO;

Swift:

myWebView.isUserInteractionEnabled = false
黯然 2024-10-02 14:36:14

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

Objective-C:

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

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

For passing touches from an overlay view to the views underneath, implement the following method in the UIView:

Objective-C:

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

Swift 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
}
网白 2024-10-02 14:36:14

这是一个旧线程,但它是在搜索中出现的,所以我想我应该添加我的 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;
}

This is an old thread, but it came up on a search, so I thought I'd add my 2c. I have a covering UIView with subviews, and only want to intercept the touches that hit one of the subviews, so I modified PixelCloudSt's answer to:

-(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;
}
仅冇旳回忆 2024-10-02 14:36:14

@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!这是开启! (否则,PassthroughView 中的代码将永远不会被调用。)

Improved version of @fresidue answer. You can use this UIView subclass as transparent view passing touches outside its subview. Implementation in 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

.

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

TIP:

Say then you have a large "holder" panel, perhaps with a table view behind. You make the "holder" panel PassthroughView. It will now work, you can scroll the table "through" the "holder".

But!

  1. On top of the "holder" panel you have some labels or icons. Don't forget, of course those must simply be marked user interaction enabled OFF!

  2. On top of the "holder" panel you have some buttons. Don't forget, of course those must simply be marked user interaction enabled ON!

  3. Note that somewhat confusingly, the "holder" itself - the view you use PassthroughView on - must be marked user interaction enabled ON! That's ON!! (Otherwise, the code in PassthroughView simply will never be called.)

疏忽 2024-10-02 14:36:14

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

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

I needed to pass touches through a UIStackView. A UIView inside was transparent, but the UIStackView consumed all touches. This worked for me:

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

All arrangedSubviews still receive touches, but touches on the UIStackView itself went through to the view below (for me a mapView).

不甘平庸 2024-10-02 14:36:14

我对 UIStackView 有类似的问题(但可能是任何其他视图)。
我的配置如下:
查看控制器containerView 和 StackView

这是一个典型的案例,我有一个容器需要放置在后台,侧面有按钮。出于布局目的,我将按钮包含在 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
   }
}

I had a similar issue with a UIStackView (but could be any other view).
My configuration was the following:
View controller with containerView and StackView

It's a classical case where I have a container that needed to be placed in the background, with buttons on the side. For layout purposes, I included the buttons in a UIStackView, but now the middle (empty) part of the stackView intercepts touches :-(

What I did is create a subclass of UIStackView with a property defining the subView that should be touchable.
Now, any touch on the side buttons (included in the * viewsWithActiveTouch* array) will be given to the buttons, while any touch on the stackview anywhere else than these views won't be intercepted, and therefore passed to whatever is below the stack view.

/** 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
   }
}
半夏半凉 2024-10-02 14:36:14

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

If the view you want to forward the touches to doesn't happen to be a subview / superview, you can set up a custom property in your UIView subclass like so:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Make sure to synthesize it in your .m:

@synthesize forwardableTouchee;

And then include the following in any of your UIResponder methods such as:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

Wherever you instantiate your UIView, set the forwardableTouchee property to whatever view you'd like the events to be forwarded to:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;
红ご颜醉 2024-10-02 14:36:14

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

In 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
    }
}
她说她爱他 2024-10-02 14:36:14

看起来即使你这里有很多答案,但没有一个是我需要的快速干净的。
所以我在这里接受了 @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
}

Looks like even thou its quite a lot of answers here, there is no one clean in swift that I needed.
So I took answer from @fresidue here and converted it to swift as it's what now mostly developers want to use here.

It solved my problem where I have some transparent toolbar with button but I want toolbar to be invisible to user and touch events should go through.

isUserInteractionEnabled = false as some stated is not an option based on my testing.

 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
}
够运 2024-10-02 14:36:14

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

I had couple of labels inside StackView and I didn't have much success with the solutions above, instead I solved my problem using below code:

let item = ResponsiveLabel()

// Configure label

stackView.addArrangedSubview(item)

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

Then you could do something like:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}
从来不烧饼 2024-10-02 14:36:14

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

}

The situation I was trying to do was build a control panel using controls inside nested UIStackView’s. Some of the controls had UITextField’s others with UIButton’s. Also, there were labels to identify the controls. What I wanted to do was put a big “invisible” button behind the control panel so that if a user tapped on an area outside a button or text field, that I could then catch that and take action - primarily dismiss any keyboard if a text field was active (resignFirstResponder). However, tapping on a label or other blank area in the control panel would not pass things through. The above discussions were helpful in coming up with my answer below.

Basically, I sub-classed UIStackView and overwrote the “point(inside:with) routine to look for the type of controls that needed the touch and “ignore” things like labels that I wanted to ignore. It also checks for inside UIStackView’s so that things can recurse into the control panel structure.

The code is a perhaps a little more verbose than it should be. But it was helpful in debugging and hopefully provides more clarity in what the routine is doing. Just be sure in Interface Builder to change the class of the UIStackView's to 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
}

}

∞琼窗梦回ˉ 2024-10-02 14:36:14

尝试这样的事情...

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

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

Try something like this...

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

The code above, in your touchesBegan method for example would pass the touches to all of the subviews of view.

酷炫老祖宗 2024-10-02 14:36:14

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

//header

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//implementation

#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中只需将视图类设置为 TouchView 并使用您的矩形设置活动矩形。你也可以改变和实现其他逻辑。

As suggested by @PixelCloudStv if you want to throw touched from one view to another but with some additional control over this process - subclass UIView

//header

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//implementation

#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

After in interfaceBuilder just set class of View to TouchView and set active rect with your rect. Also u can change and implement other logic.

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