UIViewController
A view controller is an instance of a subclass of UIViewController.
A view controller manages a view hierarchy. It is responsible for creating view objects that make up the hierarchy, for handling events associated with the view objects in its hierarchy, and for adding its hierarchy to the window.
视图控制器负责创建视图,组织视图,并处理视图的相关事件。
UIViewController的子类会继承一个重要的view property:
@property (nonatomic, strong) UIView *view;
This property points to a UIView instance that is the root of the view controller’s view hierarchy.
此属性指向一个视图,是控制器的根视图。
A view controller’s view is not created until it needs to appear on the screen. This optimization is called lazy loading, and it can often conserve memory and improve performance.
控制器的视图只有在需要显示时才会被创建,延迟加载。
To preserve the benefits of lazy loading, you should never access the view property of a view controller in
initWithNibName:bundle:
. Asking for the view in the initializer will cause the view controller to load its view prematurely.
为使用延迟加载,不要在控制器的初始化方法中访问view 属性,否则就不存在所谓延迟加载了。
There are two ways that a view controller can create its view hierarchy:
- programmatically, by overriding the UIViewController method loadView.
- in Interface Builder, by loading a NIB file. (Recall that a NIB file is the file that gets loaded and the XIB file is what you edit in Interface Builder.)
有两种方式创建视图控制器的视图:
- 代码方式,重写
UIViewController
类的loadView
方法。 - 通过Interface Builder,在界面上拖拽。
代码方式创建控制器视图
通过重写UIViewController
的loadView
方法来创建视图。
#import
// 继承UIViewController类
@interface BKHypnosisViewController : UIViewController
@end
#import "BKHypnosisViewController.h"
#import "BKHypnosisView.h"
@implementation BKHypnosisViewController
// loadView方法为ViewController创建视图
- (void)loadView{
// 创建自定义的视图
BKHypnosisView *backgroundView = [[BKHypnosisView alloc] init];
// 设置ViewController's view property
self.view = backgroundView;
}
@end
将自定义的ViewController设置给window的rootViewController property
There is a convenient method for adding a view controller’s view hierarchy to the window: UIWindow’s setRootViewController:. Setting a view controller as the rootViewControlleradds that view controller’s view as a subview of the window. It also automatically resizes the view to be the same size as the window.
#import "BKAppDelegate.h"
#import "BKHypnosisViewController.h"
@implementation BKAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
BKHypnosisViewController *hvc = [[BKHypnosisViewController alloc] init];
// 将自定义的ViewController设置给window的rootViewController property
self.window.rootViewController = hvc;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
Interface Builder方式创建视图控制器视图
新建一个ViewController。
#import "BKReminderViewController.h"
@interface BKReminderViewController()
// 声明私有属性
@property (nonatomic, weak) IBOutlet UIDatePicker *datePicker;
@end
@implementation BKReminderViewController
- (IBAction)addReminder:(id)sender{
NSDate *date = self.datePicker.date;
NSLog(@"Setting a reminder for %@", date);
}
@end
The IBOutlet and IBAction keywords tell Xcode that you will be making these connections in Interface Builder.
为ViewController创建对应的.xib文件,在xib文件上创建视图界面。
ViewController加载NIB文件
ViewController可以通过initWithNibName
方法来创建,传入NIB文件名字,以及从哪查找NIB文件的bundle。
#import "BKAppDelegate.h"
#import "BKHypnosisViewController.h"
#import "BKReminderViewController.h"
@implementation BKAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
// This will get a pointer to an object that represents the app bundle
NSBundle *appBundle = [NSBundle mainBundle];
// 在application bundle中查找BKReminderViewController.xib文件
BKReminderViewController *rvc = [[BKReminderViewController alloc] initWithNibName:@"BKReminderViewController" bundle:appBundle];
// 将自定义的ViewController设置给window的rootViewController property
self.window.rootViewController = rvc;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
The bundle that you are getting by sending the mainBundle message is the application bundle.
- 创建ViewController类,声明相应的IBOutlet属性及IBAction方法
- 创建ViewController的XIB文件,在Interface Builder上画页面
- 将XIB文件的File's Owner的Class设置为对应的ViewController类
- 将XIB中的控件和ViewController中的outlet和action关联起来
- 通过NIB文件生成ViewController(通过调用
initWithNibName
方法)
UITabBarController
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.viewControllers = @[hvc, rvc];
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch
BNRHypnosisViewController *hvc = [[BNRHypnosisViewController alloc] init];
// This will get a pointer to an object that represents the app bundle
NSBundle *appBundle = [NSBundle mainBundle];
// Look in the appBundle for the file BNRReminderViewController.xib
BNRReminderViewController *rvc = [[BNRReminderViewController alloc] initWithNibName:@"BNRReminderViewController" bundle:appBundle];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.viewControllers = @[hvc, rvc];
self.window.rootViewController = tabBarController;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
UITabBarController keeps an array of view controllers. It also maintains a tab bar at the bottom of the screen with a tab for each view controller in this array.
UITabBarController is itself a subclass of UIViewController.
Each tab on the tab bar can display a title and an image. Each view controller maintains a tabBarItem property for this purpose.
UITabBarController中有个数组,包含其他view controller,标签栏中的每个标签对应数组中的一个view controller。
每个标签由一个标题和图片组成,数组中的每个view controller都有一个tabBarItem属性,这个属性定义了标签的标题和图片。
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Get the tab bar item
UITabBarItem *tbi = self.tabBarItem;
// Give it a label
tbi.title = @"Reminder";
// Give it an image
UIImage *i = [UIImage imageNamed:@"Time.png"];
tbi.image = i;
}
return self;
}
initWithNibName:bundle:
是 UIViewController
的指定初始化方法(designated initializer),所以即使调用 init
方法,最终调用的还是initWithNibName:bundle:
方法,只不过nibName和nibBundle都是nil。
如果ViewController是通过代码方式构建视图的,当最终调用
initWithNibName:bundle:
方法时,nibName和nibBundle都是nil,无法生成控制器的view,view是nil,此时控制器会去调用loadView方法来创建视图。如果ViewController是通过Interface Builder方式构建视图的,当调用init方法时(即最终调用
initWithNibName:bundle:
方法,并且nibName和nibBundle都是nil),则会默认在applicaton bundle中查询与ViewController同名的NIB文件。所以约定XIB文件应该和ViewController命名一致。
Local Notification
* 与之相对的是Push Notification
Getting a local notification to display is easy. You create a UILocalNotificationand give it some text and a date. Then you schedule the notification with the shared application – the single instance of UIApplication.
- (IBAction)addReminder:(id)sender
{
NSDate *date = self.datePicker.date;
NSLog(@"Setting a reminder for %@", date);
UILocalNotification *note = [[UILocalNotification alloc] init];
note.alertBody = @"Hypnotize me!";
note.fireDate = date;
[[UIApplication sharedApplication] scheduleLocalNotification:note];
}
Loaded View
Often, you will want to do some extra initialization of the subviews that are defined in the XIB file before they appear to the user. However, you cannot do this in the view controller’s initializer because the NIB file has not yet been loaded. If you try, any pointers that the view controller declares that will eventually point to subviews will be pointing to nil.
不能在控制器初始化方法中访问view's subview,那什么时候可以访问呢?
当控制器加载完view后,会调用 viewDidLoad
方法(只会调用一次)。
当控制器准备将view显示到window时,会调用 viewWillAppear
方法(每次被显示window之前都会被调用)。
在ViewController的这两个方法中都可以访问控制器的view's subview,他们的区别:
You override viewDidLoad if the configuration only needs to be done once during the run of the app.
You override viewWillAppearif you need the configuration to be done and redone every time the view controller appears on screen.
- (void)viewDidLoad
{
// Always call the super implementation of viewDidLoad
[super viewDidLoad];
NSLog(@"BNRHypnosisViewController loaded its view.");
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.datePicker.minimumDate = [NSDate dateWithTimeIntervalSinceNow:60];
}
Method Summary
method | description | 一句话 |
---|---|---|
application:didFinishLaunchingWithOptions: |
is where you instantiate and set an application’s root view controller.This method gets called exactly once when the application has launched. Even if you go to another app and come back, this method does not get called again. If you reboot your phone and start the app again, this method will get called again. | 应用启动时被调用的方法,在此方法中设置APP的rootViewController |
initWithNibName:bundle: |
is the designated initializer for UIViewController. When a view controller instance is created, its initWithNibName:bundle: gets called once. Note that in some apps, you may end up creating several instances of the same view controller class. This method will get called once on each as it is created. |
此方法是ViewController的指定初始化方法,每次ViewController被创建的时候都会调用此方法 |
loadView: |
is overridden to create a view controller’s view programmatically. | 代码方法创建ViewController's view时调用此方法 |
viewDidLoad |
can be overridden to configure views created by loading a NIB file. This method gets called after the view of a view controller is created. | ViewController's view 创建完成后,会调用此方法,可以在此处对view自行配置,但只会被调用一次 |
viewWillAppear: |
can be overridden to configure views created by loading a NIB file. This method and viewDidAppear: will get called every time your view controller is moved on screen. viewWillDisappear: and viewDidDisappear: will get called every time your view controller is moved off screen. So if you launch the app you are working on and hop back and forth between Hypnosisand Reminder, BNRReminderViewController’s viewDidLoadmethod will be called once, but viewWillAppear: will be called dozens of times. |
ViewController's view在每次显示到window之前会调用此方法 |
Key-Value Coding
When a NIB file is read in, the outlets are set using a mechanism called Key-value coding(or KVC). Key-value coding is a set of methods defined in NSObjectthat enable you to set and get the values of properties by name.
- (id)valueForKey:(NSString *)k;
- (void)setValue:(id)v forKey:(NSString *)k;
valueForKey:
is a universal getter method. You can ask any object for the value of its fido property. like this:
id currentFido = [selectedObj valueForKey:@"fido"];
If there is a
fido
method (the fido-specific getter), it will be called and the returned value will be used. If there is no fido method, the system will go looking for an instance variable named _fido or fido. If either instance variable exists, the value of the instance variable will be used. If neither an accessor nor an instance variable exists, an exception is thrown.
setValue:forKey:
is a universal setter method. It lets you set the value of an object’s fido property. like this:
[selectedObject setValue:userChoice forKey:@"fido"];
If there is a
setFido:
method, it will be called. If there is no such method, the system will go looking for a variable named _fido or fido and set the value of that variable directly. If neither the accessor nor either instance variable exists, an exception will be thrown.
**The most important moral of this section: **
Using the accessor method naming conventions is more than just something nice you do for other people who might read your code. The system expects that a method called setFido:
is the setter for the fido property. The system expects that the method fido
is the getter for the fido property. Bad things happen when you violate the naming conventions.
一句话:不要违反命名约定。
本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第六章的总结。