了解事件響應(yīng)鏈的同學(xué)應(yīng)該知道hitTest
和point
方法遏匆,我們先來簡單回顧一下
hitTest
內(nèi)部實(shí)現(xiàn)
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1.判斷當(dāng)前控件能否接收事件
if isUserInteractionEnabled == false || hidden == true || alpha <= 0.01 {
return nil
}
// 2. 判斷點(diǎn)在不在當(dāng)前控件
if self.point(inside: point, with: event) == false {
return nil
}
// 3.從后往前遍歷自己的子控件
let count: Int = subviews.count
var i = count - 1
while i >= 0 {
let childView: UIView? = subviews[i]
// 把當(dāng)前控件上的坐標(biāo)系轉(zhuǎn)換成子控件上的坐標(biāo)系
let childP: CGPoint = convert(point, to: childView)
let fitView: UIView? = childView?.hitTest(childP, with: event)
if fitView != nil {
// 尋找到最合適的view
return fitView
}
i -= 1
}
// 循環(huán)結(jié)束,表示沒有比自己更合適的view
return self
}
總結(jié)
- 作用:去尋找最適合的View
- 調(diào)用:當(dāng)一個(gè)事件傳遞給當(dāng)前View,就會(huì)調(diào)用
- 返回值:返回的是誰,誰就是最適合的View(就會(huì)調(diào)用最適合的View的touch方法)
point
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// 如果返回false就代表泥张,當(dāng)前點(diǎn)不在紅色view(self)上面初厚,那么當(dāng)我們確實(shí)點(diǎn)擊紅絲view趣钱,紅色view也不會(huì)響應(yīng)事件。
// return false;
//如果返回true就代表,當(dāng)前點(diǎn)在紅色view(self)上面,那么即使我們沒有點(diǎn)擊紅絲view羔味,紅色view也會(huì)響應(yīng)事件。
}
總結(jié)
- 作用:判斷當(dāng)前點(diǎn)在不在它調(diào)用View,(誰調(diào)用pointInside,這個(gè)View就是誰)
- 調(diào)用:它是在hitTest方法當(dāng)中調(diào)用的
- 注意:point點(diǎn)必須得要跟它方法調(diào)用者在同一個(gè)坐標(biāo)系里面
究竟什么時(shí)候重寫hitTest钠右,什么時(shí)候重寫point赋元?
很多情況下hitTest和pointInside方法任選其一都可以實(shí)現(xiàn)某個(gè)功能,比如在屏蔽中飒房,point返回false可以實(shí)現(xiàn)的話们陆,都可以用hitTest返回nil代替。
但是情屹,hitTest更強(qiáng)大。因?yàn)閜oint在一般情況下其內(nèi)部頂多只能根據(jù)情況判斷怎么返回false杂腰,屏蔽掉自己和子控件的事件響應(yīng)垃你。所以只要是想保留子控件對(duì)觸摸事件響應(yīng),屏蔽其父控件的響應(yīng)喂很,單獨(dú)重寫point無法辦到惜颇,必須要重寫hitTest方法。
觸摸事件原本該由某個(gè)view響應(yīng)少辣,現(xiàn)在你不想讓它處理而讓別的控件處理凌摄,那么就應(yīng)該在該view內(nèi)重寫hitTest或point方法。
運(yùn)用Runtime擴(kuò)大UIButton點(diǎn)擊區(qū)域
根據(jù)上面的介紹可以知道通過在UIButton的extension中重寫hitTest
或point
漓帅,下面是簡單實(shí)現(xiàn)
extension UIButton {
private struct cs_associatedKeys {
static var topKey = "cs_topKey"
static var rightKey = "cs_rightKey"
static var bottomKey = "cs_bottomKey"
static var leftKey = "cs_leftKey"
static var marginKey = "cs_marginKey"
}
//方法一
func setEnlargeEdgeWith(top: CGFloat, right: CGFloat, bottom: CGFloat, left : CGFloat) {
objc_setAssociatedObject(self, &cs_associatedKeys.topKey, top, .OBJC_ASSOCIATION_COPY_NONATOMIC)
objc_setAssociatedObject(self, &cs_associatedKeys.rightKey, right, .OBJC_ASSOCIATION_COPY_NONATOMIC)
objc_setAssociatedObject(self, &cs_associatedKeys.bottomKey, bottom, .OBJC_ASSOCIATION_COPY_NONATOMIC)
objc_setAssociatedObject(self, &cs_associatedKeys.leftKey, left, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
private var enlargedRect: CGRect {
let topEdge = objc_getAssociatedObject(self, &cs_associatedKeys.topKey) as? CGFloat
let rightEdge = objc_getAssociatedObject(self, &cs_associatedKeys.rightKey) as? CGFloat
let bottomEdge = objc_getAssociatedObject(self, &cs_associatedKeys.bottomKey) as? CGFloat
let leftEdge = objc_getAssociatedObject(self, &cs_associatedKeys.leftKey) as? CGFloat
guard let top = topEdge, let right = rightEdge, let botton = bottomEdge, let left = leftEdge else {
return bounds
}
return CGRect(x: CGFloat(bounds.origin.x - left), y: CGFloat(bounds.origin.y - top), width: CGFloat(bounds.size.width + left + right), height: CGFloat(bounds.size.height + top + botton))
}
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let rect = self.enlargedRect
if rect.equalTo(bounds) {
return super.hitTest(point, with: event)
}
return rect.contains(point) ? self : nil
}
//方法二
var margin: CGFloat{
get {
if let accpetEventInterval = objc_getAssociatedObject(self, &cs_associatedKeys.marginKey) as? CGFloat {
return accpetEventInterval
}
return 0.0
}
set {
objc_setAssociatedObject(self, &cs_associatedKeys.marginKey, newValue as CGFloat, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
var bounds = self.bounds
bounds = bounds.insetBy(dx: -margin, dy: -margin)
return bounds.contains(point)
}
}
使用
let btn = UIButton()
btn.frame = CGRect(x: 100, y: 100, width: 40, height: 40)
//方法一
// btn.setEnlargeEdgeWith(top: 100, right: 100, bottom: 100, left: 100)
//方法二
btn.margin = 100
btn.backgroundColor = UIColor.red
btn.addTarget(self, action: #selector(ViewController.didTap), for: UIControlEvents.touchUpInside)
self.view.addSubview(btn)