怎樣來實現(xiàn)這個功能呢杯拐?又有多少種方式可以實現(xiàn)呢霞篡?下面一一來講。
- 理解事件傳遞過程端逼,用這個來實現(xiàn)擴大點擊范圍
- 使用Runtime機制擴大點擊范圍
事件傳遞過程
當(dāng)用戶點擊屏幕后朗兵,UIApplication 先響應(yīng)事件,然后傳遞給UIWindow顶滩。如果window可以響應(yīng)余掖。就開始遍歷window的subviews。遍歷的過程中礁鲁,如果第一個遍歷的view1可以響應(yīng)盐欺,那就遍歷這個view1的subviews(依次這樣不停地查找,直至查找到合適的響應(yīng)事件view)仅醇。如果view1不可以響應(yīng)冗美,那就開始對view2進行判斷和子視圖的遍歷。依次類推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:方法的實現(xiàn)可能是如下的:
// 因為所有的視圖類都是繼承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. 判斷點在不在當(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)前控件上的坐標系轉(zhuǎn)換成子控件上的坐標系
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;
}
有了以上的了解宵喂,我們可以利用這個來實現(xiàn)UIButton的點擊范圍,雖然不是那么優(yōu)雅:先來看一下效果
其實就是自定義view柱衔,實現(xiàn)hitTest:withEvent:方法樊破,里面加入一個按鈕愉棱,這就實現(xiàn)了放大點擊范圍唆铐。以上就是通過截斷事件傳遞的過程來實現(xiàn)放大點擊范圍。
Runtime實現(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è)置為不可點擊奔滑、隱藏艾岂、透明度小于等于0.01或者點擊在按鈕內(nèi)部,則直接執(zhí)行父類方法
if (CGRectEqualToRect(rect, self.bounds) || self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return [super hitTest:point withEvent:event];
}
//判斷點擊是否在放大的范圍內(nèi)
return CGRectContainsPoint(rect, point) ? self : nil;
}
@end
以上的分類也可以使用屬性的方式進行關(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的點擊范圍的實現(xiàn)朋其,有不足之處請大家指正王浴,--