假设我们有一个带有一个子视图的视图控制器。子视图占据屏幕的中心,四周有 100 px 的边距。然后,我们在该子视图中添加了一些小东西来点击。我们只是使用子视图来利用新框架(子视图中的 x=0, y=0 实际上是父视图中的 100,100)。
然后,想象我们在子视图后面有一些东西,比如菜单。我希望用户能够在子视图中选择任何“小东西”,但如果那里什么都没有,我希望触摸通过它(因为无论如何背景都是清晰的)到它后面的按钮。
我怎样才能做到这一点?看起来 touchesBegan 通过了,但按钮不起作用。
UIView
下的 UIButton
,而 UIView
的非透明部分仍将响应触摸事件。
为您的容器创建一个自定义视图并覆盖 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
}
}
目标 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 子类中,如果您希望将触摸发送到响应者链(IE 到您的视图后面的视图,其中没有任何内容),则返回 NO。
NO
;
一种更简单的方法是在界面构建器中“取消选中”用户交互启用。 “如果你使用故事板”
https://i.stack.imgur.com/HrDA4.png
最近我写了一个课程来帮助我解决这个问题。将其用作 UIButton
或 UIView
的自定义类将传递在透明像素上执行的触摸事件。
此解决方案比公认的答案要好一些,因为您仍然可以单击半透明 UIView
下的 UIButton
,而 UIView
的非透明部分仍将响应触摸事件。
https://i.stack.imgur.com/Mr3qX.gif
正如您在 GIF 中看到的那样,Giraffe 按钮是一个简单的矩形,但透明区域上的触摸事件会传递到下方的黄色 UIButton
。
基于 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; }
。
投票最多的解决方案对我来说并不完全有效,我想这是因为我在层次结构中有一个 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
}
}
斯威夫特 3
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if subview.frame.contains(point) {
return true
}
}
return false
}
根据“iPhone应用程序编程指南”:
关闭触摸事件的传递。默认情况下,视图接收触摸事件,但您可以将其 userInteractionEnabled 属性设置为 NO 以关闭事件传递。如果视图是隐藏的或透明的,它也不会接收事件。
更新:删除示例 - 重新阅读问题...
在按钮获取之前,您是否对可能正在处理点击的视图进行了任何手势处理?当您没有透明视图时,该按钮是否有效?
任何非工作代码的代码示例?
从其他答案中获取提示并阅读 Apple 的文档,我创建了这个简单的库来解决您的问题:
https://github.com/natrosoft/NATouchThroughView
它可以轻松地在 Interface Builder 中绘制视图,这些视图应该将触摸传递到底层看法。
我认为方法调配在生产代码中是矫枉过正且非常危险的,因为您直接弄乱了 Apple 的基本实现并进行了可能导致意外后果的应用程序范围的更改。
有一个演示项目,希望自述文件能很好地解释要做什么。要解决 OP,您需要将包含按钮的清晰 UIView 更改为 Interface Builder 中的 NATouchThroughView 类。然后找到覆盖您想要点击的菜单的清晰 UIView。在 Interface Builder 中将该 UIView 更改为 NARootTouchThroughView 类。如果您打算将这些触摸传递到底层视图控制器,它甚至可以是您的视图控制器的根 UIView。查看演示项目以了解它是如何工作的。它真的非常简单、安全、无创
我创建了一个类别来做到这一点。
一点方法调和,视图是金色的。
标题
//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
pointInside: withEvent:
?
尝试将 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
如果您懒得使用类别或子类 UIView,您也可以将按钮向前移动,使其位于透明视图的前面。根据您的应用程序,这并不总是可能的,但它对我有用。您可以随时将按钮重新带回或隐藏它。
尝试这个
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
}
}
我使用它而不是覆盖方法点(内部:CGPoint,带有:UIEvent)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.point(inside: point, with: event) else { return nil }
return self
}
不定期副业成功案例分享