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

时间:2010-06-15 15:51:41

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

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

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

我该怎么做?它看起来像touchesBegan,但按钮不起作用。

16 个答案:

答案 0 :(得分:294)

为容器创建自定义视图并覆盖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

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

答案 1 :(得分:90)

我也用

myView.userInteractionEnabled = NO;

无需子类。工作正常。

答案 2 :(得分:18)

From Apple:

  

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

Apples Best Practise

  

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

而你可以覆盖:

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

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

答案 3 :(得分:7)

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

enter image description here

答案 4 :(得分: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;
}

答案 5 :(得分:6)

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

此解决方案比接受的答案略胜一筹,因为您仍然可以点击半透明UIButton下的UIView,而UIView的非透明部分仍会响应触摸事件。

GIF

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

Link to class

答案 6 :(得分: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
    }
}

答案 7 :(得分:3)

Swift 3

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

答案 8 :(得分:2)

根据“iPhone应用程序编程指南”:

  

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

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

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

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

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

答案 9 :(得分: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

答案 10 :(得分:1)

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

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

答案 11 :(得分:1)

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

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

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

答案 12 :(得分:0)

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

答案 13 :(得分: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
        }
    } 

答案 14 :(得分:0)

尝试将backgroundColor中的transparentView设置为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

答案 15 :(得分:0)

我使用它而不是重写方法point(内部:CGPoint,带有:UIEvent)

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