美文网首页uicollectionViewios 学习iOS开发技术分享
事件传递应用-范围的修改和封装

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

作者: 喵子G | 来源:发表于2017-04-06 18:56 被阅读704次

    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占位。

    相关文章

      网友评论

        本文标题:事件传递应用-范围的修改和封装

        本文链接:https://www.haomeiwen.com/subject/xsvqattx.html