Implementação de expansão da área de clique do iOS UIButton

A primeira coisa a entender é a diferença entre frame e limites

Esta foto é estimada que os parentes que estão envolvidos em ios a viram.
frame: A posição e o tamanho da vista no sistema de coordenadas da vista principal. (O ponto de referência é o sistema de coordenadas do pai)
limites: A posição e o tamanho da vista no sistema de coordenadas local. (O ponto de referência é o sistema de coordenadas local, que é equivalente ao próprio sistema de coordenadas do ViewB, com 0,0 como ponto de partida).

O quadro refere-se ao sistema de coordenadas da vista principal para definir a posição de seu canto superior esquerdo.
A definição de limites pode modificar a posição de origem de seu próprio sistema de coordenadas, que por sua vez afeta a posição de exibição de sua "visualização filha".

Vamos começar o tópico, crie uma categoria UIButton, lembre-se de **#import <objc/runtime.h>**

O método a seguir é exposto ao mundo exterior. Esse método é muito simples para adicionar dinamicamente as propriedades da área onde o intervalo de cliques pode ser aumentado para o botão UI.

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

/**
 返回增加范围后的结果

 @return return value description
 */
- (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];//获得了获得新范围的CGRect
    
    if (CGRectEqualToRect(rect, self.bounds))  //如果没有增加点击范围就调用super 看看点击范围是不是在父控件上
    {
        return [super hitTest:point withEvent:event];
    }
    //如果触摸点为在增加后的范围内就返回此view为触摸点
    return CGRectContainsPoint(rect, point) ? self : nil;
}
复制代码

先说不重要的** enlargedRect**方法就是把之前添加到UIbutton属性里的需要扩大的上下左右的点击范围加到UIbutton的bounds里面。
而能触发这个方法的- (UIView) hitTest:(CGPoint) point withEvent:(UIEvent) event才是重头戏。此方法可以实现点击穿透,点击下层视图的功能。
iOS系统检测到手指触摸(Touch)操作时会将其放入当前活动Application的事件队列,UIApplication会从事件队列中取出触摸事件并传递给key window(当前接收用户事件的窗口)处理,window对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,称之为hit-test view。
window对象会在首先在view hierarchy的顶级view上调用hitTest:withEvent:,此方法会在视图层级结构中的每个视图上调用pointInside:withEvent:,如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是hit-test view。
先看一下hitTest:withEvent:的实现原理

// 因为所有的视图类都是继承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//    NSLog(@"%@--hitTest",[self class]);
//    return [super hitTest:point withEvent:event];


    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;

    // 2. 判断点在不在当前控件
    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];

        // 把当前控件上的坐标系转换成子控件上的坐标系
     CGPoint childP = [self convertPoint:point toView:childView];

       UIView *fitView = [childView hitTest:childP withEvent:event];


        if (fitView) { // 寻找到最合适的view
            return fitView;
        }


    }

    // 循环结束,表示没有比自己更合适的view
    return self;

}
复制代码

也就是说当点击的按钮的时候会调用hitTest:withEvent:方法寻找响应者,知道寻找到UIApplication,如果都没则此响应被废弃,如果存在就可以返回当前的view。

小结:
在平时使用过程中,只要加入此分类,在需要使用按钮的地直接添加最开始的方法分别加上需要增加的范围就好,要是不想知道那么高深,简单理解就是加了属性之后点击按钮的时候会计算出一个理论上按钮的区域,然后判断你点击的point 是否在这个心的rect中如果在就等于你成功的点击了按钮,就是这么简单。

Acho que você gosta

Origin juejin.im/post/7078159492491247652
Recomendado
Clasificación