ios開發事件處理之 四:hittest方法的底層實現與應用


#import "XMGWindow.h"
/**
 1:注意點:hitTest方法內部會調用pointInside方法,詢問觸摸點是否在自己身上,當遍歷子控件時,傳入的坐標點要進行轉化,將父視圖上的坐標點轉換到所要傳遞的子視圖上的坐標點
 2:hitTest的底層實現:當控件接收到觸摸事件的時候,不管能不能處理事件,都會調用hitTest方法,此方法的底層實現是:1:先看自己是否能接受觸摸事件  2:再看觸摸點是否在自己身上 3:從后往前遍歷子控件,拿到子控件后,再次重復1,2步驟,要把父控件上的坐標點轉換為子控件坐標系下的點,再次執行hitTest方法。
 
 3:若是最后還沒有找到合適的view,那么就return self,自己就是合適的view
 
 
 
 */
@implementation XMGWindow


//作用:去尋找最適合的View
//什么時候調用:當一個事件傳遞給當前View,就會調用.
//返回值:返回的是誰,誰就是最適合的View(就會調用最適合的View的touch方法)
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
   
    //1.判斷自己能否接收事件
    if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
        return nil;
    }
    //2.判斷當前點在不在當前View.
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    //3.從后往前遍歷自己的子控件.讓子控件重復前兩步操作,(把事件傳遞給,讓子控件調用hitTest)
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        //取出每一個子控件
        UIView *chileV =  self.subviews[i];
        //把當前的點轉換成子控件坐標系上的點.
        CGPoint childP = [self convertPoint:point toView:chileV];
        UIView *fitView = [chileV hitTest:childP withEvent:event];
        //判斷有沒有找到最適合的View
        if(fitView){
            return fitView;
        }
    }
    
    //4.沒有找到比它自己更適合的View.那么它自己就是最適合的View
    return self;
    
}



//作用:判斷當前點在不在它調用View,(誰調用pointInside,這個View就是誰)
//什么時候調用:它是在hitTest方法當中調用的.
//注意:point點必須得要跟它方法調用者在同一個坐標系里面
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    return YES;
}





@end

 2:hitTest 方法的練習1:

業務邏輯:

底部一個按鈕, 按鈕的上面有一個View,遮擋在按鈕的上面.

點擊View時, View接收事件,當發現點擊的點在按鈕的位置時, 讓底部的按鈕處理事件.

 

實現思路:

實現View的touchBegain方法,先堅聽UIView的點擊.

並去實現UIView的HitTest方法, 在hitTest方法當中通過把當前點轉換成按鈕所在的坐標系

      CGPoint btnP = [self convertPoint:point toView:self.btn];

       轉換過后查看當前點在不在按鈕上,如果在按鈕上,就直接返回按鈕.

       如果有在按鈕上,保持系統默認做法.

        

    實現代碼:

    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

    判斷當前點在不在按鈕上.

    把當前點轉換成按鈕所在的坐標系

    CGPoint btnP = [self convertPoint:point toView:self.btn];

    if ([self.btn pointInside:btnP withEvent:event]) {

        return self.btn;

    }else{

      return [super hitTest:point withEvent:event];

    }

}

#import "BlueView.h"

@interface BlueView()

@property(nonatomic, weak) IBOutlet UIButton *btn;

@end

@implementation BlueView


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    //NSLog(@"%@",self.btn);
    //return [super hitTest:point withEvent:event];
    //拿到后面的按鈕
    //當點在按鈕上的時候,才返回按鈕,如果不在按鈕上.保持系統默認做法
    
    //判斷點在不在按鈕身上
    //把當前的點轉換到按鈕身上的坐標系的點
    CGPoint btnP = [self convertPoint:point toView:self.btn];
    if ([self.btn pointInside:btnP withEvent:event]) {
        return self.btn;
    }else{
        return [super hitTest:point withEvent:event];
    }
    
}


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
}

@end

注意:在storyBoard中定義了view和按鈕,view自定義可以拖線到自定義的view中,前提是得進行類的關聯,但是button不能拖入,因為button不屬於view的子控件,解決:可以在view中屬性定義IBOutlet,在拖線到storyBoard中的按鈕,這樣就在view中拿到了不屬於view的button

 

2:hitTest練習2

業務邏輯:
按鈕可以隨着⼿手指拖動⽽而拖動.拖動過程當中,按鈕當中的⼦子控件也跟着拖動. 讓超過按鈕的⼦子控件也能夠響應事件,⼀一般情況下,當⼀一個控件超過他的⽗父控件的時候,是不能 夠接收事件的.
現在要做的事情就讓超過⽗父控件的按鈕也能夠響應事件.

 

#import "ViewController.h"
#import "chatBtn.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)btnClick:(chatBtn *)sender {
    
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setImage:[UIImage imageNamed:@"對話框"] forState:UIControlStateNormal];
    [btn setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
    
    sender.popBtn = btn;
    
    btn.frame = CGRectMake(100, -80, 100, 80);
    [sender addSubview:btn];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
#import <UIKit/UIKit.h>

@interface chatBtn : UIButton


/** 彈出的按鈕 */
@property (nonatomic, weak)UIButton *popBtn;

@end
#import "chatBtn.h"

@implementation chatBtn

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.popBtn) {
        //return self.popBtn;
        //判斷當前點在不在popBtn身上
        //把當前點轉換popBtn身上的點
         CGPoint popBtnP = [self convertPoint:point toView:self.popBtn];
        if ( [self.popBtn pointInside:popBtnP withEvent:event]) {
            return self.popBtn;
        }else{
            return [super hitTest:point withEvent:event];
        }
        
    }else{
      return  [super hitTest:point withEvent:event];
    }

}


-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //1.獲取UITouch
    UITouch *touch = [touches anyObject];
    //2.獲取當前手指的點,上一個手指的點
    CGPoint curP = [touch locationInView:self];
    CGPoint preP = [touch previousLocationInView:self];
    //3.計算偏移量
    CGFloat offsetX = curP.x - preP.x;
    CGFloat offsetY = curP.y - preP.y;
    //4.平移
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
    
}


@end

 

一般情況下,當⼀一個控件超過他的⽗父控件的時候,是不能 夠接收事件的.原因是:1:當產生觸摸事件后,系統會將觸摸事件發送到由UIApplication管理的事件隊列中,UIApplication會將隊列中最前端的事件取出來交給keywindow去處理,主窗口keywindow會1:查看自身能不能接受觸摸事件 2:觸摸點是不是在自身上  3:若是前兩個條件都滿足則其會遍歷自身的子控件,且是從后到前遍歷,也就是從子控件數組的最后一個控件開始遍歷,在執行前兩個步驟,若是不再則遍歷下一個子控件,若是一直沒找到,則自己就是最合適處理事件的view,若在,則繼續重復前兩個步驟,直到找到最合適的view。2:當點擊按鈕的時候window將事件傳遞到白色view,白色view從后往前遍歷子控件,先遍歷藍色的button,在遍歷點擊對話框找到適合處理事件的view。當子控件超出父控件的范圍后,點擊子控件時不會響應事件,原因是:當遍歷到父控件點擊對話框時,觸摸點不再其身上,則其就不會響應事件

實現思路:
第⼀一步,先辦到讓按鈕能夠跟隨着⼿手指移動⽽而移動. 實現按鈕的touchesMoved⽅方法,在touchesMoved⽅方法當中,獲得當前⼿手指所在的點.以前上⼀一 個點.一個手指對應一個UITouch對象,一個手指取出UITouch,anyobject,若是多根手指則會touch.allobjects獲取所有的UITouch對象
分別計算X軸的偏移量以及Y軸的偏移量. 然后修改當前按鈕的transform讓按鈕辦到能夠跟隨着⼿手指移動⽽而移動(累加形變).

第⼆二步,1: 實現按鈕的hitTest⽅方法. 在該⽅方法當中去判斷當前的點在不在按鈕的⼦子控件上. 如果在按鈕的⼦子控件上.就返回按鈕的⼦子控件如果不在的話, 就保持系統的默認做法.一般在父類中重寫hitTest方法,修改返回最合適的view 2:self.chatBtn.btn定義為弱引用, self.chatBtn.btn = btn賦值指針地址,弱引用指向該對象,[self.chatBtn addSubview:btn];強引用引用着btn使其不被銷毀,所以可以用weak 3:事件會由白色view傳遞到其子控件self.chatBtn上,所以在self.chatBtn的類里重寫hitTest方法,修改返回的view

實現代碼: 1.實現點擊添加⼦子控制器

   - (IBAction)chatBtnClick:(id)sender {

UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; [btn setImage:[UIImage imageNamed:@"對話框"]

forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@"⼩小孩"]

forState:UIControlStateHighlighted];
btn.frame = CGRectMake(self.chatBtn.bounds.size.width * 0.5,

-80, 100, 80); self.chatBtn.btn = btn;

[self.chatBtn addSubview:btn]; }

2.⾃自定義按鈕,實現拖動按鈕

讓按鈕跟着⼿手指移動⽽而移動.
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

獲取當前⼿手指對象

UITouch *touch = [touches anyObject]; 獲取當前⼿手指所在的點

CGPoint curP = [touch locationInView:self]; 獲取上⼀一個⼿手指所在的點

CGPoint preP = [touch previousLocationInView:self]; X軸的偏移量

CGFloat offsetX = curP.x - preP.x; Y軸的偏移量
CGFloat offsetY = curP.y - preP.y; 移動

    self.transform = CGAffineTransformTranslate(self.transform,
offsetX, offsetY);

}

3.攔截hitTest⽅方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

判斷當前的點在不在上⾯面的按鈕上.

  先把點轉換成上⾯面按鈕上⾯面的點

CGPoint btnP = [self convertPoint:point toView:self.btn]; 判斷點在不在按鈕上.

if ([self.btn pointInside:btnP withEvent:event]) { 讓按鈕點擊響應事件

        return self.btn;
    }else{

保持系統默認做法

        return [super hitTest:point withEvent:event];
    }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM