我如何点击一个透明的 UIView 后面的按钮?

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

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

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

90629 次浏览

据我所知,你应该能够通过重写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
从其他答案和阅读苹果的文档中获得提示,我创建了这个简单的库来解决你的问题 https://github.com/natrosoft/NATouchThroughView < br > 它使得在Interface Builder中绘制视图变得很容易,这些视图应该将触摸传递给底层视图

我认为在产品代码中使用方法是非常危险的,因为你直接破坏了苹果的基本实现,并在整个应用程序范围内进行更改,可能会导致意想不到的后果。

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

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

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

GIF

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

类链接

如果你懒得使用类别或子类UIView,你也可以把按钮向前移,这样它就在透明视图的前面。这并不总是可能的,取决于您的应用程序,但它适用于我。你可以随时把按钮拿回来或把它藏起来。

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

enter image description here

斯威夫特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
}
}

尝试将你的transparentViewbackgroundColor设置为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
}