iOS事件響應控制

    之前遇到一個項目,一個UIImageView對象上面有一個UIButton對象,然而項目的需求須要在點擊 button的同時,UIImageView也接收到點擊事件,在不使用代理和通知方法的前提下,經過事件響應鏈的原理,咱們也能夠很便捷的解決這個問題。ide

    在處理這個問題以前,咱們應該先清楚IOS的事件響應機制究竟是個什麼樣的原理。spa

首先,這個事件響應的機制是分爲兩個部分的。代理

一、先在視圖層級關係中找到應該響應事件的那個視圖。code

這一步是什麼意思,其實很簡單,就是找到你所觸摸點對應的那個最上層的視圖,它的工做原理是這樣的:當用戶發出事件後,會產生一個觸摸事件,系統會將該事件加入到一個由UIApplication管理的事件隊列中,UIApplication會取出隊列中最前面的事件,發消息給UIWindow,而後UIWindow會對其全部子視圖調用hitTest:withEvent:這個方法,這個方法會返回一個UIView的對象,這個方法在執行的時候,它會調用當前視圖的pointInside:withEvent:這個方法,若是觸摸事件在當前視圖範圍內,pointInside:withEvent:會返回YES,不然會返回NO;若是返回YES,則會遍歷當前視圖的全部子視圖,統統發送hitTest:withEvent:這個消息,若是返回NO,則hitTest:withEvent:方法返回nil;orm

上面提及來有些繞,其實就是:hitTest:withEvent:方法會一層一層的向上找,若最上層響應的子視圖pointInside:withEvent:返回YES,則返回此子視圖,若是全部的都返回nil,則返回當前視圖自己self。對象

例如:咱們建兩個文件,一個繼承於UIButton,一個繼承於UIImageView,咱們在UIImageView裏的代碼以下:繼承

#import "MyImageView.h"

@implementation MyImageView
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor=[UIColor redColor];
    }
    return self;
}
//在這裏,咱們重寫了這個方法,讓它直接返回自身,而不是繼續向下尋找應該響應事件的視圖
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"點擊了Image");
}

而後將他們建立在一個View上:隊列

- (void)viewDidLoad {
    [super viewDidLoad];
    MyImageView * image = [[MyImageView alloc]initWithFrame:CGRectMake(60, 80, 200, 200)];
    MyButton * btn =[UIButton buttonWithType:UIButtonTypeSystem];
    btn.frame=CGRectMake(20, 20, 40, 40);
    [btn setTitle:@"button" forState:UIControlStateNormal];
    [image addSubview:btn];
    [self.view addSubview:image];
    // Do any additional setup after loading the view, typically from a nib.
}

咱們運行,點擊這個Btn,會打印以下的信息:事件

能夠證實,在事件視圖尋找中,UIImageView咱們重寫hitTest:withEvent:方法後,切斷了尋找鏈,若是咱們這個作:get

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    return nil;
}

你會發現,UIImageView也再也不接收事件。
二、尋找到應該響應的視圖後,會進行消息處理,這個處理的方式是經過消息處理鏈來作的。若是它自身不能處理消息,會經過nextResponder將消息傳遞給下一個處理者,默認只要有一個view將消息處理了,這個消息處理傳遞鏈將再也不傳遞。

如今,咱們把剛纔UIimageView裏重寫的hitTest:withEvent:方法註釋掉,給btn添加一個點擊方法,同時將用戶交互關閉:

- (void)viewDidLoad {
    [super viewDidLoad];
    MyImageView * image = [[MyImageView alloc]initWithFrame:CGRectMake(60, 80, 200, 200)];
    MyButton * btn =[UIButton buttonWithType:UIButtonTypeSystem];
    image.userInteractionEnabled=YES;
    btn.frame=CGRectMake(20, 20, 40, 40);
    [btn setTitle:@"button" forState:UIControlStateNormal];
    [image addSubview:btn];
    [self.view addSubview:image];
    
    
    [btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
     btn.userInteractionEnabled=NO;
    // Do any additional setup after loading the view, typically from a nib.
}

-(void)click{
    NSLog(@"btn被點擊了");
}

這樣,咱們的UIImageView又能夠響應事件了,緣由是事件處理傳遞鏈向下傳遞了。

如今,在回到咱們剛開始的問題,如何讓btn響應的同時imageView也響應,咱們這樣作:

- (void)viewDidLoad {
    [super viewDidLoad];
    MyImageView * image = [[MyImageView alloc]initWithFrame:CGRectMake(60, 80, 200, 200)];
    image.userInteractionEnabled=YES;
    MyButton * btn =[UIButton buttonWithType:UIButtonTypeSystem];
    btn.frame=CGRectMake(20, 20, 40, 40);
    [btn setTitle:@"button" forState:UIControlStateNormal];
    [image addSubview:btn];
    [self.view addSubview:image];
    
    
    [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
    btn.userInteractionEnabled=NO;
    // Do any additional setup after loading the view, typically from a nib.
}

-(void)click:(UIButton *)btn{
    NSLog(@"btn被點擊了");
    //響應鏈繼續傳遞
    [btn.nextResponder touchesBegan:nil withEvent:nil];
    
}

結果以下:

雖然最終,咱們完成了這個需求,但是我建議你最好不要這麼幹,由於這樣的邏輯是違背現實生活中人們的行爲認知的,更重要的是,咱們的項目最後也確實改掉了這樣的邏輯~~~

 

錯誤之處,歡迎指正

歡迎轉載,註明出處

專一技術,熱愛生活,交流技術,也作朋友。

——琿少 QQ羣:203317592

相關文章
相關標籤/搜索