假设我们有一个带有一个子视图的视图控制器。子视图占据屏幕的中心,所有边距为100px。然后我们在子视图中添加了一些小东西来点击。我们只是使用子视图来利用新帧(子视图中的x=0, y=0在父视图中实际上是100,100)。
然后,想象我们在子视图后面有一些东西,比如菜单。我希望用户能够在子视图中选择任何“小东西”,但如果那里什么都没有,我希望触摸通过它(因为背景是清楚的)到它后面的按钮。
我该怎么做呢?看起来touchesBegan通过了,但是按钮不起作用。
据我所知,你应该能够通过重写hitTest:方法来做到这一点。我试过了,但没能让它正常工作。
最后,我在可触摸对象周围创建了一系列透明视图,这样它们就不会覆盖它。有点hack我的问题,这工作得很好。
根据“iPhone应用程序编程指南”:
关闭触摸事件的传递。 默认情况下,视图接收触摸 事件,但你可以设置它的userInteractionEnabled属性为NO 关闭事件的传递。如果视图被隐藏,它也不会接收事件 或者如果它是透明的.
http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html
更新:删除的例子-重新阅读问题…
在按钮得到之前,视图上是否有任何手势处理?当你在它上面没有透明视图时,按钮还能工作吗?
任何无效代码的代码样本?
为你的容器创建一个自定义视图,并重写pointInside:消息,当这个点不在一个合格的子视图中时返回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 } }
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.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) return YES; } return NO; } @end
使用这个视图作为容器将允许它的任何子视图接收触摸,但视图本身对事件是透明的。
我还用
myView.userInteractionEnabled = NO;
不需要子类化。工作很好。
From Apple:
事件转发是一些应用程序使用的技术。您可以通过调用另一个响应器对象的事件处理方法来转发触摸事件。虽然这可能是一种有效的技术,但您应该谨慎使用。UIKit框架的类不是设计来接收没有绑定到它们的触摸....如果你想在你的应用程序中有条件地将触摸转发给其他响应器,所有这些响应器都应该是你自己的UIView子类的实例。
苹果最佳实践:
不要显式地将事件发送到responder链(通过nextResponder);相反,调用超类实现,让UIKit处理响应链遍历。
相反,你可以重写:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
在你的UIView子类中,如果你想要那个触摸被发送到responder chain(即到你的视图后面没有任何内容的视图),返回NO。
基于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; }
我创建了一个分类来做这个。
稍微搅拌一下,风景就很美了。
头
//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
位于在这里
在那之后,你只需在你的{Project}-Prefix中导入UIView+PassthroughParent.h。PCH文件,每个视图都有这个能力。
每一个视图都会得分,但没有一个空白的空间会。
我还建议使用清晰的背景。
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
我认为在产品代码中使用方法是非常危险的,因为你直接破坏了苹果的基本实现,并在整个应用程序范围内进行更改,可能会导致意想不到的后果。
有一个演示项目,希望README能很好地解释要做什么。为了解决OP,你需要在Interface Builder中将包含按钮的清晰UIView更改为类NATouchThroughView。然后找到覆盖你想要点击的菜单的清晰UIView。在接口生成器中将UIView更改为类NARootTouchThroughView。它甚至可以是你视图控制器的根UIView如果你想让那些触摸传递到底层视图控制器。查看演示项目,看看它是如何工作的。这真的很简单,安全,无创
最近我写了一个类来帮助我解决这个问题。使用它作为UIButton或UIView的自定义类将传递在透明像素上执行的触摸事件。
UIButton
UIView
这个解决方案比公认的答案要好一些,因为你仍然可以点击半透明的UIView下面的UIButton,而UIView的非透明部分仍然会响应触摸事件。
正如你在动图中看到的,长颈鹿按钮是一个简单的矩形,但在透明区域上的触摸事件被传递给下面的黄色UIButton。
类链接
如果你懒得使用类别或子类UIView,你也可以把按钮向前移,这样它就在透明视图的前面。这并不总是可能的,取决于您的应用程序,但它适用于我。你可以随时把按钮拿回来或把它藏起来。
一个更简单的方法是在界面构建器中“取消检查”用户交互启用。如果你在使用故事板
斯威夫特3
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { for subview in subviews { if subview.frame.contains(point) { return true } } return false }
最投票的解决方案并没有完全为我工作,我猜这是因为我有一个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 } }
试试这个
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 } }
尝试将你的transparentView的backgroundColor设置为UIColor(white:0.000, alpha:0.020)。然后你可以在touchesBegan/touchesMoved方法中获取触摸事件。将下面的代码放在视图被启动的地方:
transparentView
backgroundColor
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
我用它代替重写方法点(里面:CGPoint,与:UIEvent)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard self.point(inside: point, with: event) else { return nil } return self }