uicollectionViewios 学习iOS开发技术分享

事件传递应用-范围的修改和封装

2017-04-06  本文已影响704人  喵子G

touch事件的响应和传递原理见文章:http://www.jianshu.com/p/26b67a477fc3
通过上一篇笔记的总结,一个视图的是否可以点击、点击范围是可以自定义的,这里写一个实际开发中遇到的需要修改点击范围的实际用途。
这个界面效果如图:

preImage.gif

可以看到,在点击搜索框后,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占位。

上一篇下一篇

猜你喜欢

热点阅读