iOS下原生的UIAlertView/UIActionSheet/UIAlertController有时候无法满足我们的需求,我们就会自定义一些弹出框。一般的自定义方案通常是定义一个UIView或者UIView的子类,然后在上面添加一些组件,最后添加到我们指定的parentView上,中间可能还会加几个比较炫酷的动画,这样就达到了我们某些弹出框或者菜单的效果。问题出在如果我们添加到某个View上,但是该View不是全屏的View,我们点击某些地方可能就无法获取到触摸事件,我们的菜单就无法得到响应。
我们定义菜单的时候一般会设计如下所示的两个方法:
- (NSInteger)showInView:(UIView *)view animated:(BOOL)animated;
- (void)dismissAnimated:(BOOL)animated
每当我们需要展示菜单的时候,我们就调用showInView:方法,把菜单添加到view上,反之,当菜单需要结束的时候,就会被dismiss。
之前也有一篇自定义菜单的文章:Block代码继续执行的AlertView,当时的重点不在于介绍AlertView,大家可以下载体验下。
很多时候我们设计的菜单都是非阻塞用户操作的,意思就是点击空白的时候,不能因为有菜单,而影响用户的操作,至少是点击空白的时候,要将菜单消失掉。当我们的view不是全屏的时候,我们如何能做到点击view区域外面还能dismiss当前的菜单呢?
之前的文章介绍过iOS的事件分发模块,大家可以查看之前的博客get这些知识,HitTest其实是给View第一次机会,去处理一些东西。有一点值得肯定的是,如果touchEvent发生在View’s的frame之外,则该View无法检测到这个touchEvent。所以我们要想实现博客所描述的功能,我们只能依赖于新插入的View。思路很简单,每次菜单展示的时候,插入一个透明的View到当前的window上,抓住hitTest给的这次机会,去根据当前情况分发事件。
1、如果点击区域在菜单内,则把事件分发给菜单
2、否则则直接dismiss掉菜单
这里提供一些简单的事件分发代码,以供大家参考
1 2 3 4 5 6 7 8 9 10 11 |
__weak STAlertView *weakSelf = self; self.hitTestView = [[UIView alloc] init]; self.hitTestView.hitTestBlock = ^(CGPoint point, UIEvent *event, BOOL *returnSuper) { CGRect contentRect = [weakSelf.superview convertRect:weakSelf.frame toView:nil]; if (!CGPointInRect(point, contentRect)){ [weakSelf dismissActionFired:nil]; *returnSuper = YES; } return (UIView *)nil; }; self.hitTestView.backgroundColor = [UIColor clearColor]; |
然后在show的时候,将self.hitTestView添加到window上,dismiss的时候,从window移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (NSInteger)showInView:(UIView *)view animated:(BOOL)animated { self.hitTestView.frame = [UIScreen mainScreen].bounds; [[UIApplication sharedApplication].delegate.window addSubview:self.hitTestView]; ... } - (void)_dismissAnimated:(BOOL)animated completion:(void (^)(BOOL))_completion { ... void (^completion)(BOOL) = ^(BOOL finished) { [self.hitTestView removeFromSuperview]; }; if (animated) { [UIView animateWithDuration:0.35 animations:... completion:completion]; } else { ... completion(YES); } } |
这样,就实现了我们的需求,思路比较简单,就是在window的最顶层添加一个View用于检测触摸事件,当检测到事件之后,根据当前条件来处理事件。
大家可以去我的github下载STKitDemo,里面有STAlertView的相关的实现,使用方式在组件->查看大图->菜单中可以看到。