• 庖丁解牛FPPopover


     

    作者:ani_di
    版权所有,转载务必保留此链接 http://blog.csdn.net/ani_di

    庖丁解牛FPPopover

    FPPopover是一个实现Popover控件的开源项目,比标准控件要强大一些。虽然如此,但是在定制边框弹框时,还是遇到了问题:border=NO并且arrowDirection = FPPopoverNoArrow时多出了一个头。














    因此需要动手修改。好在这份源代码不长,修改代码的时候还可以欣赏一番。

    ARCMacors.h

    这个头文件定义了很多宏,基本上就是把支持arc的代码按照不支持arc的方式些,以便适应老版本。 很简单,不多说。这份头文件可以复用。

    FPPopoverController

    FPPopover主要就3个类,这个是Controller。里面主要由2个view:touchView和contentView。touchView和contentView分别用于事件捕获和内容展示。

    init函数有两个-(id)initWithViewController:(UIViewController*)viewController-(id)initWithViewController:(UIViewController*)viewController。其实主要工作在后一个里面。

    -(id)initWithViewController:(UIViewController*)viewController

    init里面主要是初始化_touchView和_contentView。_touchView的初始化总结为以下

    _touchView = [[FPTouchView alloc] initWithFrame:self.view.bounds];
    _touchView.backgroundColor = [UIColor clearColor];
    _touchView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    _touchView.clipsToBounds = NO;
    [_touchView setTouchedOutsideBlock:^{
        [bself dismissPopoverAnimated:YES];
    }];
    

    Line 1构造FPTouchView,大小等于self.view.bounds。但这时self.view.bounds还没有大小,不过不要紧,后面setupView会重新修改。
    Line 2、3设置背景色和自动调整时大小时的属性,这里是保持宽高不变。
    Line 4设置不剪裁子view。默然是NO,此行意义不大。 Line 5设置点击到外面的事件,默认关闭view。touchView主要就是检测在view外和里面的touch事件。

    _contentView也比较简单

    self.contentSize = CGSizeMake(200, 300); //default size
    _contentView = [[FPPopoverView alloc] initWithFrame:CGRectMake(0, 0, 
                                          self.contentSize.width, self.contentSize.height)];
    [_contentView addContentView:_viewController.view];
    _contentView.title = _viewController.title;
    

    _contentView主要继承外部controller的title和view。在FPPopverView中,这两部分是独立开显示的。

    -(void)setupView

    setupView函数就是具体设置view的的大小。

    self.view.frame = CGRectMake(0, 0, [self parentWidth], [self parentHeight]);
    _touchView.frame = self.view.bounds;
    

    touchView和self.view都等于parent bound。一般情况下,它的parent view是后面的AppDelegate的view,即全屏大小。后面可以看到,这样设置最后怎么区分touch inside和outside。

    contentView的大小在-(CGRect)bestArrowDirectionAndFrameFromView:(UIView*)v设置,相对复杂一些。这里不深究计算方式。

    setupView在很多地方都被调用过:在viewDidLoad、手动present、屏幕旋转、键盘显示等。虽然重复调用的次数比较多,但减轻了代码负担。

    由于setupView调用的地方很多,必须调用setNeedsDisplay强制刷新,否则不会立即显示。

    -(void)presentPopoverFromPoint:(CGPoint)fromPoint

    这个函数主要的作用是设置_parentView。Line 7找到第一个window的第一个view,把自己的view加到里面。 根据事件的响应链window1->controller->views->window2...,self.view,即touchView会比现有的其他view更快响应。

    [self.view removeFromSuperview];
    NSArray *windows = [UIApplication sharedApplication].windows;
    if(windows.count > 0)
    {
        _parentView=nil;
        _window = [windows objectAtIndex:0];
        //keep the first subview
        if(_window.subviews.count > 0)
        {
            _parentView = [_window.subviews objectAtIndex:0];
            [_parentView addSubview:self.view];
            [_viewController viewDidAppear:YES];
        }
    
    }
    

    因为在init里面的bounds是0,因此没有真正显示出来。这里手动显示非常简单,设置好真实的frame即可。代码里还加了一点点小动画。

    [self setupView];
    self.view.alpha = 0.0;
    [UIView animateWithDuration:0.2 animations:^{
    
        self.view.alpha = self.alpha;
    }];
    

    -(void)dismissPopover

    要隐藏一个view方法很多,一般有两种方案:1. 设透明值;2. removeFromSuperview。 在这里用的方案2。这样可以排除self.view潜在的影响,效率方面也会提升一点。 另外有setupView,再次显示出来也不难。

    FPTouchView

    FPTouchView对象只有一个作用:判断点击操作的位置。

    -(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        UIView *subview = [super hitTest:point withEvent:event];
    
        if(UIEventTypeTouches == event.type)
        {
            BOOL touchedInside = subview != self;
            if(!touchedInside)
            {
                for(UIView *s in self.subviews)
                {
                    if(s == subview)
                    {
                        //touched inside
                        touchedInside = YES;
                        break;
                    }
                }            
            }
    
            if(touchedInside && _insideBlock)
            {
                _insideBlock();
            }
            else if(!touchedInside && _outsideBlock)
            {
                _outsideBlock();
            }
        }
    
        return subview;
    }
    

    最重要的是下面几段代码。

    BOOL touchedInside = subview != self;
    if(!touchedInside)
    {
        for(UIView *s in self.subviews)
        {
            if(s == subview)
            {
                //touched inside
                touchedInside = YES;
                break;
            }
        }            
    }
    

    如果hitTest返回的是自身,则一定是在弹窗外部;如果不是,则判断是不是自己的子view。 在 FPPopoverController 的初始化方法里面,_contentView是 _touchView的子view。

    基类的hitTest返回事件位于当前View最远的view(PS:这样才能让最后添加的view最先接收到事件), 如果没有这返回nil。 _touchView本身的frame等于第一个window的第一个view(参见 presentPopoverFromPoint), 一般这个view等于窗口大小。

    作者这里代码很奇怪。判断子view的条件是 touchedInside == NO;而满足此条件则 subview == self。 那么检测self在self.subviews又是为何?

    FPTouchedOutsideBlock & FPTouchedInsideBlock

    这两个block是作者提供出来的回调。不同于以往的delegate模式,这里用的是block。
    FPTouchedOutsideBlock在 FPPopoverController 用于关闭视图。所以修改这个时要注意一下。

    FPPopoverView

    这里面大部分代码都是用Quartz画外面的边框。计算orign稍微复杂一点,画tint的代码比较多。

    回到以前

    最开始的问题是在border=NO并且arrowDirection = FPPopoverNoArrow时多出了一个头。 不知道这是一个bug还是一个特性,反正不是我想要的需求。从上面的分析,解决这个问题应该从FPPopoverView的drawRect下手。

    由于边框都是在drawRect中实现的,所以我的解决方案非常简单

    - (void)drawRect:(CGRect)rect
    {
        [super drawRect:rect];
        if (self.border == NO && self.arrowDirection == FPPopoverNoArrow)
            return;
        // Keep others unchange
    }
    

    效果如下


  • 相关阅读:
    如何计算二进制数的取值范围
    理解网络请求中的连接超时和读取超时
    两行代码玩转Spring Data排序和分页
    面试必问Elasticsearch倒排索引原理
    你知道Java的四种引用类型吗
    抛弃配置后的Spring终极教程
    Python学习第二篇
    Python
    关于always块内for循环的执行方式
    三态门实现“一读多写”总线结构
  • 原文地址:https://www.cnblogs.com/suncoolcat/p/3348124.html
Copyright © 2020-2023  润新知