超出父视图的button,可以将button的frame按照(0,0)点设置好,然后更新center,或者直接将button设置初始坐标在超出视图的位置,这很简单!
难点在于,如何让超出父视图的button部分响应我们的点击事件!
首先需要普及的是点击事件的响应链!
应用程序在接收到用户的点击事件后,UIKit会将屏幕接收到的事件(UIEvent)放在一个事件队列里,然后UIApplication的单例会从这个事件
队列里依次取出事件,然后开始分发事件!
在分发过程中首先需要确定事件发生的点在哪个视图中,于是会依次从UIWindow开始调用hitTest:withEvent方法来确定事件是否发生在
该视图中,如果在该视图中,则会对该视图的子
视图继续调用这个方法进行查找,直到找到该事件发生的视图!
在查找过程中会通过pointInside:withEvent来确定事件发生的点是否在该视图中,如果pointInside方法返回了NO,则hitTest方法将不再对
该视图的子视图继续查找,如果返回了YES,说明事件发生的点在这个视图内,则会继续对这个视图的子视图进行查找!
这个查找过程不会只进行一次,我的简单测试中都是调用两次,没有找到相关原因,猜测是针对确定事件点的位置和多点手势或拖拽等复杂手势而产生的重复
确定.
在找到该事件发生的位置后,会看该视图的响应者(继承自UIResponser的对象)是否对这个事件进行了响应处理,如果没有处理,则将事件
对其上一级进行传递,直到有响应者处理为止,如果直到最后都没有响应者对这个事件进行处理,则将该事件丢弃!
具体的事件响应传递链:subView->superView->...->UIViewController->UIWindow->UIApplication->UIAppDelegate,也就是如果一个时间在传递到
UIAppDelegate还没有处理的话就被丢弃!
原理不想看直接看这里
所以针对超出父视图的button的点击,首先要让父视图的hitTest方法返回不为nil,也就是如果点击范围在button范围内,就让父视图的
hitTest方法返回响应的按钮对象,这个过程中需要对点击事件的坐标进行处理,可以通过调用button的pointInside来确定点击的点是否在
按钮上,以此来判断父视图是该返回按钮对象还是nil!
具体操作过程如下
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 将点击事件在父视图的坐标转为参照按钮的坐标,这样方便确定点击是否在按钮上
CGPoint btnPoint = [self.tstBtn convertPoint:point fromView:self];
if ([self.tstBtn pointInside:btnPoint withEvent:event]) {
return self.tstBtn;
}else{
return [super hitTest:point withEvent:event];
}
}
这样,在确定事件发生的点在button上之后,就可以通过正常的响应者链来确定事件的传递了!
通过这种方法,还可以实现许多效果,比如屏蔽一些操作,或者拦截操作等!
经过一番无聊的折腾倒腾出两张图
hello是个button,白色的框是个view!
这是针对在button外部点击实现点击button效果,点击白框可以触发按钮事件,没什么太大的用!
这个是在按钮上面覆盖一个带有白边的View,和上面不同的是只更改了view的frame!
其实我是为了在tabBar切换时加一个边框移动的动画才倒腾出来这玩意,我知道可以使用button的drawRect画个边框出来,但是在tabBar切换到另一个按钮时就不能平移过去了!而且这个知识点绝对不止这么点用的!
而针对响应传递链可以实现一个操作改变一系列的状态!
比如在ViewController中的touchesBegan中调用[super touchesBegan]方法,可以在ViewController响应的同时,UIWindow也进行响应!
需要注意的是这里的super并非指ViewController的父类!而是指它的下一个响应者,即nextResponser!