庖丁解牛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
}
效果如下
庖丁解牛FPPopover的更多相关文章
- 庖丁解牛-----Live555源码彻底解密(RTP打包)
本文主要讲解live555的服务端RTP打包流程,根据MediaServer讲解RTP的打包流程,所以大家看这篇文章时,先看看下面这个链接的内容; 庖丁解牛-----Live555源码彻底解密(根据M ...
- Gradle 庖丁解牛(构建生命周期核心托付对象创建源代码浅析)
[工匠若水 http://blog.csdn.net/yanbober 未经同意严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 上一篇<Gradle 庖丁解牛(构建源头源代码浅析)> ...
- ARM“庖丁解牛”之存储器管理单元MMU
转:http://blog.sina.com.cn/s/blog_a07635070101bcbt.html 最近笔者详细地学习了由杜春雷老师编写的<ARM体系结构与编程>.对ARM存储管 ...
- Android基础新手教程——4.3.2 BroadcastReceiver庖丁解牛
Android基础新手教程--4.3.2 BroadcastReceiver庖丁解牛 标签(空格分隔): Android基础新手教程 本节引言: 上节我们对BroadcastReceiver已经有了一 ...
- 庖丁解牛Linux内核分析 0x00:《庖丁解牛》
庖丁解牛 吾生也有涯,而知也无涯 .以有涯随无涯,殆已!已而为知者,殆而已矣!为善无近名,为恶无近刑.缘督以为经,可以保身,可以全生,可以养亲,可以尽年. 庖丁为文惠君解牛,手之所触,肩之所倚,足之 ...
- Mysql性能优化全揭秘-庖丁解牛
「为什么写」 一直想写数据库相关的文章,最直接的原因是数据库这块我们工作中每天都会用到,也是面试求职绕不开的话题,无论你是何种测试,优秀的数据库能力都会非常加分,最近我在总结数据库性能优化这块内容,性 ...
- 干货 | 45张图庖丁解牛18种Queue,你知道几种?
在讲<21张图讲解集合的线程不安全>那一篇,我留了一个彩蛋,就是Queue(队列)还没有讲,这次我们重点来看看Java中的Queue家族,总共涉及到18种Queue.这篇恐怕是市面上最全最 ...
- 匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密
0x00 前言 在匹夫的上一篇文章<匹夫细说C#:不是“栈类型”的值类型,从生命周期聊存储位置>的最后,匹夫以总结和后记的方式涉及到一部分迭代器的知识.但是觉得还是不够过瘾,很多需要说清楚 ...
- 匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的
0x00 前言 由于工作繁忙所以距离上一篇博客已经过去一个多月的时间了,因此决心这个周末无论如何也得写点东西出来,既是总结也是分享.那么本文主要的内容集中在了委托的使用以及内部结构(当然还有事件了,但 ...
随机推荐
- printf 格式化最常用用法
printf 操作符的参数包括”格式字符串“及”要输出的数据列表". 格式字符串好像用来填空的模版,代表你想要的输出格式: printf "Hello,%s;your passwo ...
- java获取数据库数据表的元数据
Connction conn; DatabaseMetaData dmd=conn.getMetaData();//获取数据库元数据 PreparedStatment ps; ps.getParame ...
- iOS图片的伪裁剪(改变图片的像素值)
0x00 原理 利用一张图片事先画好的图片(以下称为蒙板),盖在要被裁剪的的图片上,然后遍历蒙板上的像素点,修改被裁剪图片对应位置的像素的色值即可得到一些我们想要的不规则图片了(比如人脸) 0x01 ...
- python3 遍历文件
程序很简单,parent,dirnames,filenames分别表明当前目录下的文件夹数及文件数,然后通过os.wolk向深入遍历. import os import os.path # thi ...
- Dedecms自定义sql 出现错误Safe Alert: Request Error step 2!
Dedecms自定义执行sql: SELECT body FROM dede_addonarticle WHERE aid = (select max(aid) fromdede_addonartic ...
- android 学习第一步
今天是2015年7月24号,今年下半年的主要学习方向是android,学习的目标是做出3个或以上的有实用价值的app.
- 在 iOS 应用中直接跳转到 AppStore 的方法--备用
找到应用程序的描述链接,比如:http://itunes.apple.com/gb/app/yi-dong-cai-bian/id391945719?mt=8 然后将 http:// 替换为 itms ...
- 转:php中实现精确设置session过期时间的方法
原文来自于:http://www.jb51.net/article/52309.htm 大多数据情况下我们对于session过期时间使用的是默认设置的时间,而对于一些有特殊要求的情况下我们可以设置一下 ...
- 工作总结:检查字符串合法性(C++)
BOOL CLiftCtrlModbusConfigDlg::CheckValid(const CString &str) { ASSERT(str.GetLength() > ); ] ...
- oracle timestamp的转换
select to_char(stime,'yyyy-mm-dd HH24:MI:ss.ff3') from e_bmp_log_operation t where t.sdetail like '% ...