代码之家  ›  专栏  ›  技术社区  ›  Sean Clark Hess

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

  •  161
  • Sean Clark Hess  · 技术社区  · 14 年前

    假设我们有一个视图控制器和一个子视图。子视图占据屏幕中心,各边有100像素的边距。然后我们添加了一些小东西来点击这个子视图。我们只使用子视图来利用新的框架(子视图中的x=0,y=0在父视图中实际上是100100)。

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

    我该怎么做?看起来Touchesbegan已经通过了,但是按钮不起作用。

    15 回复  |  直到 5 年前
        1
  •  286
  •   John Stephen    6 年前

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

    Swift:

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

    目的:

    @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
  •  87
  •   akw    13 年前

    我也用

    myView.userInteractionEnabled = NO;
    

    无需子类。工作良好。

        3
  •  18
  •   jackslash    9 年前

    From Apple:

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

    Apples Best Practise :

    不要显式地将事件发送到响应器链上(通过NextResponder);而是调用超类实现并让uikit处理响应器链遍历。

    相反,您可以覆盖:

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

    在您的uiview子类中,如果您希望将该触摸发送到响应器链上,则返回no(即发送到视图后面没有任何内容的视图)。

        4
  •  7
  •   madx    5 年前

    一种更简单的方法是“取消检查”界面生成器中启用的用户交互。如果你在使用故事板“,

    enter image description here

        5
  •  6
  •   Tod Cunningham    12 年前

    基于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
  •  6
  •   Segev    9 年前

    最近我写了一个班,可以帮我解决这个问题。将其用作在透明像素上执行的触摸事件的自定义类。

    此解决方案比公认的答案要好一些,因为您仍然可以单击半透明的 uiview uibutton that is under a semi transparent uiview while the non transparent part of the uiview will still response to touch events.

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

    链接到类

    那。将它用作 UIButton UIView 将传递在透明像素上执行的触摸事件。

    此解决方案比公认的答案要好一些,因为您仍然可以单击 乌布顿 在半透明的 UIVIEW 而非透明部分 UIVIEW 仍将响应触摸事件。

    GIF

    在GIF中可以看到,长颈鹿按钮是一个简单的矩形,但透明区域上的触摸事件被传递到黄色。 乌布顿 在下面。

    Link to class

        7
  •  3
  •   Barath    6 年前

    斯威夫特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
  •  3
  •   PakitoV    6 年前

    顶级投票解决方案对我来说并不是完全有效,我想这是因为我在层次结构中有一个TabbarController(正如其中一条评论指出的那样),它实际上是将触摸传递给UI的某些部分,但它干扰了TableView拦截触摸事件的能力,最终它覆盖了什么? 希泰 在视图中,我希望忽略触摸,并让该视图的子视图处理它们。

    - (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
        }
    }
    
        9
  •  2
  •   LK.    14 年前

    根据《iPhone应用程序编程指南》:

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

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

    更新的 :删除了示例-重新阅读问题…

    在按钮到达之前,您是否对可能正在处理点击的视图进行了任何手势处理?当您没有透明的视图时,按钮是否工作?

    是否有非工作代码的代码示例?

        10
  •  2
  •   Community Egal    7 年前

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

    一个小小的方法在旋转,景色是金色的。

    报头

    //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
    
        11
  •  1
  •   Liam    14 年前

    据我所知,你应该能够通过超越 hitTest: 方法。我试过了,但没能让它正常工作。

    最后,我在可触摸对象周围创建了一系列透明视图,这样它们就不会覆盖对象。这对我的问题有点小作用。

        12
  •  1
  •   n8tr    11 年前

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

    我认为在生产代码中使用方法Swizing是非常危险的,因为您直接干扰了苹果的基本实现,并在应用程序范围内进行更改,这可能导致意想不到的结果。

    有一个演示项目,希望自述文件能够很好地解释应该做什么。要处理OP,您需要将包含按钮的Clear uiView更改为Interface Builder中的Class NatouchthroughView。然后找到覆盖您想要点击的菜单的清除uiview。在接口生成器中将该uiview更改为类naroottouchthroughview。如果您希望这些触摸传递到底层视图控制器,它甚至可以是视图控制器的根uiview。查看演示项目以了解其工作原理。它非常简单、安全、无创

        13
  •  0
  •   Shaked Sayag    9 年前

    如果您不必费心使用类别或子类uiview,您也可以将按钮向前移动,使其位于透明视图的前面。这不可能总是取决于你的申请,但它为我工作。你总是可以把按钮拿回来或者把它藏起来。

        14
  •  0
  •   tBug    6 年前

    试试这个

    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
            }
        } 
    
        15
  •  0
  •   Agisight    5 年前

    尝试设置 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