然后,想象一下我们在子视图后面放置了某些东西,比如菜单。我希望用户能够选择子视图中的任何“小东西”,但如果没有,则希望触摸事件可以穿过子视图(因为背景本来就是透明的),传递到其后面的按钮。
我该怎么做呢?看起来touchesBegan可以通过,但按钮却不起作用。
创建一个自定义视图用于容器,并覆盖pointInside:方法,当点不在符合条件的子视图内时返回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
}
}
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;
不需要子类化。可以正常工作。
事件转发是一种由某些应用程序使用的技术。您可以通过调用另一个响应者对象的事件处理方法来转发触摸事件。虽然这可能是一种有效的技术,但应谨慎使用。 UIKit 框架的类不是设计用于接收未绑定到它们的触摸。如果您想将触摸有条件地转发到应用程序中的其他响应者,则所有这些响应者都应该是您自己的 UIView 子类的实例。
不要显式地通过 nextResponder 将事件发送给响应者链上的下一个响应者;相反,调用超类实现并让 UIKit 处理响应者链遍历。
如果需要,您可以覆盖:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
在您的UIView子类中重写该方法,如果要将触摸事件发送到响应者链(即到其后面没有任何内容的视图),则返回NO。
对我而言,得票最高的解决方案不完全适用,我猜测这是因为我将一个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
}
}
在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
子类 PassThroughView
,只需覆盖 -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }
,如果您想将任何触摸事件转发到下面的视图。 - aucoUIButton
或UIView
的自定义类将传递在透明像素上执行的触摸事件。UIView
下面的UIButton
,同时非透明部分的UIView
仍然会响应触摸事件。UIButton
。
类链接
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
}
更新:删除示例 - 请重新阅读问题...关闭触摸事件的传递。 默认情况下,视图会接收触摸事件,但您可以将其 userInteractionEnabled 属性设置为 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
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
pointInside: withEvent:
呢? - auco
UIStackView
是一个渲染视图。这意味着它可以有一个背景。即使它是.clear
颜色,它也不会将触摸事件传递给底层视图。 - heyfrank