如何单击透明UIView后面的按钮?

问题描述 投票:165回答:16

假设我们有一个带有一个子视图的视图控制器。子视图占据屏幕中心,四周边距为100 px。然后我们在子视图中添加一些小东西来点击。我们只使用子视图来利用新帧(子视图中的x = 0,y = 0实际上是父视图中的100,100)。

然后,假设我们在子视图后面有一些东西,比如菜单。我希望用户能够在子视图中选择任何“小东西”,但如果没有任何东西,我希望触摸通过它(因为背景是清晰的)到它后面的按钮。

我怎样才能做到这一点?它看起来像touchesBegan,但按钮不起作用。

ios objective-c iphone cocoa-touch ipad
16个回答
298
投票

为容器创建自定义视图并覆盖pointInside:message,当该点不在符合条件的子视图中时返回false,如下所示:

迅速:

class PassThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for subview in subviews {
            if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
                return true
            }
        }
        return false
    }
}

目标C:

@interface PassthroughView : UIView
@end

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

将此视图用作容器将允许其任何子项接收触摸,但视图本身对事件是透明的。


2
投票

我创建了一个类别来执行此操作。

一个小方法调整,视图是金色的。

标题

//UIView+PassthroughParent.h
@interface UIView (PassthroughParent)

- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;

@end

实施文件

#import "UIView+PassthroughParent.h"

@implementation UIView (PassthroughParent)

+ (void)load{
    Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:));
}

- (BOOL)passthroughParent{
    NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"];
    if (passthrough) return passthrough.boolValue;
    return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
    [self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"];
}

- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
    // Allow buttons to receive press events.  All other views will get ignored
    if (self.passthroughParent){
        if (self.alpha != 0 && !self.isHidden){
            for( id foundView in self.subviews )
            {
                if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
                    return YES;
            }
        }
        return NO;
    }
    else {
        return [self passthroughPointInside:point withEvent:event];// Swizzled
    }
}

@end

您需要添加我的Swizz.h和Swizz.m

位于Here

之后,您只需在{Project} -Prefix.pch文件中导入UIView + PassthroughParent.h,每个视图都具有此功能。

每个视图都会占据一点,但空白区域都不会。

我还建议使用清晰的背景。

myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];

编辑

我创建了自己的属性包,之前没有包含。

头文件

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;

@end

实施档案

// NSObject+PropertyBag.m

#import "NSObject+PropertyBag.h"



@implementation NSObject (PropertyBag)

+ (void) load{
    [self loadPropertyBag];
}

+ (void) loadPropertyBag{
    @autoreleasepool {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc));
        });
    }
}

__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
    return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
    [[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]];
    if (propBag == nil){
        propBag = [NSMutableDictionary dictionary];
        [self setPropertyBag:propBag];
    }
    return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]];
}

- (void)propertyBagDealloc{
    [self setPropertyBag:nil];
    [self propertyBagDealloc];//Swizzled
}

@end

1
投票

据我所知,你应该能够通过覆盖hitTest:方法来做到这一点。我确实尝试过,但无法让它正常工作。

最后,我围绕可触摸对象创建了一系列透明视图,以便它们不会覆盖它。对我的问题有点不好意思这很好用。


1
投票

从其他答案中获取提示并阅读Apple的文档,我创建了这个简单的库来解决您的问题: https://github.com/natrosoft/NATouchThroughView 它使得在Interface Builder中绘制视图变得容易,这些视图应该将触摸传递到底层视图。

我认为方法调整在生产代码中是过度杀伤和非常危险的,因为你直接搞乱Apple的基础实现并进行可能导致意外后果的应用程序范围的更改。

有一个演示项目,希望README可以很好地解释该做什么。要解决OP,您可以更改包含按钮的清晰UIView,以在Interface Builder中对NATouchThroughView进行分类。然后找到覆盖您想要点击的菜单的清晰UIView。将UIView更改为Interface Builder中的类NARootTouchThroughView。如果您希望这些触摸传递到底层视图控制器,它甚至可以是视图控制器的根UIView。查看演示项目以了解它是如何工作的。它非常简单,安全且无创


0
投票

如果您无法使用类别或子类UIView,您也可以将按钮向前移动,使其位于透明视图的前面。根据您的应用,这并不总是可行的,但它对我有用。您可以随时再次将按钮拉回或隐藏它。


0
投票

试试这个

class PassthroughToWindowView: UIView {
        override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            var view = super.hitTest(point, with: event)
            if view != self {
                return view
            }

            while !(view is PassthroughWindow) {
                view = view?.superview
            }
            return view
        }
    } 

0
投票

尝试将你的backgroundColortransparentView设为UIColor(white:0.000, alpha:0.020)。然后你可以在touchesBegan / touchesMoved方法中获得触摸事件。将代码放在视图所在的某个位置下方:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true

0
投票

我使用它代替覆盖方法点(内部:CGPoint,带:UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }

95
投票

我也用

myView.userInteractionEnabled = NO;

不需要子类。工作良好。


18
投票

From Apple:

事件转发是某些应用程序使用的技术。您可以通过调用另一个响应程序对象的事件处理方法来转发触摸事件。虽然这可能是一种有效的技术,但您应谨慎使用。 UIKit框架的类不是设计用于接收未绑定到它们的触摸....如果要有条件地将触摸转发给应用程序中的其他响应者,则所有这些响应者都应该是您自己的UIView子类的实例。

Apples Best Practise

不要在响应者链中明确发送事件(通过nextResponder);相反,调用超类实现并让UIKit处理响应者链遍历。

相反,你可以覆盖:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

在您的UIView子类中,如果您希望将响应发送到响应程序链(对于您视图后面的视图的I.E.,其中没有任何内容),则返回NO。


7
投票

更简单的方法是在接口构建器中“取消选中”用户交互。 “如果你正在使用故事板”

enter image description here


6
投票

在John发布的内容的基础上,这里有一个示例,它允许触摸事件通过除按钮之外的视图的所有子视图:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // Allow buttons to receive press events.  All other views will get ignored
    for( id foundView in self.subviews )
    {
        if( [foundView isKindOfClass:[UIButton class]] )
        {
            UIButton *foundButton = foundView;

            if( foundButton.isEnabled  &&  !foundButton.hidden &&  [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
                return YES;
        }        
    }
    return NO;
}

6
投票

最近我写了一个课程,这将帮助我。将它用作UIButtonUIView的自定义类将传递在透明像素上执行的触摸事件。

这个解决方案比接受的答案要好一些,因为你仍然可以点击半透明UIButton下的UIView,而UIView的非透明部分仍然会响应触摸事件。

正如您在GIF中看到的那样,长颈鹿按钮是一个简单的矩形,但透明区域上的触摸事件会传递到下面的黄色UIButton

Link to class


4
投票

最受欢迎的投票解决方案对我来说并不完全有效,我想这是因为我在层次结构中有一个TabBarController(正如其中一条评论所指出的那样)实际上它已经传递到UI的某些部分,但它却在弄乱我的tableView能够拦截触摸事件,最终在视图中覆盖了hitTest我想忽略触摸并让该视图的子视图处理它们

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        return nil; //avoid delivering touch events to the container view (self)
    }
    else{
        return view; //the subviews will still receive touch events
    }
}

3
投票

斯威夫特3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.frame.contains(point) {
            return true
        }
    }
    return false
}

2
投票

根据'iPhone应用程序编程指南':

关闭触摸事件的传递。默认情况下,视图接收触摸事件,但您可以将其userInteractionEnabled属性设置为NO以关闭事件的传递。如果视图被隐藏或者透明,则视图也不会接收事件。

http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

更新:删除示例 - 重读问题...

在按钮获取之前,您是否对可能正在处理水龙头的视图进行任何手势处理?如果没有透明视图,按钮是否有效?

任何非工作代码的代码示例?

© www.soinside.com 2019 - 2024. All rights reserved.