视图的重新理解
首先,在一个应用程序中,只有一个唯一的窗口对象,也就是 window,之后的各种视图添加的过程就是在这个 window 的上面添加各个 view,就像在一块黑板上面贴了各种各样的便签纸一样,只不过有的便签纸是智能的便签纸,可以和一个手机连接,由手机处理一些比较复杂的功能,这个手机就是视图控制器。如果说要再举一个例子的话,那么视图就像是富人家的大少爷,所有你能看到的好看的也好酷炫的也好都是少爷的面子,而视图控制器就像是少爷家的管家一样,帮着少爷处理好各种各样的事情。每个视图控制器可以有一个主视图,很多个其他的小视图。主视图就是控制器的 view 属性。
不过有的视图控制器比较特殊,比如导航视图控制器 UINavigationController,这个控制器会控制其他很多的控制器的出现与否,就好像一个总管家一样。
大致的理解定位了以后,来看看都有哪些视图的函数会被调用(以下是按照顺序写的,实际视图生成时调用顺序就是下面的顺序)。
1 2 3 #pragma mark -viewLoading 4 5 - (void)viewDidLoad 6 7 { 8 9 [super viewDidLoad]; 10 11 // Do any additional setup after loading the view, typically from a nib. 12 13 LOG(@"hello"); 14 15 LOGSize(self.view.frame.size); 16 17 LOGRect(self.view.frame); 18 19 LOGPoint(self.view.frame.origin); 20 21 } 22 23 -(void)viewWillAppear:(BOOL)animated 24 25 { 26 27 [super viewWillAppear:animated]; 28 29 } 30 31 //ios6之后有的方法,当主视图发生变化的时候会进行调用,比如屏幕旋转 32 33 -(void)viewWillLayoutSubviews 34 35 { 36 37 38 39 } 40 41 -(void)viewDidLayoutSubviews 42 43 { 44 45 46 47 } 48 49 -(void)viewDidAppear:(BOOL)animated 50 51 { 52 53 [super viewDidAppear:animated]; 54 55 } 56 57 #pragma mark viewDestroy 58 59 -(void)viewWillDisappear:(BOOL)animated 60 61 { 62 63 [super viewWillDisappear:animated]; 64 65 } 66 67 -(void)viewDidDisappear:(BOOL)animated 68 69 { 70 71 [super viewDidDisappear:animated]; 72 73 74 75 }
在来说说视图的旋转,旋转的话会调用以下的函数(按照以下的顺序)
//判断是否可以旋转
-(BOOL)shouldAutorotate
{
return YES;
}
//返回可以支持的旋转方向,ipad 默认是支持所有方向,iphone 是除了上下颠倒的其他方向。
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
//在旋转之前,布局没有改变时候调用
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
LOG(@"还没开始");
LOGPoint(self.labForTest.frame.origin);
}
-(void)viewWillLayoutSubviews
{
LOG(@"要改变了");
LOGPoint(self.labForTest.frame.origin);
}
-(void)viewDidLayoutSubviews
{
LOG(@"改变完了");
LOGPoint(self.labForTest.frame.origin);
}
//所有的旋转后的布局已经结束
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
LOG(@"旋转后布局已经开始");
LOGPoint(self.labForTest.frame.origin);
}
//关于旋转的布局已经全部结束
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
LOG(@"全部结束");
LOGPoint(self.labForTest.frame.origin);
}
不过,要注意的是旋转之前两个 sunviews 的方法就会被调用,不知道为什么会这样,当一个视图出现的时候也是被调用了两次。同时,在以上最后一个方法调用的时候视图已经调整过来了,在之前都是没有调整过来的。所以可以在最后一个方法之前的方法中将当前的视图换为 IB 中自己设置为横向的视图,这样可以实现比较复杂的横竖屏切换,比如竖屏的时候有两个 label,横屏的时候有三个这样的 效果(生出来了?),就像 iPhone 的自带的计算器一样。效果就像下面这样:
实现的方式也很简单,就在最后一个方法之前的方法中写添加的代码就可以了:(下面代码中的 landscape 是在 IB 中创建的一个横向的视图,就是在右边设置就好,如图:)
//所有的旋转后的布局已经结束
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
LOG(@"旋转后布局已经开始");
LOGPoint(self.labForTest.frame.origin);
if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft||toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
[self.view addSubview: landscapeView];
}
else{
[landscapeView removeFromSuperview];
}
}
在这里的实际编写还发现了一个小细节,就是如果在项目的 target 的设置中如果不把 UIDevice Oritation 加上 upsideDown(上下颠倒的方向),即使在 supportedInterfaceOrientations 方法中返回了全方向支持,也没有用,上下颠倒还是不能用。必须选中了才可以用,原因是 interfaceOrientation 是来自于 Decive Orientation 的。同时,视图控制器的主 view 在被替换之前必须要和视图控制器解除关系,否则就会报错,所以这里用了 addSubview 的方法,而不是直接替换。
另外,不仅视图可以多层进行开发,视图控制器也是可以多层开发的,同时控制器拥有回调函数,所以开发起来更为方便。一般这种多层视图开发用于构建自己的一个容器类的视图控制器。什么是容器类的视图控制器?就是像 NavigationController 一样的可以装载其他控制器的控制器。如果有的时候系统自带的方便的 Navigation 或者 Tabber 控制器不太灵活对应当前的开发需求,就可以自己写一个控制器。
写自定义多层控制器的方法其实很简单,首先作为一个能够控制其他视图控制器的控制器(有点拗口哈),肯定得能随时得到这些视图控制器啦,每一个视图控制器都有一个自己的方法去帮助开发者做这件事:
firstVC=[self.storyboard instantiateViewControllerWithIdentifier:@"one"];
[self addChildViewController:firstVC];
上面的第二个方法就会把一个视图控制器放到我们的主视图控制器的 childControllers 这个属性中,这是一个数组类型的属性,可以保存主视图控制器分管的其他的视图控制器。
然后再当前视图中设置一个你希望展示其他视图控制器的视图的子视图(图中白色的就是,为了看着方便),然后为其设置一个首先显示的视图:
[self.childView addSubview:firstVC.view];
self.currentVC=firstVC;
然后按钮的 action 设置为:
if (self.currentVC==firstVC) {
return;
}
[self transitionFromViewController:self.currentVC toViewController:firstVC duration:600 options:0 animations:nil completion:^(BOOL finished) {
self.currentVC=firstVC;
}];
第二个方法的作用就是吧当前视图中属于其他视图控制器的 子视图替换掉,变成别的视图控制器的视图,之所以进来先判断,是因为视图加载是需要时间的,如果不判断当用户连续点击按钮就会报错。