iOS事件响应链中hitTest的应用示例

// 作用: 去寻找最适合的View
// 什么时候调用: 当一个事件传递给当前View,就会调用.
// 返回值: 返回的是谁,谁就是最适合的View(就会调用最适合的View的touch方法)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        return super.hitTest(point, with: event)
}

hitTest的底层实现:

  • 1.先看自己是否能接受触摸事件
  • 2.再看触摸点是否在自己身上
  • 3.从后往前遍历子控件,拿到子控件后,再次重复1,2步骤,要把父控件上的坐标点转换为子控件坐标系下的点,再次执行hitTest方法
  • 4.若是最后还没有找到合适的view,那么就return self,自己就是合适的view

备注:当控件接收到触摸事件的时候,不管能不能处理事件,都会调用hitTest方法

应用实例

1.扩大UIButton的响应热区

import UIKit

private var ts_touchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {
    /// Increase your button touch area.
    /// If your button frame is (0,0,40,40). Then call button.ts_touchInsets = UIEdgeInsetsMake(-30, -30, -30, -30), it will Increase the touch area
    public var ts_touchInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &ts_touchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &ts_touchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }
    
    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.ts_touchInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }
        
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.ts_touchInsets)
        
        return hitFrame.contains(point)
    }
}

使用示例

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    areaBtn.ts_touchInsets = .init(top: -30, left: -30, bottom: -30, right: -30)
}

也可以自定义UIButton,在自定义的button里实现

import UIKit
class TouchIncreaseButton: UIButton {
    
    private let btnWidth : CGFloat = 44
    private let btnHeight : CGFloat = 44

    private func hitTestBounds(minimumHitTestWidth minWidth : CGFloat,minimumHitTestHeight minHeight : CGFloat) -> CGRect {
        var hitTestBounds = self.bounds
        if minWidth > bounds.size.width {
            hitTestBounds.size.width = minWidth
            hitTestBounds.origin.x -= (hitTestBounds.size.width - bounds.size.width)/2
        }
        if minHeight > bounds.size.height {
            hitTestBounds.size.height = minHeight
            hitTestBounds.origin.y -= (hitTestBounds.size.height - bounds.size.height)/2
        }
        
        return hitTestBounds
    }
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        
        let rect = hitTestBounds(minimumHitTestWidth: btnWidth, minimumHitTestHeight: btnHeight)
        return rect.contains(point)
    }
}

2.子view超出了父viewbounds响应事件

demo.png

重载父viewhitTest(_ point: CGPoint, with event: UIEvent?)方法

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    for subView in self.subviews {
        // 把父类point点转换成子类坐标系下的点
        let convertedPoint = subView.convert(point, from: self)
        
        // 注意点:hitTest方法内部会调用pointInside方法,询问触摸点是否在这个控件上
        // 根据point,找到适合响应事件的这个View
        let hitTestView = subView.hitTest(convertedPoint, with: event)
        if hitTestView != nil {
            return hitTestView
        }
    }
    return nil
}

3.使部分区域失去响应.

tableView.png

场景需求:如图,tableView占整个屏幕,tableView底下是一个半透明的HUD,点击下面没有内容区域,要让HUD去响应事件.

在自定义的tableView中重载hitTest方法

// tableView会拦截底部`backgroundView`事件的响应,
// 实现点击tableViewCell 之外的地方,让tableView底下的backgroundView响应tap事件
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if let hitView = super.hitTest(point, with: event) , hitView.isKind(of: BTTableCellContentView.self) {
        return hitView
    }
    // 返回nil 那么事件就不由当前控件处理
    return nil;
}

4.让非scrollView区域响应scrollView拖拽事件

scrollView.png

如图,这是一个使用scrollView自定义实现的卡片式轮播器,如何实现拖拽scrollView两边的view区域,和拖拽中间scrollView一样的效果呢?只需要在scrollView的父View重载hitTest方法

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if self.point(inside: point, with: event) == true {
        return scrollView
    }
    return nil
}

3.1.使自定义Button失去响应

class NoEventButton: UIButton {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        return nil
    }
}

参考:iOS事件响应链中Hit-Test View的应用
ios开发事件处理之 四:hittest方法的底层实现与应用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,603评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,422评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,478评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,116评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,037评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,832评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,075评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,685评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,976评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,028评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,830评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,584评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,101评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,229评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,521评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,121评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,314评论 2 339

推荐阅读更多精彩内容