首先要弄懂的是frame 和 bounds 的区别
这张图估计搞ios的亲们相比都看过。
frame: 该view在父view坐标系统中的位置和大小。(参照点是,父亲的坐标系统)
bounds:该view在本地ios15.4正式版坐标approach系统中的位置和大小。(参照点是,本地坐标系统,就相当于ViewB自己的坐标系统,以0,0点为起点)。
frame是参考父view的坐标系来设置自己左上角的位置。
设置bounds可以修改自己坐标系的原点位置,进而影响到其“子view”的显示位置。
下面开始正题,创建一个approachUIButton分类,记得要**#import <appstore;objc/runtime.h>**
对外暴露以下方法,此方法很简单就ios模拟器是动态的为UIbuapplicationtton 增加了可以多增加appear的点击范围的区域的属性。
- (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属性里的需要扩大的上下左右的点击范appearance围加到UIbutton的bounds里面。
而能触发这个方法的-apple (UIView) hitTest:(CGPoint) point withEvent:(UIEvent) event才是重头戏。此方法可以实现点击穿透,点击下层视图的功能。
iOS系统检测到手指触摸(Touch)appstore操作时会将其放入当前approve活动Application的事件队列,UIApplication会从事件队列中取出触摸事件并传递给key window(当前接收用户事件的窗ios16要来了口ios14.4.1更新了什么)处理,window对象首先会使用hitTest:withEapplevent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,称之为hit-test view。
window对象会在首appetite先在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;
}
也就是说当点击的按钮的时候会调用ios系统hitTest:withEvent:方法寻找响应者,知道寻找到UIApplication,如果都没则此响应被废弃,如果存在就可以返回当前的view。
小结:
在平时使用过程中,只要加入此分类,在需要使用ios14.4.1更新了什么按钮的地直接添加最ios是苹果还是安卓开始的方法分别ios模拟器加上需要增加的范围就好,要是不想知道那么高深,简单理解就是加了属性之后点击按钮的时候会计算出一个理论上按钮的区域,然后判断你点击的point 是否在这个心的rect中如果在就等于你成功的点击了按钮,就是这么简单。