iOS使用hitTest和loadView處理UIView事件傳遞

背景

項目漸漸變大的過程當中,因爲ViewController 承擔的業務邏輯愈來愈多,會遇到ViewController膨脹變得龐大無比的狀況。好比在我作的項目中,有一個頁面包含了展現信息業務、彈幕業務、其餘附加業務,致使這個頁面的ViewController超過了1000行,這麼龐大的一個類,要修改東西第一個遇到的問題就是要查找定位,修改還很可能還會影響到其餘地方,因此打算把這些模塊獨立出來,作成獨立的ViewController,再在一個主要的ViewController中引入其餘業務的ViewController。
遇到了一個問題是ViewController中的View事件透傳的問題,加入了多個ViewController的View,這些View若是須要交互,就須要把View的userInteractionEnabled屬性設置爲YES,這樣致使了事件會被頂部的View攔截,底下的View事件會接收不到。ios

結果

主要是使用了 hitTest: withEvent: 作了一個處理,對於當前View須要處理交互事件的子View,正常處理當前子View的事件,遇到空白的地方,則把事件繼續傳遞到底下的View作處理,其餘的頁面也按照這種方式處理。git

Demo: Demoapp

分析實現

hitTest: withEvent: 的做用就是返回一個能夠處理用戶事件的View,有如下幾種狀況ide

  • 若是View是 UIControl 類型,則會調用對應的target
  • 若是是普通的View則會調用beginTrackingWithTouch: withEvent: 等方法
  • 在ViewController中則會調用touchesBegan: withEvent 等方法。
  • 若是 hitTest: withEvent: 返回一個nil則事件會傳遞到下一層作一樣的處理。

下面是自定義的UIView, hitTest: withEvent: 處理須要交互的子View,若是點擊的點位置不是須要加護的View的位置,返回nil,交個下一層處理。atom

@interface PTTransparentView() {}
@property (nonatomic, strong) NSMutableArray* hitViews;
@end

@implementation PTTransparentView

/**
 把須要交互的View添加到當前View中
 */
- (void)addTouchableView:(UIView*)touchableView {
    if (touchableView) {
        [self.hitViews addObject:touchableView];
    }
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    for (int i = 0; i<self.hitViews.count; i++) {
        UIView* hitView = self.hitViews[i];
        CGRect mappedFrame = [hitView convertRect:hitView.bounds toView:self];
        if (CGRectContainsPoint(mappedFrame, point)) {
            return hitView;
        }
    }
    return nil;
}

- (NSMutableArray *)hitViews {
    if (!_hitViews) {
        _hitViews = [[NSMutableArray alloc] initWithCapacity:2];
    }
    return _hitViews;
}

@end

自定義的UIView在ViewController中的使用,spa

  • 重寫UIViewController的方法 loadView 初始化 self.view。
  • 設置須要交互的子View

示例代碼以下:code

#import "AViewController.h"
#import "PTTransparentView.h"

@interface AViewController ()
@property (nonatomic, strong) UIButton* helpButton;
@end

@implementation AViewController


- (void)loadView {
    self.view = [[PTTransparentView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.userInteractionEnabled = YES;
    
    [self.view addSubview:self.helpButton];
    
    // 添加可點擊的View
    if ([self.view isKindOfClass:[PTTransparentView class]]) {
        [((PTTransparentView*)self.view) addTouchableView:self.helpButton];
    }
}

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

- (UIButton *)helpButton {
    if (!_helpButton) {
        _helpButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        _helpButton.frame = CGRectMake(100, 100, 100, 40);
        [_helpButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        [_helpButton setTitle:@"A幫助" forState:UIControlStateNormal];
        [_helpButton addTarget:self action:@selector(onHelpBtnClick) forControlEvents:UIControlEventTouchUpInside];
    }
    return _helpButton;
}

- (void)onHelpBtnClick {
    NSLog(@"====A幫助");
}

@end

在容器的ViewController添加了 AViewController 和 BViewController 對應的view,AViewController 和 BViewController 中各包含了一個可交互的按鈕,點擊按鈕的位置處理按鈕的點擊事件,點擊空白位置則不處理事件,事件會傳遞到下一層,若是點擊的不是按鈕的位置,最終事件會在容器的ViewController中的 touchesBegan: withEvent: 被處理。orm

@interface ViewController ()
@property (nonatomic, strong) AViewController* aVC;
@property (nonatomic, strong) BViewController* bVC;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:self.aVC.view];
    self.aVC.view.frame = self.view.bounds;
    
    [self.view addSubview:self.bVC.view];
    self.bVC.view.frame = self.view.bounds;
}


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

- (AViewController *)aVC {
    if (!_aVC) {
        _aVC = [AViewController new];
    }
    return _aVC;
}

- (BViewController *)bVC {
    if (!_bVC) {
        _bVC = [BViewController new];
    }
    return _bVC;
}

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

@end

總結

以上就是UIView事件傳遞處理的一個簡單應用場景。事件

相關文章
相關標籤/搜索