UIViewController的基本概念与生命周期

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方法。

UIViewController的基本概念与生命周期

时间: 2024-10-19 01:06:58

UIViewController的基本概念与生命周期的相关文章

react教程(一)JSX语法、组件概念、生命周期介绍

JSX React中,推出了一种新的语法取名为JSX,它给了JS中写HTML标签的能力,不需要加引号.JSX的语法看起来是一种模板,然而它在编译以后,会转成JS语法,只是书写过程中的语法糖. JSX的解析器会帮我们读取这种语法并加以处理. 下面是一个简单的例子. const element = <h1 className="greeting">Hello, world!</h1>; 其实相当于如下的代码: const element = React.create

Yii2基本概念之——生命周期(LifeCycle)

人有生老病死,一年有春夏秋冬四季演替,封建王朝有兴盛.停滞.衰亡的周期律--"其兴也勃焉,其亡也忽焉".换句话说,人,季节,王朝等等这些世间万物都有自己的生命周期.同样地,在软件行业,一个系统,一个组件,一个功能,一个类都是有自己的生命周期的. 那么,为什么要从生命周期的这个角度去理解程序? 在现实世界中如果你使用一个工具,当你理解工具的工作原理的时候你使用起来无疑更加有信心.更加得心应手:应用的开发与此如出一辙,当你理解框架的运行机制时,你开发起来就会如行云流水,写起代码汪洋恣肆一发

initWithFrame、initWithCoder、awakeFromNib的区别和调用次序 &amp; UIViewController生命周期 查缺补漏

当我们创建或者自定义一个UI控件时,就很可能会调用awakeFromNib.initWithCoder .initWithFrame这些方法.三者的具体区别如下: initWithFrame: 通过代码创建UI控件的时候就会调用: initWithCoder:从文件中解析一个对象的时候就会调用这个方法,也就是说无论是通过代码还是xib,程序运行的时候都会调用这个方法: awakeFromNib:当一个对象从xib或者storyboard中加载完毕后,就会调用一次. 比如:当苹果官方提供的按钮不能

iOS对UIViewController生命周期和属性方法的解析

目录[-] iOS对UIViewController生命周期和属性方法的解析 一.引言 二.UIViewController的生命周期 三.从storyBoard加载UIViewController实例的传值陷阱 四.UIViewController与StroyBoard的相关相互方法 1.ViewController直接在StoryBoard中进行跳转的传值 2.使用代码跳转Storyboard中的controller 五.UIViewController之间的一些从属关系 1.parentV

【iOS开发】iOS对UIViewController生命周期和属性方法的解析

iOS对UIViewController生命周期和属性方法的解析 一.引言 作为MVC设计模式中的C,Controller一直扮演着项目开发中最重要的角色,它是视图和数据的桥梁,通过它的管理,将数据有条有理的展示在我们的View层上.iOS中的UIViewController是UIKit框架中最基本的一个类.从第一个UI视图到复杂完整项目,都离不开UIViewController作为基础.基于UIViewController的封装和扩展,也能够出色的完成各种复杂界面逻辑.这篇博客,旨在讨论UIV

112+蛇蛋图:哎呀,太不小心了、谈谈Yii2生命周期的超前概念!!

请=加=我=们=的=V+信[wqv 370]关注免费资料实力解答112期已解答,请加我们的导师免费获取答案,期期免费获取,不收任何费用赶紧行动起来111期:[猴羊] 开 :羊16 中110期:[龙猪] 开 :猪36 中109期:[羊兔] 开 :羊04 中108期:[蛇虎] 开 :蛇30 中 Yii2基本概念之--生命周期(LifeCycle) 论Yii2生老病死,一年有春夏秋冬四季演替,封建王朝有兴盛.停滞.衰亡的周期律--"其兴也勃焉,其亡也忽焉".换句话说,人,季节,王朝等等这些世

Maven系列学习(三)Maven生命周期和插件

Maven生命周期和插件 Maven另外的两个核心概念就是生命周期和插件,Maven的生命周期都是抽象的,其实实际行为都是由插件来完成的,生命周期和插件两者协同工作 1.生命周期 Maven的生命周期就是为了对所有的构建过程进行抽象和统一,这个生命周期包含了项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有构建步骤,Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务都是交给插件来完成的 一次构建(build):ini

Android03-Activity生命周期及启动模式

1.返回栈概念 2.生命周期 1. onCreate() 这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动 第一次被创建的时候调用.你应该在这个方法中完成活动的初始化操作,比如说加载布 局.绑定事件等. 2.onStart() 这个方法在活动由不可见变为可见的时候调用. 3.onResume() 这个方法在活动准备好和用户进行交互的时候调用.此时的活动一定位于返回栈的栈顶,并且处于运行状态. 4. onPause() 这个方法在系统准备去启动或者恢复另一个活动的时候调用.

maven入门(3)maven的生命周期2

[0]README 1)本文部分文字转自 "maven实战",旨在 review  "maven(7)生命周期和插件" 的相关知识: 2)maven 另外两个核心概念是生命周期和插件:maven的生命周期是抽象的,其实际行为都由插件来完成,如package阶段的任何可能都会由 maven-jar-plugin 完成: [1]何为生命周期 1)intro:maven 的 生命周期就是为了对所有的构建过程进行抽象和统一: 2)maven的生命周期: 生命周期本身不做任何