事件传递应用-范围的修改和封装
touch事件的响应和传递原理见文章:http://www.jianshu.com/p/26b67a477fc3
通过上一篇笔记的总结,一个视图的是否可以点击、点击范围是可以自定义的,这里写一个实际开发中遇到的需要修改点击范围的实际用途。
这个界面效果如图:
可以看到,在点击搜索框后,UINavigationController的顶栏隐藏,并且下面出现一个视图遮挡住下面的列表。
我们可能首先想到在当前控制器的view上面add一个subView挡住当前控制器的view。但仔细看下键盘收起后:
WX20170406-174949@2x.png实际这个后弹出的view是遮挡住UITabBarController的底栏的,所以它一定不是当前控制器的subView。
同时,还要考虑到,这个搜索界面的搜索框,就是当前控制器的那个搜索框,而且还是能响应触摸事件的。
所以从基本功能需求的角度看,这里就需要即弹出一个视图,它的层级是在屏幕最上面,同时还需要把搜索框漏出来,并且还能够响应屏幕触摸事件。
从代码结构优化的角度看,既然这个界面和系统默认UISearchController的实现基本相似,我们最好能够创建出一个和UISearchController类似用法的搜索控件,而不是把搜索视图的弹出、收起、数据刷新分散写在多个地方。
所以当前的目标就是创建一个可以这样使用的搜索控件:
/**
所有关于SearchController的声明、创建、添加、回调的代码都放在当前控制器中
*/
// 自定义成员变量的声明
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) JKRSearchController *searchController;
...
// 自定义SearchController的searchBar添加到tableView上面
[self.tableView setTableHeaderView:self.searchController.searchBar];
...
// 自定义SearchController中输入的回调
- (void)updateSearchResultsForSearchController:(JKRSearchController *)searchController {
NSString *searchText = searchController.searchBar.text;
if (!(searchText.length > 0)) return;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(SELF CONTAINS %@)", searchText];
JKRSearchResultViewController *resultController = (JKRSearchResultViewController *)searchController.searchResultsController;
resultController.filterDataArray = [self.dataArray filteredArrayUsingPredicate:predicate];
}
...
// 自定义SearchController的懒加载
- (JKRSearchController *)searchController {
if (!_searchController) {
JKRSearchResultViewController *resultSearchController = [[JKRSearchResultViewController alloc] init];
_searchController = [[JKRSearchController alloc] initWithSearchResultsController:resultSearchController];
_searchController.searchBar.placeholder = @"搜索";
_searchController.hidesNavigationBarDuringPresentation = YES;
_searchController.searchResultsUpdater = self;
}
return _searchController;
}
这就是目前的需求,自定义一个可以这样使用SearchController,首先查看一下系统自带SearchController搜索状态下的层级结构:
WX20170406-183721@2x.png可以发现,当进入搜索状态后,UISearchController是视图添加到了当前window的rootViewController上面,并在UISearchController上面添加resultViewController的view,并且把searchBar从控制器上面移到自己的view的最上层。
首先我们先实现简单的弹出自定义SearchController的视图,并把它覆盖在窗口的最上层,这个很容易办到:
[[UIApplication sharedApplication].keyWindow.rootViewController.view addSubview:self.view];
当前的状态,视图结构如下:
WX20170406-182645@2x.png自定义的SearchController的view挡住了searchBar的textField,所以自定义searchBar无法响应触摸事件。
现在有两种处理思路:
第一种方案:像系统一样,想办法把searchBar移到SearchController的view上面来,并且在移动的过程中处理好隐藏UINavigationBar的动画同步。
第二种方案:让SearchController的view顶部的垂直高度64px区域的touch事件不响应,这样它的下面的searchBar就能够响应。
第一种方案我没有找到好的解决办法,而第二种方案就非常简单了,创建一个UIView替换成自定义SearchController的self.view,然后重写它的pointInside方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (point.y < 64) {
return NO;
} else {
return [super pointInside:point withEvent:event];
}
}
下面进一步优化下结构,这个功能以后很可能经常用到,但是仅仅为了一个改变touch区域就创建一个UIView替换控制器的view,多创建了一对UIView文件,不如把它封装成UIView的扩展:
#import <UIKit/UIKit.h>
@interface UIView (JKRTouch)
// 是否能够响应touch事件
@property (nonatomic, assign) BOOL unTouch;
// 不响应touch事件的区域
@property (nonatomic, assign) CGRect unTouchRect;
@end
#import "UIView+JKRTouch.h"
#import <objc/runtime.h>
@implementation UIView (JKRTouch)
+ (void)load {
method_exchangeImplementations(class_getInstanceMethod([UIView class], @selector(pointInside:withEvent:)), class_getInstanceMethod([UIView class], @selector(jkr_pointInside:withEvent:)));
}
- (void)setUnTouch:(BOOL)unTouch {
objc_setAssociatedObject(self, @"JKR_UN_TOUCH", [NSNumber numberWithInt:unTouch], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)unTouch {
return objc_getAssociatedObject(self, @"JKR_UN_TOUCH") ? [objc_getAssociatedObject(self, @"JKR_UN_TOUCH") boolValue] : NO;
}
- (void)setUnTouchRect:(CGRect)unTouchRect {
objc_setAssociatedObject(self, @"JKR_UN_TOUCH_RECT", [NSValue valueWithCGRect:unTouchRect], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CGRect)unTouchRect {
return objc_getAssociatedObject(self, @"JKR_UN_TOUCH_RECT") ? [objc_getAssociatedObject(self, @"JKR_UN_TOUCH_RECT") CGRectValue] : CGRectZero;
}
- (BOOL)jkr_pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (self.unTouch) return NO;
if (self.unTouchRect.origin.x == 0 && self.unTouchRect.origin.y == 0 && self.unTouchRect.size.width == 0 && self.unTouchRect.size.height == 0) {
return [self jkr_pointInside:point withEvent:event];
} else {
if (CGRectContainsPoint(self.unTouchRect, point)) return NO;
else return [self jkr_pointInside:point withEvent:event];
}
}
@end
这样,就不需要替换自定义SearchController的view,只需要一行代码:
self.view.unTouchRect = CGRectMake(0, 0, self.view.width, 64);
就可以实现touch事件的穿透。
Demo地址:https://github.com/Joker-388/JKRCustomSearchController
⚠️:其实系统的UISearchController的视图是present出来的,把searchBar移到UISearchController上面,并在原来的searchBar的位置用一个UIView占位。