建控制器的三种方法
1.纯代码创建控制器
2.通过 xib 的方式创建控制器(目前已经很少用了,知道就好)
3.通过 storyboard 创建控制器
第一种
实现一个重复很多遍的代码,深入理解内部细节
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
_window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
_window.rootViewController= [[ViewController alloc] init];
[_window makeKeyAndVisible];returnYES;}
在敲[[ViewController alloc] init];的时候,Xcode 的智能提示会有一个initWithNibName:bundle:的智能提示
查阅头文件,说明如下:
/*
The designated initializer. If you subclass UIViewController,
you must call the super implementation of this method,
even if you aren't using a NIB.
(As a convenience, the default init method will do this for you,
and specify nil for both of this methods arguments.)
- 指定的构造函数
- 如果开发
UIViewController
的子类,应该调用super
实现此方法 - 即使没有使用 NIB
- 为了方便,默认的 init 方法将为您完成此操作,并且给方法的两个参数都传入 nil
In the specified NIB, the File's Owner proxy should
have its class set to your view controller subclass,
with the view outlet connected to the main view.
- 在指定的 Nib 中,应该在
File's Owner
设置子控制器的类名 - 同时应该将
view
连线到主视图连线
If you invoke this method with a nil nib name,
then this class' -loadView method will attempt to load a NIB
whose name is the same as your view controller's class.
- 如果调用此方法时,传入了的 nib name 是一个 nil
- 视图控制器的
-loadView
方法将尝试加载一个与视图控制器的类同名的 NIB
If no such NIB in fact exists then you must either call
-setView: before -view is invoked, or override the -loadView method to set up your views programatically.
- 如果该 NIB 文件不存在,应该:
- 在调用
-view
方法之前,调用-setView:
- 或者重写
-loadView
,用程序创建视图层次结构
*/ 提示:以上一段文档说明已经存在很多年了!
在ViewController实现指定构造函数如下:
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
// 增加断点
self= [superinitWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
}
returnself;
}
调用堆栈截图如下:
第二种使用 XIB 加载控制器(很古老的技术,知道就好,不知道不行!)
选中File's Owner并且在右侧面板中设置控制器的子类(注意 必须先指定类 才能继续下面的)
view 连线控制器
用代码加载 XIB
在AppDelegate中实现以下代码
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
_window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
// 系统会优先加载与控制器完全重名的 XIB
// 如果没有找到,会查找是否有缺少 `Controller` 的 XIB
_window.rootViewController= [[ViewController alloc] init];
[_window makeKeyAndVisible];returnYES;}
第三种.用代码加载 storyboard 的入口控制器
在AppDelegate实现以下代码
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
_window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
// 1. 加载
StoryboardUIStoryboard*sb = [UIStoryboardstoryboardWithName:@"Main"bundle:nil];
// 2. 获取 Storyboard 的入口控制器
UIViewController*vc = [sb instantiateInitialViewController];
// 3. 设置根视图控制器
_window.rootViewController= vc;
[_window makeKeyAndVisible];
return YES;
}
用代码加载 storyboard 的其他控制器
在Main.storyboard中新建一个其他的控制器,不做任何连线,Xcode 会有以下提示:
warning: Unsupported Configuration:
Scene is unreachable due to lack of entry points and does not have an
identifier for runtime access via
-instantiateViewControllerWithIdentifier:.
警告:不支持的配置
场景无法到达,因为:
缺少入口点
没有指定一个标识符,从而无法在运行时通过
instantiateViewControllerWithIdentifier:
访问
原因就是给每个SB 添加标识符
修改AppDelegate中的代码
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
_window = [[UIWindowalloc] initWithFrame:[UIScreenmainScreen].bounds];
// 1. 加载 StoryboardUIStoryboard*sb = [UIStoryboardstoryboardWithName:@"Main"bundle:nil];
// 2.1 获取 Storyboard 的入口控制器//
UIViewController *vc = [sb instantiateInitialViewController];
// 2.2 根据 storyboard 标识符创建控制器
UIViewController*vc = [sb instantiateViewControllerWithIdentifier:@"mainTableViewController"];
// 3. 设置根视图控制器
_window.rootViewController= vc;
[_window makeKeyAndVisible];
returnYES;
}
视图的生命周期
目标1
知道 4 个在viewDidLoad方法之前会被执行的方法
-initWithNibName:bundle:
-initWithCoder:
-awakeFromNib
-loadView
/ 纯代码开发调用的构造函数
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
// 增加断点
return [superinitWithNibName:nibNameOrNil bundle:nibBundleOrNil];
}
// IB 开发时调用的构造函数
- (instancetype)initWithCoder:(NSCoder*)coder {
self= [superinitWithCoder:coder];
if(self) {
// 增加断点}returnself;
}
// 将 NIB 文件唤醒之后执行,IB 开发自定义视图时通常在此方法中做一些初始工作,ViewController 同样有此方法// 是 NSObject 的分类方法
- (void)awakeFromNib {
// 增加断点,如果 po self.view,再继续执行,不会停在 loadView 方法}
loadView方法的设计目的就是创建视图控制器的根视图
可以说:loadView方法是苹果为纯代码开发准备的一个方法
在此方法中,程序员应该负责完成界面层次结构的搭建
所有工作与在 IB 中拖拽控件等效
注意: loadView方法是一个不需要super的方法,一旦实现了loadView方法,同时不super,IB 中的一切都是浮云,在调用视图控制器的view时,如果view == nil就会调用此方法创建视图
原因:
loadView 不要轻易 super
一旦调用 super,会执行系统默认的加载 stroyboard 或者 xib 的工作,之前的代码全部白写
在 loadView 中实现以下代码,注释和打开 [super loadView] 观察运行效果
- (void)loadView {
self.view= [[UIViewalloc] init];self.view.backgroundColor= [UIColorredColor];
// 一旦调用 super,会执行系统默认的加载 stroyboard 或者 xib 的工作,之前的代码全部白写[superloadView];}
loadView 方法小结
如果view为 nil,会调用此方法创建view
用纯代码开发建立视图层次结构时,视图加载完成后调用viewDidLoad方法
在viewDidLoad方法中做其他附加的加载任务,例如:加载数据
提示:由于loadView和viewDidLoad是顺序调用的
在商业代码中,也会见到很多项目在viewDidLoad方法中创建视图层次结构
视图控制器生命周期方法以及调用顺序
目标理解视图控制器生命周期方法以及调用顺序
提示:关于视图的生命周期方法只需要有印象即可,不需要死记硬背
实际开发中,有些特殊的场景,可以通过断点决定具体在哪个生命周期方法中实现代码
视图生命周期方法
/// 将要移动到父视图
- addSubview 方法会触发
- (void)willMoveToSuperview:(UIView*)newSuperview {
[superwillMoveToSuperview:newSuperview];
NSLog(@"%@ %s", [selfclass], __FUNCTION__);
}
/// 已经移动到父视图
- (void)didMoveToSuperview {
[superdidMoveToSuperview];
NSLog(@"%@ %s %@", [selfclass], __FUNCTION__,self.superview);}
/// 将要显示在窗口
- (void)willMoveToWindow:(UIWindow*)newWindow {
[superwillMoveToWindow:newWindow];
NSLog(@"%@ %s", [selfclass], __FUNCTION__);}
/// 已经显示在窗口
- (void)didMoveToWindow {
[superdidMoveToWindow];
NSLog(@"%@ %s %@", [selfclass], __FUNCTION__,self.window);}
注意 一点 很重要的
当视图当前没有显示时,其window属性 == nil
内存警告方法
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
NSLog(@"%@ %s", [selfclass], __FUNCTION__);}
探索头文件
- (void)didReceiveMemoryWarning;
// Called when the parent application receives a memory warning.
On iOS 6.0 it will no longer clear the view by default.
- 当应用程序接收到内存警告后会调用-
在 iOS6.0之后,默认不会再销毁视图
注意:如果控制器对子视图强引用,再接收到内存警告时,根视图被释放后,子视图不会被释放( 注意 :很多人都是直接写个强引用的 变量 指向 创建的子视图 这样是不太好的 打破了视图的生命周期)
在接收到内存警告后,可以释放当前没有显示的视图
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
NSLog(@"%@ %s", [selfclass], __FUNCTION__);
// 判断当前视图是否已经被加载,并且没有显示!if(self.isViewLoaded&&self.view.window==nil) {
// 释放根视图
self.view=nil;
}
}
内存警告释放子控件,需要用纯代码的方式演练,才能够看清执行顺序
视图被释放后,在下次点击时,会重新调用loadView方法创建视图层次结构
在内存警告时,还应该释放控制器自身强引用的属性!
两个已经被废弃,但是需要知道的方法(ios 6.0)
viewWillUnload
viewDidunload
Storyboard 中的 Segue
Storyboard 上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue)
Segue 的属性
每一个 Segue 对象,都有 3 个属性
/// 唯一标识
@property (nullable, nonatomic, copy, readonly) NSString *identifier;
/// 来源控制器
@property (nonatomic, readonly) __kindof UIViewController *sourceViewController;
/// 目标控制器
@property (nonatomic, readonly) __kindof UIViewController *destinationViewController;
Segue 的类型
根据 Segue 的执行(跳转)时刻,Segue 可以分为 2 大类型:
自动型:点击某个控件后(比如按钮),自动执行Segue,自动完成界面跳转
按住 Control 键,直接从控件拖线到目标控制器
如果点击某个控件,不需要做任何判断,直接跳转到下一个界面,建议使用“自动型Segue”
手动型:需要通过写代码手动执行 Segue,才能完成界面跳转
手动型的 Segue 需要设置一个标识
如果点击某个控件,需要做一些处理之后才跳转到下一个界面,建议使用“手动型Segue”
在需要的时刻,由来源控制器执行 perform 方法调用对应的 Segue
[self performSegueWithIdentifier:@"segueId"sender:nil];
performSegueWithIdentifier 方法
调用 sourceViewController 的下面方法,做跳转前的准备工作并传入创建好的 Segue 对象
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender;
重要提示:
sender 是调用 performSegueWithIdentifier:sender:
方法时传入的任意对象所有通过segue执行的控制器跳转都会执行prepareForSegue:sender:
方法