前言
前端頁面開發(fā)中,經(jīng)常用到按鈕,按鈕的圖片是比較小的,但是需要擴大按鈕的點擊范圍.這時,我們一般會想到在響應(yīng)鏈上動手
hitTest
/// 返回視圖層次結(jié)構(gòu)(包括它自己)中包含指定點的接收方的最遠后代。
func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView?
- 這個方法通過調(diào)用每個子視圖的point(inside:with:)方法來遍歷視圖層次結(jié)構(gòu),以確定哪個子視圖應(yīng)該接收觸摸事件。如果point(inside:with:)返回true,則子視圖的層次結(jié)構(gòu)將被類似地遍歷,直到找到包含指定點的最前面的視圖為止。如果一個視圖不包含這個點,它的視圖層次結(jié)構(gòu)分支將被忽略。你很少需要自己調(diào)用這個方法,但是你可以覆蓋它來隱藏子視圖中的觸摸事件。
- 此方法忽略隱藏的、禁用用戶交互或alpha級別小于0.01的視圖對象。在確定命中時,此方法不考慮視圖的內(nèi)容。因此,即使指定的點位于視圖內(nèi)容的透明部分,視圖仍然可以被返回。
- 位于接收方邊界之外的點永遠不會被報告為命中,即使它們實際上位于接收方的子視圖之一。如果當(dāng)前視圖的clipsToBounds屬性設(shè)置為false并且受影響的子視圖擴展超出了視圖的邊界,就會發(fā)生這種情況。
實現(xiàn)方案
- 擴大的點擊區(qū)域,我們不用去給按鈕添加上下左右單獨的屬性,直接用
UIEdgeInsets即可,方便直觀,不容易設(shè)置錯誤 - 下面是實現(xiàn)代碼
open class EnlargeEdgeButton: UIButton {
open var enlargeEdge: UIEdgeInsets = .zero
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if alpha == 0 || isHidden == true || enlargeEdge == .zero {
return super.hitTest(point, with: event)
}
let rect = bounds.enlargeRect(edgeInsets: enlargeEdge)
return rect.contains(point) ? self : nil
}
}
extension CGRect {
public func enlargeRect(edgeInsets: UIEdgeInsets) -> CGRect {
return CGRect(x: minX - edgeInsets.left, y: minY - edgeInsets.top, width: width + (edgeInsets.left + edgeInsets.right), height: height + (edgeInsets.top + edgeInsets.bottom))
}
}
- 這是特別需要注意的是,按鈕的幾種特殊情況,需要做處理
-
alpha == 0,透明度為0時,相當(dāng)是按鈕是看不見的,那么這時,我們的期望時按鈕是隱藏狀態(tài)的,如果這里加了擴大按鈕點擊范圍的代碼,會導(dǎo)致雖然按鈕是隱藏的,但是點擊按鈕的放大區(qū)域,還是會響應(yīng)事件,這會出現(xiàn)一個非常奇怪的bug,而且bug還不好找,本人在工作中就出現(xiàn)過這個失誤
2.isHidden == true,同理,隱藏狀態(tài)時更不應(yīng)該去擴大點擊范圍了
3.enlargeEdge == .zero,擴大區(qū)域的edge為.zero,相當(dāng)于是沒有加點擊范圍,那就可以直接調(diào)用父類方法,不做額外處理
分類 VS 子類化
- 個人覺得的分類的侵入性比較大,如果給
UIButton添加分類屬性enlargeEdge,導(dǎo)致任何繼承自UIButton的類都有這個功能.有可能人家壓根就不想要這個功能,但是你本來就有這個功能 - 子類化的話,你想要這個功能,你就用這個類,或者繼承自這個類,能夠做到精準控制