在平时开发的时候经常会遇到UI的给的设计图button很小,但是老板就说不好点击,让你凭空增加点击区域的情况,此时心里是矛盾的,不过想着开发一直都是背锅侠,啥都得接着,这个难不倒我们程序员是吧,办法走起。
事先声明,以下的说的方法并不是我自己想到的哈,而是参看公司的旧代码不知道哪位大哥从哪里搞过来的,不过觉得思路还是有可以借鉴的地方哈,所以学习学习。
其实思路很简单,就是动态的增加了button的bounds,这样暗暗的给自己增加了尺寸,就不怕老板点不到了。
先展开一点点,frame 和bounds 两个东西还是挺唬人的。先上一张图已是原理。
这张图估计搞ios的亲们相比都看过啦。
frame: 该view在父view坐标系统中的位置和大小。(参照点是,父亲的坐标系统)
bounds:该view在本地坐标系统中的位置和大小。(参照点是,本地坐标系统,就相当于ViewB自己的坐标系统,以0,0点为起点)。
frame是参考父view的坐标系来设置自己左上角的位置。
设置bounds可以修改自己坐标系的原点位置,进而影响到其“子view”的显示位置。
其次还需要用到runtime的一些知识,因为大家知道默认情况下载分类中是不能增加属性的,不过oc是一个底线比较低的语言,支持强上的,这时候就用到了runtime,不过本人也是一知半解,所以找了牛人的博客,并且献上了自己的膝盖大神runtime系列博客,大家可以自行学习呀,我看过两遍受益匪浅呀。(ps: 啥时候能写出这种水平的博客呢!!!)
这算提前热身科普一下基础知识,下面开始正题,创建一个UIButton分类,记得要#import
对外暴露以下方法,此方法很简单就是动态的为UIbutton 增加了可以多增加的点击范围的区域的属性。
- (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中如果在就等于你成功的点击了按钮,就是这么简单。
参考博客:
http://www.jianshu.com/p/ef83a798121c