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.

視圖控制器負(fù)責(zé)創(chuàng)建視圖,組織視圖,并處理視圖的相關(guān)事件。

UIViewController的子類會(huì)繼承一個(gè)重要的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.

此屬性指向一個(gè)視圖,是控制器的根視圖。

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.

控制器的視圖只有在需要顯示時(shí)才會(huì)被創(chuàng)建,延遲加載。

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

有兩種方式創(chuàng)建視圖控制器的視圖:

  1. 代碼方式,重寫 UIViewController 類的 loadView 方法。
  2. 通過Interface Builder,在界面上拖拽。

代碼方式創(chuàng)建控制器視圖

通過重寫UIViewControllerloadView方法來創(chuàng)建視圖。

#import 

// 繼承UIViewController類
@interface BKHypnosisViewController : UIViewController

@end
#import "BKHypnosisViewController.h"
#import "BKHypnosisView.h"

@implementation BKHypnosisViewController

// loadView方法為ViewController創(chuàng)建視圖
- (void)loadView{
    // 創(chuàng)建自定義的視圖
    BKHypnosisView *backgroundView = [[BKHypnosisView alloc] init];
    // 設(shè)置ViewController's view property
    self.view = backgroundView;
}

@end

將自定義的ViewController設(shè)置給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設(shè)置給window的rootViewController property
    self.window.rootViewController = hvc;
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Interface Builder方式創(chuàng)建視圖控制器視圖

新建一個(gè)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創(chuàng)建對(duì)應(yīng)的.xib文件,在xib文件上創(chuàng)建視圖界面。

ViewController加載NIB文件

ViewController可以通過initWithNibName方法來創(chuàng)建,傳入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設(shè)置給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. 創(chuàng)建ViewController類,聲明相應(yīng)的IBOutlet屬性及IBAction方法
  2. 創(chuàng)建ViewController的XIB文件,在Interface Builder上畫頁(yè)面
  3. 將XIB文件的File's Owner的Class設(shè)置為對(duì)應(yīng)的ViewController類
  4. 將XIB中的控件和ViewController中的outlet和action關(guān)聯(lián)起來
  5. 通過NIB文件生成ViewController(通過調(diào)用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中有個(gè)數(shù)組,包含其他view controller,標(biāo)簽欄中的每個(gè)標(biāo)簽對(duì)應(yīng)數(shù)組中的一個(gè)view controller。
每個(gè)標(biāo)簽由一個(gè)標(biāo)題和圖片組成,數(shù)組中的每個(gè)view controller都有一個(gè)tabBarItem屬性,這個(gè)屬性定義了標(biāo)簽的標(biāo)題和圖片。

- (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),所以即使調(diào)用 init 方法,最終調(diào)用的還是initWithNibName:bundle: 方法,只不過nibNamenibBundle都是nil

  1. 如果ViewController是通過代碼方式構(gòu)建視圖的,當(dāng)最終調(diào)用initWithNibName:bundle: 方法時(shí),nibNamenibBundle都是nil,無法生成控制器的view,view是nil,此時(shí)控制器會(huì)去調(diào)用loadView方法來創(chuàng)建視圖。

  2. 如果ViewController是通過Interface Builder方式構(gòu)建視圖的,當(dāng)調(diào)用init方法時(shí)(即最終調(diào)用initWithNibName:bundle:方法,并且nibNamenibBundle都是nil),則會(huì)默認(rèn)在applicaton bundle中查詢與ViewController同名的NIB文件。所以約定XIB文件應(yīng)該和ViewController命名一致。

Local Notification

* 與之相對(duì)的是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,那什么時(shí)候可以訪問呢?

當(dāng)控制器加載完view后,會(huì)調(diào)用 viewDidLoad 方法(只會(huì)調(diào)用一次)。
當(dāng)控制器準(zhǔn)備將view顯示到window時(shí),會(huì)調(diào)用 viewWillAppear 方法(每次被顯示window之前都會(huì)被調(diào)用)。

在ViewController的這兩個(gè)方法中都可以訪問控制器的view's subview,他們的區(qū)別:

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. 應(yīng)用啟動(dòng)時(shí)被調(diào)用的方法,在此方法中設(shè)置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被創(chuàng)建的時(shí)候都會(huì)調(diào)用此方法
loadView: is overridden to create a view controller’s view programmatically. 代碼方法創(chuàng)建ViewController's view時(shí)調(diào)用此方法
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 創(chuàng)建完成后,會(huì)調(diào)用此方法,可以在此處對(duì)view自行配置,但只會(huì)被調(diào)用一次
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之前會(huì)調(diào)用此方法

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.

一句話:不要違反命名約定。


本文是對(duì)《iOS Programming The Big Nerd Ranch Guide 4th Edition》第六章的總結(jié)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容