UIViewController - 视图控制器
UIViewController是一个为UIKit应用管理视图层次结构的对象,定义了所有视图控制器通用的共享行为,很少直接创建UIViewController类的实例,通常可以将UIViewController子类化,并添加管理视图控制器的视图层次结构所需的方法和属性。
视图控制器的主要职责包括:
- 更新视图的内容,通常是为了响应对基础数据的更改。
- 使用视图响应用户交互。
- 调整视图大小并管理整个界面的布局。
- 与其他对象协调,包括应用程序中的其他视图控制器。
视图控制器与它所管理的视图紧密绑定,并参与处理其视图层次结构中的事件。具体地说,视图控制器是UIResponder对象,被插入到视图控制器的根视图和该视图的superview之间的响应程序链中,superview通常属于不同的视图控制器。如果视图控制器的视图都不处理事件,则视图控制器可以选择处理事件或将其传递给超级视图。
视图控制器很少单独使用,通常使用多个视图控制器,每个视图控制器拥有应用程序用户界面的一部分。例如,一个视图控制器可能显示一个项目表,而另一个视图控制器显示该表中选定的项目。通常,一次只有一个视图控制器中的视图可见。视图控制器可以呈现不同的视图控制器来显示一组新的视图(例如通过模态显示新的视图控制器),或者它可以充当其他视图控制器内容的容器,并根据需要设置视图动画(例如添加子视图控制器)。
UIViewController初始化相关函数
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
函数摘要:使用指定bundle(包)中的nib文件创建一个视图控制器。该方法是指定初始化方法。子类如果重写了该方法,则必须调用此方法的super实现,即使您没有使用NIB(默认的init方法也会这样做,并为该方法的两个方法参数指定nil)。指定的nib文件没有立即加载。它会在第一次访问视图控制器的视图时被加载。如果想在nib文件加载后执行额外的初始化,需要覆盖viewDidLoad方法并在那里执行任务。
注:当使用storyboard定义视图控制器及其相关视图时,永远不要直接初始化视图控制器类。而是当segue触发时,视图控制器由storyboard自动实例化,或者当应用程序调用storyboard对象的instantiateViewControllerWithIdentifier:方法时,以编程方式实例化。当从storyboard实例化视图控制器时,iOS通过调用它的initWithCoder:方法而不是这个方法来初始化新的视图控制器,并将nibName属性设置为存储在故事板中的nib文件。
参数 :
nibNameOrNil : 要关联到视图控制器的nib文件的名称。nib文件名不应该包含任何前导路径信息。如果指定nil,则nibName属性会被设置为nil。
nibBundleOrNil :要在其中搜索nib文件的bundle。这个方法首先在bundle的特定于语言的项目目录中查找nib文件,然后是Resources目录。
返回值 :一个新初始化的UIViewController对象。
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
函数摘要:使用未归档的数据创建一个视图控制器。
参数 :
coder :未归档的数据对象。
UIViewController生命周期相关函数
- (void)loadView;
函数摘要:创建控制器管理的视图。永远不要直接调用这个方法,当视图控制器的view属性被请求但当前为nil时,视图控制器调用此方法。此方法加载或创建一个视图并将其分配给view属性。
如果视图控制器是从一个故事板(storyboard)实例化的,当使用initWithNibName:bundle:方法显式地为它分配一个nib文件,或者iOS在应用程序包中找到一个名称基于视图控制器的类名的nib文件,当nibName属性返回一个非nil值,说明一个视图控制器就会有一个关联的nib文件,此方法会从nib文件加载视图。如果视图控制器没有关联的nib文件,此方法将创建一个普通的UIView对象。
可以覆盖此方法以手动创建视图。如果选择这样做,需要将视图层次结构的根视图分配给视图属性。创建的视图应该是唯一的实例,并且不应与任何其他视图控制器对象共享。此方法的自定义实现不应调用 super。
如果使用Interface Builder(界面生成器)来创建你的视图并初始化视图控制器,则一定不能重写这个方法。
如果想对视图执行任何额外的初始化,需要在viewDidLoad方法中执行。
- (void)viewDidLoad;
函数摘要:在视图控制器将其视图层次结构加载到内存后调用此方法。 无论视图层次结构是从 nib 文件加载还是在 loadView 方法中以编程方式创建,都会调用此方法。 通常覆盖此方法以对从 nib 文件加载的视图执行额外的初始化。
- (void)viewWillAppear:(BOOL)animated;
函数摘要:在视图控制器的视图即将添加到视图层次结构之前以及配置任何动画以显示视图之前调用此方法。可以覆盖此方法以执行与显示视图相关的自定义任务。例如可以使用此方法更改状态栏的方向或样式,以与正在呈现的视图的方向或样式相协调。如果重写了这个方法,则必须在实现中调用 super 。
如果视图控制器由弹出框内的视图控制器呈现(例如Alert),则在被呈现的控制器被解除后,不会在呈现方的视图控制器上调用此方法。
参数 :
animated : 如果为YES,则使用动画将视图添加到窗口中。
- (void)viewDidAppear:(BOOL)animated;
函数摘要:通知视图控制器其视图已添加到视图层次结构中。可以覆盖此方法以执行与呈现视图相关的其他任务。 如果重写了这个方法,则必须在实现中调用 super 。
如果视图控制器由弹出框内的视图控制器呈现(例如Alert),则在被呈现的控制器被解除后,不会在呈现方的视图控制器上调用此方法。
参数 :
animated : 如果为YES,则使用动画将视图添加到窗口中。
- (void)viewWillDisappear:(BOOL)animated;
函数摘要:通知视图控制器其视图即将从视图层次结构中删除。在实际移除视图和配置任何动画之前调用此方法。子类可以覆盖此方法并使用它来提交编辑更改、退出视图的第一响应者状态或执行其他相关任务。 例如可以使用此方法恢复对第一次呈现视图时在 viewDidAppear: 方法中对状态栏的方向或样式所做的更改。 如果重写了这个方法,则必须在实现中调用 super 。
参数 :
animated : 如果为YES,则为正在消失的视图设置动画。
- (void)viewDidDisappear:(BOOL)animated;
函数摘要:通知视图控制器其视图已从视图层次结构中删除。可以重写此方法以执行与关闭或隐藏视图相关的其他任务。 如果重写了这个方法,则必须在实现中调用 super 。
参数:
animated:如果为YES,视图的消失会设置动画。
- (void)didReceiveMemoryWarning;
函数摘要:当系统确定可用内存量低时调用此方法。应用从不直接调用此方法。可以覆盖此方法以释放视图控制器使用的任何额外内存。 如果这样做,则此方法的实现必须在某个时候调用super实现。
- (void)viewWillUnload API_DEPRECATED("", ios(5.0, 6.0)) API_UNAVAILABLE(tvOS);
函数摘要:在从内存中释放控制器视图之前调用。已废弃。
- (void)viewDidUnload API_DEPRECATED("", ios(3.0, 6.0)) API_UNAVAILABLE(tvOS);
函数摘要:当控制器的视图从内存中释放时调用。已废弃。
控制器视图布局其子视图相关函数
- (void)loadViewIfNeeded API_AVAILABLE(ios(9.0));
函数摘要:调用这个方法从它的故事板文件中加载视图控制器的视图,或者根据建立的规则根据需要创建视图。
- (void)viewWillLayoutSubviews API_AVAILABLE(ios(5.0));
函数摘要:调用此函数以通知视图控制器其视图即将布局其子视图。在调用视图控制器的视图的layoutSubviews方法之前调用。子类可以根据需要实现,此方法的默认实现什么也不做。
- (void)viewDidLayoutSubviews API_AVAILABLE(ios(5.0));
函数摘要:调用此函数以通知视图控制器其视图刚刚布局了其子视图。在调用视图控制器的视图的layoutSubviews方法之后调用。视图控制器可以覆盖此方法以在视图布置其子视图后进行更改。 此方法的默认实现什么也不做。
模态呈现控制器相关属性与函数
在导航过程中,有时候需要放弃主要任务转而做其他次要任务,然后在返回到主要任务,这个“次要”任务就是在模态视图中完成的。
默认情况下,模态视图是从屏幕下方滑出来的。完成的时候需要关闭这个模态视图,如果不关闭就不能做别的事情,这就是“模态”的含义,它具有必须响应处理的意思,因此模态视图中一定会有“关闭”或“完成”按钮,其根本原因是IOS只有一个Home键。Android就不会遇到这个问题,因为Android在遇到上述问题时,可以通过Back键返回。
负责控制模态视图的控制器称为模态视图控制器。模态视图控制器并非一个专门的类,它可以是视图控制器的子类,负责主要任务的控制器称为主视图控制器,它与模态视图控制器间是“父子”关系。
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle API_AVAILABLE(ios(3.2));
属性描述 :定义此视图控制器以模态显示时将使用的表示样式。在将要呈现的视图控制器(而不是它的呈现者)上设置此属性。
如果此属性已设置为UIModalPresentationAutomatic,则读取它将始终返回具体的表示样式。
默认情况下,UIViewController将UIModalPresentationAutomatic解析为UIModalPresentationPageSheet,但系统提供的子类可以将UIModalPresentationAutomatic解析为其他具体的表示样式。系统提供的视图控制器保留对UIModalPresentationAutomatic解析的参与。
从iOS 13.0开始,在iOS上默认为UIModalPresentationAutomatic,在早期版本上默认为UIModalPresentationFullScreen。在所有其他平台上默认为UIModalPresentationFullScreen。
@property(nonatomic,assign) UIModalTransitionStyle modalTransitionStyle API_AVAILABLE(ios(3.0));
属性描述 :呈现视图控制器时使用的过渡样式。此属性确定视图控制器在使用 presentViewController:animated:completion: 方法呈现时如何在屏幕上显示动画。 要更改过渡类型,必须在呈现视图控制器之前设置此属性。 此属性的默认值为 UIModalTransitionStyleCoverVertical。
- UIModalTransitionStyle提供的枚举值:
typedef NS_ENUM(NSInteger, UIModalTransitionStyle) {
//显示视图控制器时,其视图从屏幕底部向上滑动。解除显示时,视图向下滑动。这是默认的过渡样式。
UIModalTransitionStyleCoverVertical = 0,
//显示视图控制器时,当前视图将启动从右到左的水平三维翻转,从而显示新视图,就像它位于前一视图的背面一样。解除显示时,从左向右翻转,返回到原始视图。
UIModalTransitionStyleFlipHorizontal API_UNAVAILABLE(tvOS),
//显示视图控制器时,当前视图淡出,而新视图同时淡入。取消时,将使用类似类型的交叉淡入淡出来返回原始视图。
UIModalTransitionStyleCrossDissolve,
//显示视图控制器时,当前视图的一个角向上卷曲以显示下面显示的视图。在撤销时,卷曲的页面将自己展开回到显示视图的顶部。使用此转换显示的视图控制器本身无法显示任何其他视图控制器。仅当父视图控制器呈现全屏视图并且您使用UIModalPresentationFullScreen模式呈现样式时,才支持此转换样式。尝试对父视图使用不同的形状因子或不同的表示样式会触发异常。
UIModalTransitionStylePartialCurl API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvOS),
};
TSChannelSixViewController *six = [[UIStoryboard storyboardWithName:@"ChannelSix" bundle:nil] instantiateViewControllerWithIdentifier:NSStringFromClass(TSChannelSixViewController.class)];
six.modalPresentationStyle = UIModalPresentationFullScreen;
six.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:six animated:YES completion:nil];
样式如图 :
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion API_AVAILABLE(ios(5.0));
函数摘要:以模态呈现视图控制器。
参数:
viewControllerToPresent:要在当前视图控制器的内容上显示的视图控制器。
flag:是否显示动画。
completion:显示完成后要执行的块。此块没有返回值,不接受任何参数。可以为此参数指定nil。
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion API_AVAILABLE(ios(5.0));
函数摘要:解除视图控制器以模式显示的视图控制器。
参数描述:
flag:是否显示动画。
completion:解除视图控制器后要执行的块。此块没有返回值,不接受任何参数。可以为此参数指定nil。
添加子控制器到父控制器相关函数
有时为了在一个本就已经比较复杂的页面增加功能时,可以将要增加的视图交由一个新的控制器去管理,然后将新的视图控制器添加至父视图控制器,从而更清晰的管理页面视图的逻辑。例如为了灵活的放置子视图控制器位置,并且将页面的逻辑单独由一个控制器去处理,采用添加子控制器到父控制器的方式显示视图控制器。
UIViewController (UIContainerViewControllerProtectedMethods) - 容器视图控制器受保护方法对UIViewController的扩展
- (void)addChildViewController:(UIViewController *)childController API_AVAILABLE(ios(5.0));
函数摘要 :将指定的视图控制器添加为当前视图控制器的子视图控制器。该方法在当前视图控制器和childController参数中的对象之间创建父子关系。当将子视图控制器的视图嵌入到当前视图控制器的内容中时,这种关系是必要的。如果新的子视图控制器已经是容器视图控制器的子控制器,它在被添加之前会从容器中移除。
此方法仅用于自定义容器视图控制器的实现来调用。如果重写此方法,则必须在实现中调用super。
参数描述:
childController:要作为子级添加的视图控制器。
注:添加子视图控制器后,也需要通过addSubview将子ViewController的view添加到父视图的视图层级中。
- (void)removeFromParentViewController API_AVAILABLE(ios(5.0));
函数摘要 :从其父视图控制器中移除视图控制器。此方法仅用于由自定义容器视图控制器的实现调用。如果重写此方法,则必须在实现中调用super。
UIViewController (UIContainerViewControllerCallbacks) - 容器视图控制器回调对UIViewController的扩展
- (void)didMoveToParentViewController:(nullable UIViewController *)parent API_AVAILABLE(ios(5.0));
函数摘要:在从容器视图控制器中添加或删除视图控制器后调用。当视图控制器希望在被添加到容器视图控制器或在容器视图控制器中被移除之后做出某些反应时,视图控制器(被添加的视图控制器)可以重写这个方法。从容器视图控制器过渡到被添加的视图控制器后,会调用被添加的视图控制器重写的didMoveToParentViewController:方法,但前提是在调用addChildViewController:方法后立即调用了didMoveToParentViewController:方法。
通过removeFromParentViewController方法在移除子视图控制器后自动调用子视图控制器重写的didMoveToParentViewController:方法。
参数:
parent:父视图控制器,如果没有父视图控制器,则为nil。
- (void)willMoveToParentViewController:(nullable UIViewController *)parent API_AVAILABLE(ios(5.0));
函数摘要:在视图控制器从容器视图控制器中添加或删除之前调用。当视图控制器希望在被添加到容器视图控制器或在容器视图控制器中被移除之前做出某些反应时,视图控制器(被添加的视图控制器)可以重写这个方法。容器视图控制器调用addChildViewController:方法时,会自动调用要作为子级添加的视图控制器重写的willMoveToParentViewController:方法。但作为子级添加的视图控制器在调用removeFromParentViewController在容器视图控制器中移除之前,必须调用willMoveToParentViewController:方法并传入nil值。
参数 :
parent :父视图控制器,如果没有父视图控制器则为nil。
///以添加子控制器的方式打开店铺分类选择视图控制器
- (void)openShopCategorySelectViewController:(UIViewController *)controller{
//判读打开店铺分类选择视图控制器的是否是店铺根视图控制器
if([controller isMemberOfClass:[YSCShopViewController class]]){
//是店铺根视图控制器
self.shopCategorySelectViewController.view.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight - 44 - bottomPadding());
}else{
//其他视图控制器
self.shopCategorySelectViewController.view.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight - HEAD_BAR_HEIGHT - 44 - bottomPadding());
}
//添加店铺分类选择视图控制器到当前视图控制器
[controller addChildViewController:self.shopCategorySelectViewController];
//在调用addChildViewController:方法后需要调用
[self.shopCategorySelectViewController didMoveToParentViewController:controller];
//设置视图切换控制器视图在其父视图边界更改时调整自身的方式
self.shopCategorySelectViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
//添加视图切换控制器视图
[controller.view addSubview:self.shopCategorySelectViewController.view];
}
///移除店铺分类选择视图控制器
- (void)removeShopCategorySelectViewController{
[self.view endEditing:YES];
//在容器视图控制器中添加或删除视图控制器之前调用
[self willMoveToParentViewController:nil];
//在父视图移除视图
[self.view removeFromSuperview];
//从其父视图控制器中删除视图控制器
[self removeFromParentViewController];
}
常用属性
@property(null_resettable, nonatomic,strong) UIView *view;
属性描述:控制器管理的视图。此属性表示视图控制器的视图层次结构的根视图。此属性的默认值为 nil。如果在其值为 nil 时访问此属性,视图控制器会自动调用 loadView 方法并返回结果视图。
每个视图控制器都是其view对象的唯一所有者。不要将同一个view对象与多个视图控制器相关联。唯一打破这种规则的例外是容器视图控制器实现可以将另一个视图控制器的视图对象添加到它自己的视图层次结构中。在添加子视图之前,容器必须首先调用它的addChildViewController:方法来在两个视图控制器对象之间创建父子关系。
因为访问该属性会导致视图被自动加载,所以可以使用 viewLoaded 来确定视图当前是否在内存中。与此属性不同,如果视图当前不在内存中,viewLoaded 不会强制加载视图。
@property(nullable, nonatomic, readonly, strong) UIView *viewIfLoaded API_AVAILABLE(ios(9.0));
属性描述 :视图控制器的视图,如果视图控制器的视图已被加载,则此属性包含该视图。 如果视图尚未加载,则此属性设置为 nil。
@property(nonatomic, readonly, getter=isViewLoaded) BOOL viewLoaded API_AVAILABLE(ios(3.0));
属性描述 :一个布尔值,指示视图当前是否已加载到内存中。当视图在内存中时,此属性的值为“YES”,否则为“NO”。
@property(nullable, nonatomic,copy) NSString *title;
属性描述 :将标题设置为描述视图的可读字符串。 如果视图控制器具有有效的导航项(UINavigationItem)或标签栏项(UIBarItem),则为该属性分配一个值会更新这些对象的标题文本。
@property(nullable,nonatomic,weak,readonly) UIViewController *parentViewController;
属性描述:调用方的父视图控制器。如果调用方是容器视图控制器的子级,则此属性包含它所在的容器视图控制器。如果调用方没有父级视图控制器,则此属性中的值为 nil。
在 iOS 5.0 之前,如果视图没有父视图控制器并且正在呈现,则将返回呈现视图控制器。 在 iOS 5 上,此行为不再发生。 相反,使用presentingViewController 属性来访问呈现视图控制器。
@property(nullable, nonatomic,readonly) UIViewController *presentingViewController API_AVAILABLE(ios(5.0));
属性描述 : 呈现调用方的视图控制器。当调用方被使用presentViewController:animated:completion: 方法模态(显式或隐式)的呈现出来时,调用方(被呈现的视图控制器)将这个属性设置为呈现它的视图控制器。如果调用方不是以模态方式呈现的,但它的一个祖先是,则这个属性包含呈现调用方祖先的视图控制器。如果当前视图控制器或其任何祖先都没有以模态方式显示,则此属性中的值为nil。
@property(nullable, nonatomic,readonly) UIViewController *presentedViewController API_AVAILABLE(ios(5.0));
属性描述 : 当调用方使用 presentViewController:animated:completion: 方法以模态(显式或隐式)呈现一个视图控制器时,调用方(调用该模态呈现方法的视图控制器)将此属性设置为其呈现出的视图控制器。如果调用方没有以模态显示另一个视图控制器,则此属性中的值为nil。
@property(nonatomic,assign) BOOL definesPresentationContext API_AVAILABLE(ios(5.0));
属性描述 : 一个布尔值,指示调用方或其作为其后代之一的视图控制器呈现其它视图控制器时是否覆盖该视图控制器的视图。当使用UIModalPresentationCurrentContext或UIModalPresentationOverCurrentContext样式来呈现一个视图控制器时,这个属性控制在你的视图控制器层次结构中哪些现有的视图控制器实际上被新内容覆盖。当一个基于上下文的表示发生时,UIKit从表示视图控制器开始并向上走到视图控制器层次结构。如果它找到一个视图控制器,它的这个属性的值是YES,它会请求那个视图控制器呈现新的视图控制器。如果没有视图控制器定义表示上下文,UIKit会请求窗口的根视图控制器来处理表示。此属性的默认值为NO。一些系统提供的视图控制器,如UINavigationController,将默认值更改为YES。
@property (nonatomic) CGSize preferredContentSize API_AVAILABLE(ios(7.0));
属性描述 :视图控制器视图(view属性)的首选大小。此属性中的值主要用于在弹出窗口中显示视图控制器的内容,但也可以在其他情况下使用。当视图控制器显示在弹出窗口中时更改此属性的值会为大小更改设置动画, 但是如果将宽度或高度指定为 0.0,则不会对更改进行动画处理。
@property(nonatomic, readonly) BOOL prefersStatusBarHidden API_AVAILABLE(ios(7.0)) API_UNAVAILABLE(tvOS);
属性描述 :模态显示一个视图控制器时,指定视图控制器是隐藏还是显示状态栏的首选项(需要被呈现的视图控制器重写该属性的值 )。默认情况下,此方法返回 NO,但有一个例外,对于链接到 iOS 8 或更高版本的应用程序,如果视图控制器处于垂直紧凑环境中,则此方法返回 YES。
@property(nonatomic,assign) BOOL extendedLayoutIncludesOpaqueBars API_AVAILABLE(ios(7.0));
属性描述 :一个布尔值,指示视图控制器视图布局是否扩展到不透明条形栏(UINavigationBar、UITabBar等)之下。此属性的默认值为 NO。在 iOS 7.0 中,条形栏默认为半透明。
记录的代码片段
+ (UIViewController *)getCurrentVC {
//获取窗口对象
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
//窗口在z轴上的位置不是默认级别
if (window.windowLevel != UIWindowLevelNormal){
//获取应用程序的可见和隐藏窗口
NSArray *windows = [[UIApplication sharedApplication] windows];
//遍历应用程序的可见和隐藏窗口
for(UIWindow * tmpWin in windows){
//如果窗口在z轴上的位置是默认级别
if (tmpWin.windowLevel == UIWindowLevelNormal){
//设置窗口对象
window = tmpWin;
break;
}
}
}
//获取窗口的根视图控制器
UIViewController *result = window.rootViewController;
//寻找根视图控制器模态显示最后一个控制器
while (result.presentedViewController) {
result = result.presentedViewController;
}
//如果视图控制器是工具栏控制器
if ([result isKindOfClass:[UITabBarController class]]) {
//返回与当前选定的工具项关联的视图控制器。
result = [(UITabBarController *)result selectedViewController];
}
//如果视图控制器是导航视图控制器
if ([result isKindOfClass:[UINavigationController class]]) {
//返回位于导航堆栈顶部的视图控制器
result = [(UINavigationController *)result topViewController];
}
//返回视图控制器
return result;
}
- (void)backToRootViewController{
//获取当前视图控制器
UIViewController *currentVC = [self getCurrentVC];
//如果当前控制器是以模态显示的
while(currentVC.presentingViewController != nil){
//获取模态显示当前视图控制器的视图控制器
currentVC = currentVC.presentingViewController;
//模态的关闭显示当前视图控制器的视图控制器
[currentVC dismissViewControllerAnimated:NO completion:nil];
}
//返回根视图控制器
[currentVC.navigationController popToRootViewControllerAnimated:NO];
}