View Controllers

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.)

有两种方式创建视图控制器的视图:

  1. 代码方式,重写 UIViewController 类的 loadView 方法。
  2. 通过Interface Builder,在界面上拖拽。

代码方式创建控制器视图

通过重写UIViewControllerloadView方法来创建视图。

#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.

  1. 创建ViewController类,声明相应的IBOutlet属性及IBAction方法
  2. 创建ViewController的XIB文件,在Interface Builder上画页面
  3. 将XIB文件的File's Owner的Class设置为对应的ViewController类
  4. 将XIB中的控件和ViewController中的outlet和action关联起来
  5. 通过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: 方法,只不过nibNamenibBundle都是nil

  1. 如果ViewController是通过代码方式构建视图的,当最终调用initWithNibName:bundle: 方法时,nibNamenibBundle都是nil,无法生成控制器的view,view是nil,此时控制器会去调用loadView方法来创建视图。

  2. 如果ViewController是通过Interface Builder方式构建视图的,当调用init方法时(即最终调用initWithNibName:bundle:方法,并且nibNamenibBundle都是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》第六章的总结。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容