iOS扩大UIButton按钮的可点击区域

为什么80%的码农都做不了架构师?>>>   hot3.png

一、开发中遇到的问题

我们在开发时有时遇到这中情况:UI给的图片很小,button的点击区域要求比较大。如果用 setBackgroundImage: 方式设置图片会导致图片也跟着button的frame放大,如果使用 setImage: 设置图片,图片虽然不会跟着button的frame 发生变化,但是想要调整好图片显示的位置确很麻烦。

二、分析解决方法

因此最好的办法就是:设置button的大小刚好就是图片的大小,此时再调整图片frame的时候就非常方便了 (button的frame就是图片的frame)。接下来要解决的问题就是扩大button的点击区域。

三、解决方案

1、在原来button的基础上再添加一个透明的大button(不推荐)

2、继承 UIButton 然后重写 pointInside:(CGPoint)point withEvent:(UIEvent *)event 方法

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    // 若原热区小于44x44,则放大热区,否则保持原大小不变
    CGFloat deltaW = MAX(44 - bounds.size.width, 0);
    CGFloat deltaH = MAX(44 - bounds.size.height, 0);
    bounds = CGRectInset(bounds, -deltaW * 0.5, -deltaH * 0.5);
    return CGRectContainsPoint(bounds, point);
}

使用:

    ZHButton *btn = [[ZHButton alloc] initWithFrame:CGRectMake(100, 100, 20, 20)];
    [btn addTarget:self action:@selector(log) forControlEvents:UIControlEventTouchUpInside];
    btn.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:btn];

虽然button 的宽和高都只有 20 但是按钮能响应点击的热点区域却在原点击区域基础上上下左右各扩展了 (44 - 20)/ 2 = 21 .如此一来button 的可点击区域就非常大了,而且button 的frame也没有受到影响。

3、给button添加一个分类,使用对象关联设置button 的热点区域

其实其思路和方法二一致,不过使用起来更方便了。

.h 文件

@interface UIButton (EnlargeArea)

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

@end

.m 文件

#import 
@implementation UIButton (EnlargeArea)
static char topNameKey;
static char rightNameKey;
static char bottomNameKey;
static char leftNameKey;

- (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;
    }
}


- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.isHidden||!self.isUserInteractionEnabled) {
        return NO;
    }
    CGRect responseArea = [self enlargedRect];
    return CGRectContainsPoint(responseArea, point);
}
@end

4.使用objc运行时交换 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 方法的实现,在自己的实现逻辑中进行判断,其中 extendedHitArea属性 是你要扩展的可以点击的区域,在pointInside方法里判断点击的坐标是否在可响应点击区域,如果在返回YES,否则返回NO 。

// extendHitArea 是暴露出来的一个属性 。
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalM = class_getInstanceMethod([self class], @selector(pointInside:withEvent:));
        Method swzM = class_getInstanceMethod([self class], @selector(swz_pointInside:withEvent:));
        BOOL addMethod = class_addMethod([self class], @selector(pointInside:withEvent:), method_getImplementation(swzM), method_getTypeEncoding(swzM));
        if (addMethod) {
            class_replaceMethod([self class], @selector(swz_pointInside:withEvent:), method_getImplementation(originalM), method_getTypeEncoding(originalM));
        } else {
            method_exchangeImplementations(originalM, swzM);
        }
    });
}

- (void)setExtendedHitArea:(CGRect)extendedHitArea {
    objc_setAssociatedObject(self, @selector(extendedHitArea), @(extendedHitArea), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGRect)extendedHitArea {
    CGRect rect = [objc_getAssociatedObject(self, @selector(extendedHitArea)) CGRectValue];
    if (CGRectEqualToRect(rect, CGRectZero)) {
        return self.bounds;
    }
    return rect;
}

/*!
 @method 可以响应点击事件的rect
 */
- (CGRect)responseHitArea {
    CGRect extendedHitArea = [objc_getAssociatedObject(self, @selector(extendedHitArea)) CGRectValue];
    if (!CGRectEqualToRect(extendedHitArea, CGRectZero)) {
        return                       CGRectMake(
                                     self.bounds.origin.x-extendedHitArea.origin.x,
                                     self.bounds.origin.y-extendedHitArea.origin.y,
                                     self.bounds.size.width+extendedHitArea.origin.x+extendedHitArea.size.width,
                                     self.bounds.size.height+extendedHitArea.origin.y+extendedHitArea.size.height);

    } else {
        return self.bounds;
    }
}

- (BOOL)swz_pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    BOOL result = [self swz_pointInside:point withEvent:event];
    CGRect responseHitArea = [self responseHitArea];
    BOOL ret = ( result
                ||CGRectEqualToRect(responseHitArea, CGRectZero)
                ||!self.isUserInteractionEnabled
                ||self.isHidden);
    if (ret) return result;
    return CGRectContainsPoint(responseHitArea, point);
}

以上就是扩大按钮点击区域的4中常见方法 ,第3中和第四种,不仅仅适用于按钮,也同样适用于其他view组件 。例如:在view上添加一个手势 ,第三四中方法可以直接写成 一个view的分类,拖到项目直接使用简单方便 。

转载于:https://my.oschina.net/zhxx/blog/833549

你可能感兴趣的:(iOS扩大UIButton按钮的可点击区域)