前言
看似常用的UIView,其实有很多不经常用到的方法和应该注意的机制,我是一个喜欢打破砂锅问到底的人,可是很多问题在网上搜索不到答案,大部分的博客都是转载相同的东西,所以我自己就新建 Dome,实际测试了一些数据,得到了一些有用的东西,分享给大家,其中也用到了其它大神的理论和指点,在此感谢。
viewController的生命周期
-
单个viewController的生命周期
initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib) loadView:#加载view 会多次调用并且会使viewWillLayoutSubviews、viewDidLayoutSubviews不再执行 viewDidLoad:view加载完毕 viewWillAppear:控制器的view将要显示, viewWillLayoutSubviews:控制器的view将要布局子控件 viewDidLayoutSubviews:控制器的view布局子控件完成 viewDidAppear:控制器的view完全显示 viewWillDisappear:控制器的view即将消失的时候 viewDidDisappear:控制器的view完全消失的时候
-
多个viewControllers跳转
当我们点击push的时候首先会加载下一个界面然后才会调用界面的消失方法 initWithCoder:(NSCoder *)aDecoder:ViewController2 (如果用xib创建的情况下) loadView:ViewController2 viewDidLoad:ViewController2 viewWillDisappear:ViewController1 将要消失 viewWillAppear:ViewController2 将要出现 viewWillLayoutSubviews ViewController2 viewDidLayoutSubviews ViewController2 viewWillLayoutSubviews:ViewController1 viewDidLayoutSubviews:ViewController1 viewDidDisappear:ViewController1 完全消失 viewDidAppear:ViewController2 完全出现
小结:
整个控制器声明周期: viewDidLoad -> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewDidDisappear说明
viewWillLayoutSubviews 在 viewWillAppear 之后 viewDidAppear 之前执行,这个方法会被调用多次,如果在此创建视图,可能会创建多个,而且这个方法中执行耗时操作依然会造成跳转卡顿的问题。
一个VC 在执行viewWillDisappear 或者viewDidDisappear 方法时都是出栈之后才执行的,也就是说 已经不在 self.navigationController 的viewControllers中了,而一个 VC 执行viewWillAppear 时必定是 self.navigationController 的 topViewController即栈顶的视图。
一个很常见的问题,我们需要在VC被销毁的时候处理一些事情,可是 viewWillDisappear 不管是入栈新的视图还是出栈当前视图都会被调用,无法判断,结合我之前说的原理,我们可以利用下面这个方法判断当前VC是否是被出栈了。
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
if ([[self.navigationController viewControllers] containsObject:self ]) {
NSLog(@"TwoViewController 暂时消失");
}else
{
NSLog(@"TwoViewController 出栈了");
}
}
首先需要特别说明的一点是:
在使用 VC绘制UI的时候,它不同于 UIView,UIView中 init方法调用后,UIView就已经绘制好(初始化好了)。
但是在VC中,当前UIView的是在 viewDidLoad 执行后才初始化好的,所以我们会见到这样一个Bug,在VC初始化语句后面调用 一个 VC的绘制UI的方法,你会发现根本没有效果,这就是因为,VC中跟试图的初始化是需要时间的,你在VC根试图初始化之前调用绘制UI的方法当然是不会有效果出现的。
UIView的setNeedsDisplay和setNeedsLayout方法。
首先两个方法都是异步执行的。setNeedsDisplay会调用 drawRect方法,这样可以拿到UIGraphicsGetCurrentContext,就可以画画。
setNeedsLayout会默认调用layoutSubViews,就可以处理子视图中的一些数据。
综上两个方法都是异步执行的,layoutSubviews方便数据计算,drawRect方便视图重绘。UIView中方法的执行顺序大概是这样的:
init 方法 >>属性的set/get 方法>> layoutSubviews(当然是要达到触发条件)>>drawRect, 使用得当可以在View的调用中起到很好的效果。
layoutSubviews在以下情况下会被调用:
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转Screen会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
7、直接调用setLayoutSubviews。
drawRect在以下情况下会被调用:
1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在 控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量 值).
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。以上1,2推荐;而3,4不提倡
drawRect方法使用注意点:
1、 **若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate 的ref并且不能用于画图**。**drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或 者 setNeedsDisplayInRect,让系统自动调该方法。强行调用也不会起作用的。**
2、**若使用calayer绘图,只能在drawInContext: 中(类似鱼drawRect)绘制**,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
3、**若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕。**
值得注意的是:
viewWillAppear 严谨的说应该是视图将要达到栈顶时调用,不是每次视图出现在屏幕时就会调用,一个很常见的现象是,home键后,再次启动程序,第一个出现的页面的 viewWillAppear 是不回被调用的。