UIView 中的控件事件穿透 Passthrough 的实现

我们在有多个 UIView 层叠时,比如一个按钮被一个 UIView 遮盖时,想要在点击最上层的 UIView 时能触发按钮的相应事件,我们该如何实现呢,初步可以想到几种办法:

1. 把按钮上层的所有 UIView 的 userInteractionEnabled 属性设置为 NO,要是 UIView 有自己的交互事件该如何办呢?而且这个 userInteractionEnabled 不能动态设置,等到点击后决定设置它的 NO 是没用的

2. UIView 接受到点击事件后主动去触发下面按钮的点击,这时的关题有三,按钮没有点击过程中的交换效果、多层 UIView 时不切实际,逐层下传吗、还有就是其他双击、三击或别的手势如何处理

我也一直被前面两种方式纠缠着,同时也让 UIPopoverController 的 NSArray *passthroughViews 属性提醒着,因为对于 UIPopoverController,设置到它的 passthoughViews 属性中的控件事件可以完全从 UIDimmingView 下透出来。但苦于不可能看到 UIPopoverController 的源码,还是后面一而再的 Google 终于发现了 UIView 的方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system

只要实现了最顶层的 UIView 的 hitTest 方法,在某些情况返回下层的某个按钮实例,即相当于把那个按钮的事件透出来了,比如在点击落在该按钮上时,不管这个按钮在 UIView 下多少层都可以把它挖出来。

先看效果图:

UIView 中的控件事件穿透 Passthrough 的实现_第1张图片   UIView 中的控件事件穿透 Passthrough 的实现_第2张图片

UIView 中的控件事件穿透 Passthrough 的实现_第3张图片

三个图分别是:

1. 所见到的,按钮被半透明红色 View 遮住了一部分
2. 可点击未遮住的按钮部分,可看到按钮被点下未抬起的效果
3. 在红色的 View 中点击按钮被遮住部分,同样触发了按钮的相应事件,且有中间效果,也就是说按钮穿透出来了

再看代码实现,有两部分代码,分别是 ViewController 和  CustomController

ViewController.h

01
02
03
04
05
06
07
08
09
10
11
12
13
14
//
//  ViewController.h
 
//
//  Created by Unmi on 11/15/11.
//  Copyright (c) 2011 http://unmi.cc. All rights reserved.
//
 
#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController {
}
 
@end

ViewController.h

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//
//  ViewController.m
//
//  Created by Unmi on 11/15/11.
//  Copyright (c) 2011 http://unmi.cc. All rights reserved.
//
 
#import "ViewController.h"
#import "CustomView.h"
 
@implementation ViewController{
     UIButton *passthroughButton;
}
 
#pragma mark - View lifecycle
 
- ( void )viewDidLoad
{
     [ super viewDidLoad];
 
     passthroughButton = [ UIButton buttonWithType: UIButtonTypeRoundedRect ];
     [passthroughButton setTitle:@ "Passthrough" forState: UIControlStateNormal ];
     [ self .view addSubview:passthroughButton];
     passthroughButton.frame = CGRectMake(20, 50, 120, 28);
     [passthroughButton release];
     
     CustomView *customView = [[CustomView alloc] initWithFrame:CGRectMake(80, 10, 300, 200)];
     customView.backgroundColor = [ UIColor colorWithRed:1 green:0 blue:0 alpha:.5];
     customView.passthroughViews = [ NSArray arrayWithObject:passthroughButton];
     [ self .view addSubview:customView];
     [customView release];
}
 
- ( void )dealloc {
     [ super dealloc];
}
@end

CustomView.h

01
02
03
04
05
06
07
08
09
10
11
12
13
//
//  CustomView.h
//  TestPopover
//
//  Created by Unmi on 2/19/12.
//  Copyright (c) 2012 http://unmi.cc. All rights reserved.
//
 
#import <UIKit/UIKit.h>
 
@interface CustomView : UIView
@property ( nonatomic , copy ) NSArray *passthroughViews;
@end

CustomView.m

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//
//  CustomView.m
//
//  Created by Unmi on 2/19/12.
//  Copyright (c) 2012 http://unmi.cc. All rights reserved.
//
 
#import "CustomView.h"
 
@interface CustomView()
-( BOOL ) isPassthroughView: ( UIView *) view;
@end
 
@implementation CustomView{
     BOOL testHits;
}
 
@synthesize passthroughViews=_passthroughViews;
 
-( UIView *) hitTest:(CGPoint)point withEvent:( UIEvent *)event{
     if (testHits){
         return nil ;
     }
     
     if (! self .passthroughViews
         || ( self .passthroughViews && self .passthroughViews.count == 0)){
         return self ;
     } else {
         
         UIView *hitView = [ super hitTest:point withEvent:event];
         
         if (hitView == self ) {
             //Test whether any of the passthrough views would handle this touch
             testHits = YES ;
             CGPoint superPoint = [ self .superview convertPoint:point fromView: self ];
             UIView *superHitView = [ self .superview hitTest:superPoint withEvent:event];
             testHits = NO ;
             
             if ([ self isPassthroughView:superHitView]) {
                 hitView = superHitView;
             }
         }
         
         return hitView;
     }
}
 
- ( BOOL )isPassthroughView:( UIView *)view {
     
     if (view == nil ) {
         return NO ;
     }
     
     if ([ self .passthroughViews containsObject:view]) {
         return YES ;
     }
     
     return [ self isPassthroughView:view.superview];
}             
 
-( void ) dealloc{
     self .passthroughViews = nil ;
}
 
@end

关键要理解 hitTest 方法的作用,可参考:

1. http://blog.sina.com.cn/s/blog_677089db01012wpg.html
2. https://github.com/werner77/WEPopover

本文链接 http://unmi.cc/uiview-event-passthrough/, 来自隔叶黄莺 Unmi Blog

你可能感兴趣的:(UIView 中的控件事件穿透 Passthrough 的实现)