1. 程式人生 > >UIView 中的控制元件事件穿透 Passthrough 的實現

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 下多少層都可以把它挖出來。

先看效果圖:

三個圖分別是:

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 方法的作用,可參考: