// 作用: 去尋找最適合的View
// 什么時(shí)候調(diào)用: 當(dāng)一個(gè)事件傳遞給當(dāng)前View,就會(huì)調(diào)用.
// 返回值: 返回的是誰,誰就是最適合的View(就會(huì)調(diào)用最適合的View的touch方法)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event)
}
hitTest
的底層實(shí)現(xiàn):
- 1.先看自己是否能接受觸摸事件
- 2.再看觸摸點(diǎn)是否在自己身上
- 3.從后往前遍歷子控件裁僧,拿到子控件后订讼,再次重復(fù)1,2步驟挟憔,要把父控件上的坐標(biāo)點(diǎn)轉(zhuǎn)換為子控件坐標(biāo)系下的點(diǎn)狸窘,再次執(zhí)行hitTest方法
- 4.若是最后還沒有找到合適的view墩朦,那么就return self,自己就是合適的view
備注:當(dāng)控件接收到觸摸事件的時(shí)候翻擒,不管能不能處理事件氓涣,都會(huì)調(diào)用hitTest方法
應(yīng)用實(shí)例
1.擴(kuò)大UIButton的響應(yīng)熱區(qū)
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里實(shí)現(xiàn)
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超出了父view
的bounds
響應(yīng)事件
demo.png
重載父view
的hitTest(_ point: CGPoint, with event: UIEvent?)
方法
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subView in self.subviews {
// 把父類point點(diǎn)轉(zhuǎn)換成子類坐標(biāo)系下的點(diǎn)
let convertedPoint = subView.convert(point, from: self)
// 注意點(diǎn):hitTest方法內(nèi)部會(huì)調(diào)用pointInside方法牛哺,詢問觸摸點(diǎn)是否在這個(gè)控件上
// 根據(jù)point,找到適合響應(yīng)事件的這個(gè)View
let hitTestView = subView.hitTest(convertedPoint, with: event)
if hitTestView != nil {
return hitTestView
}
}
return nil
}
3.使部分區(qū)域失去響應(yīng).
tableView.png
場景需求:如圖,tableView
占整個(gè)屏幕,tableView
底下是一個(gè)半透明的HUD
,點(diǎn)擊下面沒有內(nèi)容區(qū)域,要讓HUD
去響應(yīng)事件.
在自定義的tableView
中重載hitTest
方法
// tableView會(huì)攔截底部`backgroundView`事件的響應(yīng),
// 實(shí)現(xiàn)點(diǎn)擊tableViewCell 之外的地方,讓tableView底下的backgroundView響應(yīng)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 那么事件就不由當(dāng)前控件處理
return nil;
}
4.讓非scrollView
區(qū)域響應(yīng)scrollView
拖拽事件
scrollView.png
如圖,這是一個(gè)使用scrollView
自定義實(shí)現(xiàn)的卡片式輪播器劳吠,如何實(shí)現(xiàn)拖拽scrollView
兩邊的view
區(qū)域引润,和拖拽中間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失去響應(yīng)
class NoEventButton: UIButton {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil
}
}
參考:iOS事件響應(yīng)鏈中Hit-Test View的應(yīng)用
ios開發(fā)事件處理之 四:hittest方法的底層實(shí)現(xiàn)與應(yīng)用