iOS擴(kuò)大UIButton的點(diǎn)擊范圍

怎樣來實(shí)現(xiàn)這個功能呢?又有多少種方式可以實(shí)現(xiàn)呢?下面一一來講。

  • 理解事件傳遞過程,用這個來實(shí)現(xiàn)擴(kuò)大點(diǎn)擊范圍
  • 使用Runtime機(jī)制擴(kuò)大點(diǎn)擊范圍

事件傳遞過程

當(dāng)用戶點(diǎn)擊屏幕后,UIApplication 先響應(yīng)事件,然后傳遞給UIWindow。如果window可以響應(yīng)。就開始遍歷window的subviews。遍歷的過程中,如果第一個遍歷的view1可以響應(yīng),那就遍歷這個view1的subviews(依次這樣不停地查找,直至查找到合適的響應(yīng)事件view)。如果view1不可以響應(yīng),那就開始對view2進(jìn)行判斷和子視圖的遍歷。依次類推view3,view4…… 如果最后沒有找到合適的響應(yīng)view,這個消息就會被拋棄。這個就是iOS中的事件鏈,如下圖所示

事件鏈條

然而事件的響應(yīng)鏈條是事件鏈條的逆向,根據(jù)視圖層級的添加順序從后往前的

關(guān)鍵的兩個方法:UIView方法

// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

在系統(tǒng)的UIView中,以下4個條件不執(zhí)行事件響應(yīng)。

1.隱藏(hidden=YES)的視圖
2.禁止用戶操作(userInteractionEnabled=NO)的視圖
3.alpha<0.01的視圖
4.視圖超出父視圖的區(qū)域

hitTest:withEvent:方法的實(shí)現(xiàn)可能是如下的:

// 因?yàn)樗械囊晥D類都是繼承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1.判斷當(dāng)前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    
    // 2. 判斷點(diǎn)在不在當(dāng)前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    
    // 3.從后往前遍歷自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        // 把當(dāng)前控件上的坐標(biāo)系轉(zhuǎn)換成子控件上的坐標(biāo)系
        CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { // 尋找到最合適的view
            return fitView;
        }
    }
    // 循環(huán)結(jié)束,表示沒有比自己更合適的view
    return self;
}

有了以上的了解,我們可以利用這個來實(shí)現(xiàn)UIButton的點(diǎn)擊范圍,雖然不是那么優(yōu)雅:先來看一下效果

簡單示例

其實(shí)就是自定義view,實(shí)現(xiàn)hitTest:withEvent:方法,里面加入一個按鈕,這就實(shí)現(xiàn)了放大點(diǎn)擊范圍。以上就是通過截斷事件傳遞的過程來實(shí)現(xiàn)放大點(diǎn)擊范圍。

Runtime實(shí)現(xiàn)方式如下:

@interface UIButton (EnlargeTouchArea)

- (void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left;

@end

#import "UIButton+EnlargeTouchArea.h"
#import <objc/runtime.h>

static char topNameKey;
static char rightNameKey;
static char bottomNameKey;
static char leftNameKey;

@implementation UIButton (EnlargeTouchArea)

- (void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left {
    objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (CGRect)enlargedRect {
    NSNumber* topEdge = objc_getAssociatedObject(self, &topNameKey);
    NSNumber* rightEdge = objc_getAssociatedObject(self, &rightNameKey);
    NSNumber* bottomEdge = objc_getAssociatedObject(self, &bottomNameKey);
    NSNumber* leftEdge = objc_getAssociatedObject(self, &leftNameKey);
    if (topEdge && rightEdge && bottomEdge && leftEdge) {
        return CGRectMake(self.bounds.origin.x - leftEdge.floatValue,
                          self.bounds.origin.y - topEdge.floatValue,
                          self.bounds.size.width + leftEdge.floatValue + rightEdge.floatValue,
                          self.bounds.size.height + topEdge.floatValue + bottomEdge.floatValue);
    } else {
        return self.bounds;
    }
}

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
    CGRect rect = [self enlargedRect];
    //如果按鈕設(shè)置為不可點(diǎn)擊、隱藏、透明度小于等于0.01或者點(diǎn)擊在按鈕內(nèi)部,則直接執(zhí)行父類方法
    if (CGRectEqualToRect(rect, self.bounds) || self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
        return [super hitTest:point withEvent:event];
    }
    //判斷點(diǎn)擊是否在放大的范圍內(nèi)
    return CGRectContainsPoint(rect, point) ? self : nil;
}

@end

以上的分類也可以使用屬性的方式進(jìn)行關(guān)聯(lián)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

.m
static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if (value) {
        UIEdgeInsets edgeInsets;
        [value getValue:&edgeInsets];
        return edgeInsets;
    }
    return UIEdgeInsetsZero;
}

以上就是對放大UIButton的點(diǎn)擊范圍的實(shí)現(xiàn),有不足之處請大家指正,--

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容