庖丁解牛FPPopover

来源:互联网 发布:手机网络被运营商劫持 编辑:程序博客网 时间:2024/06/05 07:22

作者: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}

效果如下


原创粉丝点击