【iOS开发】UIViewController的基本概念与生命周期
http://www.cnblogs.com/wayne23/p/3868535.html
UIViewController是iOS顶层视图的载体及控制器,用户与程序界面的交互都是由UIViewController来控制的,UIViewController管理UIView的生命周期及资源的加载与释放。
UIView与UIWindow共同展示了应用程序的用户界面。可以将UIView理解成画布,UIWindow理解成画框。这两个类的继承关系是这样的:
NSObject — UIResponder — UIView — UIWindow
iOS中,所有显示在界面上的对象都是从UIResponder直接或间接继承的,UIView和UIWindow也不例外。
可以将它们之间的关系想象成这样一个场景:首先会有一个空的画框(UIWindow),我们在画框上放置一块画布(UIView),然后可以在这个画布(UIView)上进行绘画,画布上可能会被画上各种元素,例如UILabel、UIButton等。这些元素其实也是一个又一个UIView,它们会有一个层级关系管理,有点相当于Photoshop图层的概念,层级高的元素会覆盖住层级低的元素,从而导致层级低的元素被部分或完全遮挡。
UIWindow
虽然UIWindow继承自UIView,但是在模型中,它是一个首席View。UIWindow的主要作用是提供一个区域来显示UIView,然后将事件分发给UIView。一般情况下,应用程序只有一个UIWindow对象,即使有多个UIWindow对象,也只有一个UIWindow可以接受到用户的触屏事件。
当新建一个最原始的Empty Application工程后,会发现系统在application:didFinishLaunchingWithOptions:方法里已经为我们建好了一个UIWindow,代码如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}

1. 创建一个全屏的window:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
2. 给window设置背景色:
self.window.backgroundColor = [UIColor whiteColor];
3. 给将window设置为KeyWindow并显示:
[self.window makeKeyAndVisible];
假如这个时候通过调试器启动这个程序,将会收到系统输出的一个提示:
Application windows are expected to have a root view controller at the end of application launch
这个提示表示没有指定根view controller,我们可以新建一个带xib文件的ViewController类作为window的rootViewController:
MyFirstViewController *myVC = [[MyFirstViewController alloc] init];
self.window.rootViewController = myVC;
P.s. 这里有一点需要注意,无论你怎么修改ViewController类实例的尺寸,都会强制铺满整个window,文档里是这样解释的:”If a view controller is owned by a window object, it acts as the window’s root view controller. The view controller’s root view is added as a subview of the window and resized to fill the window.”
UIView
UIView有下面这些基础概念:
- UIViewController的view属性拥有一个UIView。
- UIView中可以包含多个UIView(subviews)。
- UIView可以通过superview属性访问父UIView。
- 如果是UIWindow的子元素,则可以通过Window属性访问UIWindow。
UIView中常用的结构体:
CGPoint point = CGPointMake(x, y); //坐标
CGSize size = CGSizeMake(width, height); //大小
CGRect rect = CGRectMake(x, y, width, height); //位置和大小
UIView的常用属性:
frame — 相对父视图的位置和大小
bounds — 相对自身的位置和大小,所以bounds的x和y永远为0
center — 子视图的中点坐标相对父视图的位置
transform — 可以通过这个属性控制视图的放大缩小和旋转
superview — 获取父视图
subviews — 获取所有子视图
alpha — 视图的透明度(0 - 1)
tag — 视图的标志,设置了tag后,可以通过viewWithTag方法获取这个视图
userInteractionEnabled — 是否相应用户交互事件
通过transform属性来对视图进行缩放、旋转和平移

//获取当前transform
CGAffineTransform transform = self.lblSeg.transform; //缩放
self.lblSeg.transform = CGAffineTransformMakeScale(.5, .5); //在一个transform的基础上再缩放
self.lblTest.transform = CGAffineTransformScale(transform, 0.5, 0.5); //旋转(弧度制)
self.lblSeg.transform = CGAffineTransformMakeRotation(M_2_PI); //在一个transform的基础上再旋转
self.lblTest.transform = CGAffineTransformRotate(transform,M_2_PI); //平移
self.lblSeg.transform = CGAffineTransformMakeTranslation(100, 100); //在一个transform的基础上再平移
self.lblTest.transform = CGAffineTransformTranslate(transform, 100, 100);

UIView的常用方法:
初始化视图
- (id)initWithFrame:(CGRect)aRect
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(10.0, 20.0, 150.0, 100.0)];
将视图从父视图中移除
- (void)removeFromSuperview
[self.lblTest removeFromSuperview];
插入一个视图到指定位置
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index

NSInteger myIndex = 0; if(self.myView1.subviews.count>0)
{
myIndex = self.myView1.subviews.count;
} [self.myView1 insertSubview:self.lblTest atIndex:myIndex];

将index1和index2位置的两个视图互换位置
- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2
NSInteger index1 = [self.view.subviews indexOfObject:self.lblTest1]; NSInteger index2 = [self.view.subviews indexOfObject:self.lblTest2]; [self.view exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
添加子视图
- (void)addSubview:(UIView *)view
[self.myView1 addSubview:self.lblTest];
插入视图到指定位置
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
[self.view insertSubview:self.lblTest atIndex:0];
插入视图到指定视图的下面
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview
[self.view insertSubview:self.lblTest1 belowSubview:self.lblTest2];
插入视图到指定视图的上面
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview
[self.view insertSubview:self.lblTest1 aboveSubview:self.lblTest2];
将指定子视图移到最顶层
- (void)bringSubviewToFront:(UIView *)view
[self.view bringSubviewToFront:self.lblTest];
将指定子视图移到最底层
- (void)sendSubviewToBack:(UIView *)view
[self.view sendSubviewToBack:self.lblTest];
根据视图的tag查找视图
- (UIView *)viewWithTag:(NSInteger)tag
[self.lblTest setTag:5];
[self.view viewWithTag:5].alpha = 0;
取得视图下的所有子视图
@property(nonatomic, readonly, copy) NSArray *subviews
NSArray *arrView = [self.view subviews]; for (UIView *view1 in arrView)
{
NSLog(@"%@",view1);
}
UIScreen
UIScreen类代表了屏幕,通过这个类我们可以获取一些关于屏幕的内容:
返回带有状态栏的Rect

CGRect bounds = [UIScreen mainScreen].bounds; NSLog(@"%.0f,%.0f,%.0f,%.0f",bounds.origin.x,
bounds.origin.y,
bounds.size.width,
bounds.size.height
);

在4英寸设备的屏幕下测试,将打印出0,0,320,568
返回不带状态栏的Rect

CGRect bounds = [[UIScreen mainScreen] applicationFrame]; NSLog(@"%.0f,%.0f,%.0f,%.0f",bounds.origin.x,
bounds.origin.y,
bounds.size.width,
bounds.size.height
);

在4英寸设备的屏幕下测试,将打印出0,20,320,548
无论是带状态栏还是不带状态栏获得的Rect,都是相对于设备屏幕来说的,所以当返回不带状态栏的Rect时,y坐标为20(状态栏的高度为20),而高度减少了20,为568-20=548。
下面这个方法可以获得状态栏(StatusBar)的位置和大小:

CGRect rect = [[UIApplication sharedApplication] statusBarFrame]; NSLog(@"%.0f,%.0f,%.0f,%.0f",rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height
);

在4英寸设备的屏幕下测试,将打印出0,0,320,20
生命周期
我们建立一个简单的模型来测试生命周期:新建两个ViewController,一个是主视图控制器(main ViewController,以下简称mainVC),一个是副视图控制器(sub ViewController,以下简称subVC),在mainVC里点击一个Button,以modal方式切换至subVC,然后在subVC里点击另一个Button关闭subVC并返回mainVC。我们将这两个控制器的每个状态都打印出来,各个阶段的执行如下:
case 1. 第一次运行app:
main loadView
main viewDidLoad
main viewWillAppear
main viewDidAppear
case 2. 在mainVC里点击Button,以modal方式切换至subVC:
sub loadView
sub viewDidLoad
main viewWillDisappear
sub viewWillAppear
sub viewDidAppear
main viewDidDisappear
case 3. 在subVC里点击Button关闭subVC并返回mainVC
sub viewWillDisappear
main viewWillAppear
main viewDidAppear
sub viewDidDisappear
sub dealloc
当一个视图控制器被创建,并在屏幕上显示的时候代码的执行顺序:
step 1:alloc 创建对象,分配空间
step 2:init (initWithNibName) 初始化对象
step 3:loadView 从nib载入视图 ,通常这一步不需要去干涉。除非你没有使用xib文件创建视图
step 4:viewDidLoad 载入完成,可以进行自定义数据以及动态创建其他控件
step 5:viewWillAppear 视图将出现在屏幕之前,马上这个视图就会被展现在屏幕上了
step 6:viewDidAppear 视图已在屏幕上渲染完成
当一个视图控制器被移除屏幕并且销毁的时候的执行顺序:
step 1:viewWillDisappear 视图将被从屏幕上移除之前执行
step 2:viewDidDisappear 视图已经被从屏幕上移除,用户看不到这个视图了
step 3:dealloc 视图被销毁
这里需要说一下loadView与viewDidLoad的区别:当loadView时,还没有view;而viewDidLoad时,view已经创建好了。详细的加载循环:
step 1:程序请求ViewController的view属性
step 2:如果view在内存中,则直接加载;如果不存在,则调用loadView方法
step 3:loadView方法执行如下方法:
- 如果重载了这个方法,则必须创建必要的UIView并且将一个非nil值传给ViewController的view属性。
- 如果没有重载这个方法,ViewController会默认使用自己的nibName和nibBundle属性尝试从nib文件加载view。如果没有找到nib文件,它尝试寻找一个与ViewController类名匹配的nib文件。
- 如果没有可用的nib文件,那么它创建一个空的UIView作为它的view。
最后还要考虑一个重要的情况:内存不足警告。当程序收到内存警告的时候,会调用每一个ViewController的didReceiveMemoryWarning方法,我们需要做出相应,释放程序中暂时不需要的资源;通常都会重写该方法,但记得重写的时候要调用super的该方法。
iOS3.0 - iOS6.0期间,didReceiveMemoryWarning方法会判断当前ViewController的view是否显示在window上,如果没有显示在window上,则didReceiveMemoryWarning会自动将ViewController的view以及其所有子view全部销毁,然后调用View Controller的viewDidUnload方法。但是从iOS6.0开始,viewDidUnload和viewWillUnload这两个方法已被废除,收到low-memory时系统不会释放view,而只是释放controller的resource。
一种常见处理内存警告的方式:

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning]; float ver = [[[UIDevice currentDevice] systemVersion] floatValue]; if(ver >= 6.0f)
{
if(self.isViewLoaded && !self.view.window)
{
self.view = nil; //确保下次重新加载
}
}
}

上面的代码先取得当前iOS系统的版本号,如果是iOS6.0或以上版本,进一步判断视图是否被装载进内存,并且是否为当前视图,在这两个条件都满足(已经装载进内存&&不是当前视图)时,将self.view设置为nil,这样就能保证再调用该ViewController时,loadView和viewDidLoad被再次调用。
我们在xcode调试器里模拟内存警告,监控此时切换的状态:
case 4. 当已切换至subVC,模拟内存警告,并返回mainVC,不处理didReceiveMemoryWarning。
Received memory warning.
main didReceiveMemoryWarning
sub didReceiveMemoryWarning
sub viewWillDisappear
main viewWillAppear
main viewDidAppear
sub viewDidDisappear
sub dealloc
case 5. 当已切换至subVC,模拟内存警告,并返回mainVC,处理didReceiveMemoryWarning。
Received memory warning.
main didReceiveMemoryWarning
sub didReceiveMemoryWarning
main loadView
main viewDidLoad
sub viewWillDisappear
main viewWillAppear
main viewDidAppear
sub viewDidDisappear
sub dealloc
可以很明显的看出,当处理了didReceiveMemoryWarning后,重新执行了非当前视图的loadView和viewDidLoad方法。
【iOS开发】UIViewController的基本概念与生命周期的更多相关文章
- UIViewController的基本概念与生命周期
UIViewController是iOS顶层视图的载体及控制器,用户与程序界面的交互都是由UIViewController来控制的,UIViewController管理UIView的生命周期及资源的加 ...
- iOS系列 基础篇 03 探究应用生命周期
iOS系列 基础篇 03 探究应用生命周期 目录: 1. 非运行状态 - 应用启动场景 2. 点击Home键 - 应用退出场景 3. 挂起重新运行场景 4. 内存清除 - 应用终止场景 5. 结尾 本 ...
- iOS系列 基础篇 04 探究视图生命周期
iOS系列 基础篇 04 探究视图生命周期 视图是应用的一个重要的组成部份,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻. 以视图的四种状态为基础,我们来系统了解一下视 ...
- atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較
atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較 例如以下列举了server控件生命周期所要经历的11个阶段. (1)初始 ...
- IOS开发 GCD介绍: 基本概念和Dispatch Queue
iOS的三种多线程技术 1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØN ...
- iOS开发 - UIViewController控制器管理
创建一个控制器 控制器常见的创建方式有下面几种 //通过storyboard创建 //直接创建 ViewController *vc = [[ViewController alloc] init]; ...
- OS开发之旅之App的生命周期【转载】
原文链接 http://www.360doc.com/content/15/0918/14/27799428_499912639.shtml 在iOS App中,入口函数并不在根目录下,而是在“Sup ...
- SVN使用—概念及生命周期
一.SVN简介 Apache Subversion 通常被缩写成 SVN,是一个开放源代码的版本控制系统,Subversion 在 2000 年由 CollabNet Inc 开发,现在发展成为 Ap ...
- AIR Native Extension for iOS 接入第三方sdk 如何实现 AppDelegate 生命周期
作者:Panda Fang 出处:http://www.cnblogs.com/lonkiss/p/6492385.html 原创文章,转载请注明作者和出处,未经允许不可用于商业营利活动 去年到今年做 ...
随机推荐
- SpringBoot非官方教程 | 第十四篇:在springboot中用redis实现消息队列
转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/11/springboot14-redis-mq/ 本文出自方志朋的博客 这 ...
- MySQL 避免行锁升级为表锁——使用高效的索引
文章目录 普通索引 属性值重复率高 属性值重复率低 小结 众所周知,MySQL 的 InnoDB 存储引擎支持事务,支持行级锁(innodb的行锁是通过给索引项加锁实现的).得益于这些特性,数据库支持 ...
- string类中字符的大小写转换
今天做一道题,要用string类,涉及大小写转换,查看了C++文档,string类没有提供这样的方法,只好自己写. 之后是想到一个比较笨的方法,我把string当成一个容器,然后用迭代器一个一个来替换 ...
- 亚马逊VPS
添加用户: 第一步 注册亚马逊云(已注册直接进入第二步)1.开始前需要准备好:有外币支付功能的信用卡一张(注册需要,不扣钱).Email地址.电话(手机更方便一些)2.访问 https://amazo ...
- 【PHP项目】产品新增的多图上传
产品新增:多图上传 1:html的更改 在 type=file的input框中添加multiple="multiple" name属性中必须添加[] ,否则$_FILES只能接收最 ...
- 源码安装CentOs7下的PHP7
首先安装APACHE环境,直接用yum安装 yum install httpd httpd-devel /etc/httpd/ systemctl start httpd.service #启动apa ...
- 转自 阿里云技术文档的 centos + PHP 环境 搭建
产品亮点 1.基于阿里云CentOS7.2镜像 2.采用yum方式安装,软件安装均为默认目录,未作任何修改. 3.采用经典LAMP组合,拓展性强,资源丰富,解决方案较多 4.附带PhpMyadmin和 ...
- JNI模板
java为了调用底层驱动函数,需要调用外部的C/C++代码,java提供了JNI接口: 然后将C代码编译成库(windows下 .dll / android环境下 .so) arm-linux-gcc ...
- Leecode刷题之旅-C语言/python-100相同的树
/* * @lc app=leetcode.cn id=100 lang=c * * [100] 相同的树 * * https://leetcode-cn.com/problems/same-tree ...
- QOS-CBQ概述
QOS-CBQ概述 2018年7月7日 19:56 CBQ(基于类的对列)是一种基于QOS policy实现的拥塞管理技术. CBQ中包含一个LLQ(低延迟队列),用来支撑EF(快速转发)类业 ...