iOS VC生命周期、启动流程、View常用方法等

4,891 阅读13分钟

ViewController 生命周期

生命周期方法调用顺序

init —> loadView —> viewDidLoad —> viewWillAppear —> viewWillLayoutSubviews —> viewDidLayoutSubviews —> viewDidAppear —> viewWillDisappear —> viewDidDisappear —> 接收到内存警告

1. init(init 法中初始化ViewController本身)

应该只有相关数据的初始化,而且这些数据是比较关键的数据。 init方法中实例化必要的对象(遵从LazyLoad思想)

2. loadView(controller的view为nil时调用,初始化view)

view 控制器默认会注册memory warning notification。 当第一次使用控制器的view时,会调用loadView方法创建view,一般在这里自定义view(如手动创建自己的视图,那么应该覆盖这个方法并将它们赋值给试图控制器的 view 属性); 视图控制器自带的视图并不是视图控制器一创建就马上创建的,而是被访问时才创建即自动调用loadView,这个方法返回时视图就创建好了

- (void)loadView{
    //self.view.backgroundColor = [UIColor greenColor];//这时候view还没创建,程序会崩
    [super loadView];
    //loadView返回之后,view创建好了
    self.view.backgroundColor = [UIColor greenColor];
}
3. viewDidLoad(view 从nib文件初始化)

当控制器的view创建完毕时会调用(视图控制器自带的视图创建完就会自动调用这个方法),也就是在loadView后调用,一般在这里添加子控件、初始化数据。 只有在视图控制器将其视图载入到内存之后才调用该方法,这是执行任何其他初始化操作的入口

4. viewWillAppear(页面即将被显示)

当试图将要添加到窗口中并且还不可见的时候或者上层视图移出图层后本视图变成顶级视图时调用该方法。一般在View被添加到superView之前,切换动画之前调用。在这里进行一些显示前的处理。比如键盘弹出、一些特殊的动画(比如状态条和navbar的颜色)、改变视图方向等的操作。

5. viewWillLayoutSubviews(将要布局控制器View的子控件时调用)

一般用于显示前,对子控件进行布局。

6. viewDidLayoutSubviews(布局控制器View的子控件完成时调用)

子控件布局完成,可以在这里对子控件进行一些初始化。

7. viewDidAppear(已经出现)

当视图添加到窗口中以后或者上层视图移出图层后本视图变成顶级视图时调用,用于放置那些需要在视图显示后执行的代码。

8. viewWillDisappear(即将消失)
9. viewDidDisappear(已经消失)
10. dealloc(页面销毁)
11. didReceiveMemoryWarning

当应用程序接收到系统的内容警告时,就有可能调用控制器的didReceiveMemoryWarning方法。 它的默认做法是:当控制器的view不在窗口上显示时,就会直接销毁,并且调用viewDidUnload方法

controller推出controller的流程

oneVC viewDidLoad -> oneVC viewWillAppear -> oneVC viewDidAppear -> twoVC viewDidLoad -> twoVC viewWillAppear -> oneVC viewWillDisappear -> oneVC viewDidDisappear -> twoVC viewDidAppear

iOS 启动流程

UIApplication->VC

  • 有stortboard
    1. main函数
    2. UIApplicationMain
      • 创建UIApplication对象
      • 创建UIApplication的delegate对象
    3. 根据 Info.plist 获取主要 stortboard 的文件名,加载最主要的 stortboard(有stortboard)
      • 创建UIWindow
      • 创建和设置UIWindow的rootViewController(把箭头指向的控制器设为根控制器)
      • 显示窗口
  • 没有stortboard
    1. main函数
    2. UIApplicationMain
      • 创建UIApplication对象
      • 创建UIApplication的delegate对象
    3. delegate对象开始处理(监听)系统的事件(没有storyboard)
      • 程序启动完毕的时候就会调用系统的didFinishLaunchingWithOptions方法
      • 在didFinishLaunchingWithOptions方法中创建UIWindow
      • 创建和设置UIWindow的rootViewController
      • 显示窗口

main函数

  • main函数创建一个UIApplication对象,并设置UIApplication对象里的代理,这个代理一定要遵守UIApplicationDelegate协议。开启一个主运行循环(事件循环)调用代理的方法(程序启动完成获取焦点等)

  • main函数

    int main(int argc, char * argv[]) {
      @autoreleasepool {
          return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      }
    }
    

    argv:直接传递给UIApplicationMain进行相关处理即可 principalClassName:指定应用程序类名(app的象征),该类必须是UIApplication(或子类)。如果为nil,则用UIApplication类作为默认值 delegateClassName:指定应用程序的代理类,该类必须遵守UIApplicationDelegate协议。UIApplicationMain函数会根据principalClassName创建UIApplication对象,根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性。接着会建立应用程序的Main Runloop(事件循环),进行事件的处理(首先会在程序完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法) 程序正常退出时UIApplicationMain函数才返回

UIWindow

UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow。iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了。一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow也就说,没有UIWindow,就看不见任何UI界面。

View 的创建流程和常见方法

视图控制器view的创建流程

控制器的view是延迟加载的:用到时再加载

  1. 可以用isViewLoaded方法判断一个UIViewController的view是否已经被加载
  2. 控制器的view加载完毕就会调用viewDidLoad方法

layoutSubviews -- setNeedsLayout、layoutIfNeeded

layoutSubviews

layoutSubviews 调用时机:

  1. 初始化不会触发layoutSubviews,但是如果设置了不为CGRectZero的frame的时候就会触发。
  2. 被父控件addSubview的时候。(当view的size的值为0的时候,addSubview也不会调用layoutSubviews。当要给这个view添加子控件的时候不管他的size有没有值都会调用)
  3. 设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。(尺寸指的是宽或高,位置发生变化时是不会被调用的!!)
  4. 自己的子控件尺寸发生变化时会被调用(或者说,改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件)。
  5. 滚动一个 UIScrollView 会触发 layoutSubviews
  6. 旋转屏幕时会触发 layoutSubviews
  7. 直接调用[self setNeedsLayout]
setNeedsLayout、layoutIfNeeded

setNeedsLayout:UIView对象调用setNeedsLayout方法时,相当于做了一个标记,告诉系统需要重新布局,但不会立刻执行,直到drawing cycle循环到达该节点时,才会调用layoutSubviews方法重新布局。

layoutIfNeeded;立即更新。允许在drawing cycle循环到达该节点之前,就立刻执行布局刷新调用layoutSubviews方法。当时更新的前提是对象布局设置已经改变或者在此之前调用setNeedsLayout方法标记,否则layoutIfNeeded并不会触发layoutSubviews的调用。

drawRect -- setNeedsDisplay、setNeedsDisplayInRect

drawRect

这个方法是用来重绘的。 drawRect 调用时机:

  1. 如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在 VC 的 loadView->viewDidLoad 两方法之后掉用的,所以不用担心在 VC 生成过程中,这些View的drawRect就开始画了。这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值)。
  2. 该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
  3. 通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
  4. 直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。

drawRect方法使用注意点:

  1. 若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取,将获取到一个invalidate的ref,并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。
  2. 若使用calayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法。
  3. 若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕。
  4. 永远不要去调用drawRect,因为drawRect不是让你调用的,而是系统会去调用的。
setNeedsDisplay、setNeedsDisplayInRect

默认情况下,视图几何图形的改变自动重绘而不需要调用drawRect:方法。因此,你需要去请求视图重绘当视图的数据或者状态改变的时候。使用这个方法或者用 setNeedsDisplaysetNeedsDisplayInRect: 方法来标记视图需要显示的地方。任何UIView对象标记为需要显示後将会在应用程序循环中自动重新绘制。

sizeToFit、sizeThatFits

  • - (void)sizeToFit
    调整大小并移动接收者视图大小,所以他包含了他的子视图;
    这个方法使用sizeThatFits: 方法来决定大小。子类需要重写sizeThatFits:用来计算正确的尺寸大小。默认的实现不做任何事情;
    sizeToFit会自动调用sizeThatFits方法;
    sizeToFit不应该在子类中被重写,应该重写sizeThatFits;
    sizeToFit和sizeThatFits方法都没有递归,对subviews也不负责,只负责自己;

  • - (CGSize)sizeThatFits:(CGSize)size
    计算并返回一个最好的适应接收者子视图的大小
    参数: size:接收者首选的尺寸;
    返回值:一个新的大小用来适应接收者子视图

  • 区别:
    sizeToFit:会计算出最优的 size 而且会改变自己的size。
    sizeThatFits:会计算出最优的 size 但是不会改变 自己的 size。

pointInside、hitTest

pointInside:withEvent:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event 用来判断触摸点是否在控件上 point:一个在接收者坐标系内的点
event:这个方法的目标事件或者如果这个方法被预调用返回nil
返回值:如果点在接收者边界内返回YES,否则返回NO

hitTest:withEvent:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event 用来判断控件是否接受事件以及找到最合适的view point:接收者坐标系中的点
event:触发这个方法的事件或者是如果这个方法被预调用就返回nil
返回值:一个视图对象最远的派生点。如果这个点位于接收者之外就返回nil

讨论 这个方法贯穿视图的层次发送 pointInside:withEvent: 消息到每一个子视图用来决定那个子视图需要接收触摸事件。如果pointInside:withEvent: 返回YES,那么视图的层次全部贯穿;否则视图层次的分支是被否定的。你不太需要调用这个方法,但是你需要重写它用来隐藏子视图的触摸事件。 如果视图是隐藏的,禁止用户交互的或者透明值小于01那么这个方法不可用

convertPoint、convertRect
  • 转换像素点 将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值 - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view; 将像素point从view中转换到当前视图中,返回在当前视图中的像素值 - (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;

  • 转换Rect 将rect由rect所在视图转换到目标视图view中,返回在目标视图view中的rect - (CGRect)convertRect:(CGRect)rect toView:(UIView *)view; 将rect从view中转换到当前视图中,返回在当前视图中的rect - (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;

其他方法

- (BOOL)isDescendantOfView:(UIView *)view:返回一个布尔值指出接收者是否是给定视图的子视图或者指向那个视图
- (void)sendSubviewToBack:(UIView *)view: 移动指定的子视图到它相邻视图的後面

- (id)initWithFrame:(CGRect)aRect
- (UIView *)viewWithTag:(NSInteger)tag

- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview

- (void)bringSubviewToFront:(UIView *)view
- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2

- (void)removeFromSuperview

- (void)didAddSubview:(UIView *)subview :告诉视图当子视图已经添加。
参数 subview:被添加做子视图的视图对象。
作用:被子类重写用来执行额外的命令当子视图添加到接收者。这个方法被addSubview调用。

- (void)didMoveToSuperview:当一个控件被添加到父控件中就会调用。通知接收者父视图已经改变(nil是允许的)。
作用:默认不做任何事情;子类可以重写这方法来作为特定的实现

- (void)willMoveToSuperview:(UIView *)newSuperview :通知接收者他的父视图将会改变到特定的父视图(也有可能是nil)
作用:子类可以重写这个方法来做一些特定的行为

- (void)didMoveToWindow :通知接收者它已经给添加到窗口中。
作用:子类可以重写这个方法来做特殊的实现 。
窗口的属性有可能是nil当这个方法调用的时候,这表明接收者并不属于当然任何一个窗口。这个只发生在接收者从它的父视图上移除或者接收者添加到父视图中而不是添加到window中。重写这个方法可以用来选择忽略一些他们不关心的对象。

- (void)willMoveToWindow:(UIWindow *)newWindow :通知接收者它已经被添加到特定的窗口对戏那个的视图层次中(也有可能是nil)。
作用:子类可以重写这个方法来提供一些特定的必要实现。

- (void)willRemoveSubview:(UIView *)subview :由子类重写用来在子视图从接收者视图中移除前执行一些特定的方法。
作用:这个方法被调用当子视图接收到removeFromSuperview消息或者子视图从接收者视图层次中移除因为它要被添加到其他视图了

UIApplication

1. 什么是UIApplication

每一个应用都有自己的UIApplication对象,而且是单例的。
iOS程序启动后创建的第一个对象就是UIApplication对象。
利用UIApplication对象,能进行一些应用级别的操作。

2. UIApplication的常用属性

  • 设置应用程序图标右上角的红色提醒数字
    @property(nonatomic) NSInteger applicationIconBadgeNumber;
  • 设置联网指示器的可见性
    @property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;

3. openURL- (BOOL)openURL:(NSURL*)url;

  • 打电话UIApplication [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel://10086"]];
  • 发短信 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"sms://10086"]]; * 发邮件 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://xxxxx@qq.com"]];
  • 打开一个网页资源 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.baidu.cn"]];
  • 打开其他app程序

4. UIApplication和delegate

  • 应用程序的生命周期事件(如程序启动和关闭)
  • 系统事件(如来电)
  • 内存警告

storyboard 相关

如何加载storyboard文件

```
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
 // instantiateInitialViewController:默认加载箭头指向的控制器
[storyboard instantiateInitialViewController];
```
bundle是一个目录,其中包含了程序会使用到的资源。这些资源包含了入图像、声音、编译好的代码、nib文件。对bundle,cococa提供了类NSBundle。我们的程序是一个bundle。在Finder中,一个应用程序看上去和其他文件没有区别。但是实际上他是一个包含了nib文件,编译代码,以及其资源的目录。我们把这个目录叫main bundle。

Segue

Storyboard上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue)

  1. 每一个Segue对象,都有3个属性
    唯一标识: @property (nonatomic, readonly) NSString *identifier; 来源控制器: @property (nonatomic, readonly) id sourceViewController; 目标控制器:@property (nonatomic, readonly) id destinationViewController;`

  2. 根据Segue的执行(跳转)时刻,Segue可以分为2大类型
    自动型:点击某个控件后(比如按钮),自动执行Segue,自动完成界面跳转。 手动型:需要通过写代码手动执行Segue,才能完成界面跳转。

如果点击某个控件后,需要做一些判断,也就是说:满足一定条件后才跳转到下一个界面,建议使用“手动型Segue”
[self performSegueWithIdentifier:@“login2contacts” sender:@“jack”];