天天看點

iOS中hitTest如何擴大一個視圖的點選區域。

1.問題:之前在面試時被問到這樣一個問題:如何擴大一個給定視圖的點選區域,或者說如何讓一個視圖在目前視圖外的子視圖也能響應點選事件。

2.場景:可能大家不太了解這種問題是什麼意思,現在給定場景。我們在開發中經常會遇到這樣問題,上傳圖檔的時候,我們選中圖檔後要删除選中的圖檔,會在右上角添加一個删除按鈕。這時候删除按鈕就會有一半,或者全部都出現在目前圖檔視圖的bounds外,導緻不好點或者不能點的情況出現,那就需要用到我們今天的命題,擴大視圖的點選區域。

3.前提:在解決這個問題的時候,我們先要清楚兩個東西

           1.觸摸時間的touch的産生順序。一個APP中touch時間是如何産生的,當我們手指接觸到螢幕時,通過硬體産生一個touch

      然後以 touch -> UIApplication -> UIWindow -> UIViewController.View -> subviews -> ... ->最終找到最合适的響應者 or 丢棄

          2.響應鍊:當我們找到最合适的響應者的時候,我的view就開始進行touch時間的響應。順序恰恰和産生相反。

             以 view -> supView ->... -> UIViewController -> UIWindow -> UIApplication  or(丢棄)。

4.方法:hitTest,這個方法就負責進行touch事件的傳遞,當事件通過觸摸點傳過來之後,由這個類的對象通過hitTest方法判斷傳遞給哪些subView,或者丢棄。一個事件會丢棄有四種情況

           ①:view.userinterfaceEnable == NO

           ②:view.hidden == YES

           ③:view.alpha < 0.05

           ④:點選區域 超出 view.bounds 之外。

  是以我們不難推斷hitTest的實作。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    if (self.hidden == NO || self.alpha < 0.05 || self.userInteractionEnabled == NO) {
        //1.當滿足這幾個條件時,直接丢棄touch事件,不再向下分發。
        return nil;
    }else{
        if (![self pointInside:point withEvent:event]) {
            //2.如果點選point在視圖之外,丢棄
            return nil;
        }else{
            //3.分發給子視圖
            if (self.subviews.count > 0) {
                for (UIView *subView in self.subviews) {
                    UIView *hitTestSubView = [subView hitTest:point withEvent:event];
                    return hitTestSubView;
                }
            }else{
                return self;
            }
        }
    }
}           

5.解決,是以我們隻需要在改變分發政策,讓他在視圖外同樣分發給子視圖就行了。

class HitTestView: UIView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden == true || alpha < 0.05 || isUserInteractionEnabled == false {
           return super.hitTest(point, with: event)
        }else{
            if self.point(inside: point, with: event) {
                return super.hitTest(point, with: event)
            }else{
                //1.有subView時交給subView 去響應
                for subview in self.subviews{
                    let coverPoint = self.convert(point, to: subview)
                    return subview.hitTest(coverPoint, with: event)
                }
                
                //2.沒有subView時交給自己來響應,也就是說你無論在哪兒點選都會響應(擴大點選區域)
                //當然這裡如果你想擴大到一定的傳回,可以在此處加限制
                let isResponse:Bool = false
                if isResponse {
                    return self
                }else {
                    return nil
                }
                //3.如果你不想當沒有subView時就随便響應,j就傳回nil
                return nil
            }
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(#function)
    }

}           

6.參考:iOS hitTest