SlideShare une entreprise Scribd logo
1  sur  74
Télécharger pour lire hors ligne
视图控制器(View Controllers)




范圣刚,princetoad@gmail.com, www.tfan.org
Quiz 和 QuizViewController 回顾
• 在第⼀一个 session 的 Quiz 应⽤用程序中,我们把所
 有的代码都写在 QuizViewController 类中。这个类
 的⼀一个实例是 Quiz 应⽤用的控制器
• 它有指向屏幕上的 labels 的指针,指向按钮的指
 针,这些按钮被按下时会给它发送消息。还有指
 向组成应⽤用数据的模型对象的指针
• 对于界⾯面只有⼀一屏的 Quiz 应⽤用,只有⼀一个
 controller 对象就⾜足够了
• ⼤大多数应⽤用具有不⽌止⼀一个屏幕的界⾯面,⽽而且有⾮非
 常多的对象需要进⾏行管理,设计 iOS 应⽤用时最好
 针对每⼀一屏都有⼀一个 controller
UIViewController
• ⼀一个 UIViewController 实例专注在控制应⽤用程序内的
 ⼀一个单独的屏幕

• 每个 UIViewController 具有⼀一个指向 UIView 或者
 UIView ⼦子类的实例的 view 属性,这个 view 就是它
 的 screen

• 通常情况下,这个 view 是⼀一个全屏视图。⽽而且更多
 的时候,这个 view 也具有 subviews。这样⼀一个 view
 controller 控制的就是⼀一个 view hierarchy

• 因为这个 view 属性是整个 hierarchy 的 root,当 view
 controller 的 view 被作为⼀一个subview 添加到
 window 时,整个 view hierarchy 就被添加了
创建 HypoTime
从 Empty Application 模版创建⼀一个新
的 iOS 项⺫⽬目,命名为 HypnoTime。
按右边的图进⾏行配置:
两个 UIViewController 的⼦子类
• 这个应⽤用我们将创建两个 UIViewController 的⼦子类
 • HypnosisViewController
 • TimeViewController
• 每个 view controller 将会控制⼀一个 view,⽤用户将
 能够在这些 views 之间切换,取决于他们是否想
 被催眠或者只是想知道现在是⼏几点
• view 之间的切换将会由另外⼀一个类
 UITabBarController 来处理
⼦子类化 UIViewController
• 我们不会直接创建
 UIViewController 的实
 例,⽽而是创建
 UIViewController 的⼦子
 类来实例化

• ⽣生成
 HypnosisViewControlle
 r(File -> New File ->
 iOS -> Cocoa Touch ->
 Objective-C class)
HypnosisViewController
• view controller 负责创建它的 view hierarchy
• HypnosisViewController 的 view hierarchy 将仅由
 ⼀一个 view 组成。就是我们前⾯面创建的 UIView 的
 ⼦子类 HypnosisView
• 在 Finder ⾥里定位到 HypnosisView.h 和
 HypnosisView.m ⽂文件,把他们拖到 HypnoTime 项
 ⺫⽬目导航器中
• 在出现的窗⼝口中,勾选“Copy items into
 destination group’s folder”
• 同时勾选把它们添加到 target - “HypnoTime”
添加 HypnosisView 到 HypnoTime
loadView
 • UIViewController ⼦子类通过重写 loadView ⽅方法来创
    建它的 view hierarchy
 • 这个⽅方法创建⼀一个 view 的实例,并且把它设成
    view controller 的 view
 • 在 HypnosisViewController.m 中重写 loadView,确
    保在⽂文件顶部导⼊入了 HypnosisView 的头⽂文件
// 重写 loadView
- (void)loadView
{
    // 创建⼀一个 view
    CGRect frame = [[UIScreen mainScreen] bounds];
    HypnosisView *view = [[HypnosisView alloc] initWithFrame:frame];
    // 把它设成 view controller 的 view
    [self setView:view];
}
重写 loadView (代码)
 #import "HypnoViewController.h"
 // 导⼊入 HypnosisView 头⽂文件
 #import "HypnosisView.h"

 @interface HypnoViewController ()

 @end

 @implementation HypnoViewController

 // 重写 loadView
 - (void)loadView
 {
     // 创建⼀一个 view
     CGRect frame = [[UIScreen mainScreen] bounds];
     HypnosisView *view = [[HypnosisView alloc] initWithFrame:frame];
     // 把它设成 view controller 的 view
     [self setView:view];
 }
rootViewController
• 在 HypnoAppDelegate.m 中,创建⼀一个
 HypnosisViewController 的实例并且把它设成
 UIWindow 的 rootViewController
• 确保在⽂文件顶部导⼊入了 HypnosisViewController.h
 头⽂文件
设置 rootViewController(代码)

#import "HypnoAppDelegate.h"
// 导⼊入 HypnosisViewController 的头⽂文件
#import "HypnoViewController.h"

@implementation HypnoAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    // 实例化⼀一个 HypnosisViewController 的实例,并设置成 window 的 rootViewController
    HypnoViewController *hvc = [[HypnoViewController alloc] init];
    [[self window] setRootViewController:hvc];

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
HypnosisViewController 的
     HypnosisView
应⽤用的对象⽰示意图
• 把⼀一个 view controller
 设成 window 的
 rootViewController 就把
 这个 view controller 的
 view 添加成了 window
 的 subview

• 同时把 view 的⼤大⼩小⾃自
 动调整成和 window 同
 样⼤大⼩小
setRootViewController
    • 如果我们⾃自⼰己写 UIWindow 的
     setRootViewController, ⼤大概是下⾯面这样:


// UIWindow setRootViewController
- (void)setRootViewController:(UIViewController *)vc
{
    UIView *rootView = [vc view];

     CGRect viewFrame = [self bounds];
     [rootView setFrame:viewFrame];
     [self addSubview:rootView];
}
第⼆二个 UIViewController
• HypnoTime 的第⼆二个 UIViewController 的⼦子类
   • TimeViewController
• TimeViewController 的 screen 将会显⽰示⼀一个
   UILabel,和⼀一个把 label 更新到当前时间的
   UIButton
• 这个 UIView 将会具有两个 subview:按钮和标签
#import <UIKit/UIKit.h>

@interface TimeViewController : UIViewController

@end
TimeViewController 的 view
        hierarchy
编程或者从 XIB 创建 view
• 创建 HypnosisViewController 的 view hierarchy 时,
 我们通过编程来实现的:在 loadView 中,实例化了
 ⼀一个 HypnosisView, 然后把它设置成 view (view
 controller 的 view 属性)。这个 view 没有任何的
 subview
• TimeViewController 的 view 将会有两个 subview。当
 ⼀一个 view controller 的 view 具有 subview 的时候,
 最好在 XIB ⽂文件中创建和加载它的 view hierarchy,
 ⽽而不是重写 loadView
• 程序运⾏行起来以后,通过编程或者从 XIB ⽂文件创建的
 view 没有什么区别,只是 XIB ⽂文件更易于放置多个
 view objects
TimeViewController.xib
• File -> New -> File, iOS, User Interface, Empty 模版
• 弹出窗⼝口的 Device Family 选“iPhone”
• 命名为 TimeViewController
• 命名假设的重要性
• 在 project navigator 中选中这个⽂文件以使其在
 editor area 显⽰示
File’s Owner
• XIB ⽂文件是如何⼯工作的的?
• XIB ⽂文件包含的是对象:把对象拖到画布上就把对
 象保存到 XIB ⽂文件了。当 XIB ⽂文件被加载的时候,
 这些对象被加载回进内存中
TimeViewController 的 XIB ⽂文件
•在
 TimeViewController.xib
 中,拖拽⼀一个 UIView
 到画布上,然后拖拽
 ⼀一个 UIButton 和⼀一个
 UILabel 到 view 上

• 给按钮⼀一个标题:
 What time is it?

• 给标签⽂文本: ???, 然后
 让它居中
TimeViewController.xib 中的 Objects
• 我们可以看到这些对
 象都显⽰示在 Objects 段
 下⾯面

• 显⽰示在 Objects 段下⾯面
 的对象都是被保存到
 XIB ⽂文件中的对象

• 这种类型的存储有⼀一
 个专有名词叫做:
 archiving,这些对象
 也就被称作 archived
 objects
placeholder 对象
• XIB ⽂文件中还有另外⼀一
 种类型的对象:
 placeholder objects

• Placeholder 区段下⾯面
 有两个对象:
 • File’s Owner(重要!)
 • First Responder
File’s Owner
• 为什么需要 File’s Owner?
• 当 view controller 加载它的 view 的时候,它会设置
 view 属性,这样它就知道它的 view 是什么然后可以把
 它放到屏幕上
• 对于 HypnosisViewController,我们通过编程来做这
 些,这些操作在 loadView 中实现并且所有的设置都是
 在编译期间完成的
• TimeViewController 并⾮非如此,当⼀一个
 TimeViewController 的实例需要加载 view 时,它将会加
 载 XIB ⽂文件,这时 XIB ⽂文件中的所有的 archived objects
 都会被创建,然⽽而 TimeViewController 并不知道这些对
 象中的哪⼀一个是它的 view
作为 hole 的 File’s Owner
• File’s Owner 是 XIB ⽂文件的⼀一个 hole
• 在配置界⾯面的时候,我们在 XIB ⽂文件中的对象和
 File’s Owner 之间建⽴立连接
• 当 XIB ⽂文件被加载时,TimeViewController 把它⾃自
 ⼰己置⼊入 File’s Owner 的 hole,然后所有的在 File’s
 Owner 和 archived objects 之间的连接都成了到
 TimeViewController 的
• 为了能够设置 TimeViewController 需要的连接,我
 们必须告诉 Xcode TimeViewController 是要把⾃自⼰己
 放到这个 hole ⾥里的对象的类
Identity inspector for File’s Owner
• 在 outline view 选中
 File’s Owner 对象,然
 后点击 inspector 区域
 的“Identity
 inspector”,把 Custom
 Class 下⾯面的 class 改成
 “TimeViewController”
view outlet
• Control-click File’s Owner
 弹出可⽤用连接的⾯面板,我
 们指定的类是⼀一个
 UIViewController 的⼦子
 类,我们就被提供了⼀一个
 view outlet

• 把 view outlet 连到 XIB ⽂文
 件的 UIView 对象

• 现在当
 TimeViewController 加载
 XIB ⽂文件时,就具有了适
 当的连接,并且可以加载
 它的 view 了
File’s Owner
outlet
• 这样在 XIB ⽂文件中的⼀一个 outlet 连接就等同于给
 outlet 对应的对象发送⼀一个基于 outlet 名称的
 setter 消息
• 例如,当 XIB ⽂文件被加载时,把⼀一个对象设置成
 view outlet,将会发送⼀一个 setView: 消息给那个
 对象,这个⽅方法的参数是连接那头的对象
TimeViewController 作为 rootViewController

• 打开 HypnoAppDelegate.m, 创建⼀一个
  TimeViewController 的实例,把它设成 window
  的 rootViewController。确保导⼊入
  TimeViewController.h 头⽂文件
• 构建并运⾏行,我们可以看到在
  TimeViewController.xib 中创建的
  TimeViewController 的 view
TimeViewController 作为 rootViewController


#import "HypnoAppDelegate.h"
// 导⼊入 TimeViewController 头⽂文件
#import "TimeViewController.h"

@implementation HypnoAppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary
*)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
    TimeViewController *tvc = [[TimeViewController
alloc] init];
    [[self window] setRootViewController:tvc];

    self.window.backgroundColor = [UIColor
whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
outlet 和 method
 • TimeViewController 在控制这个 view hierarchy,
   TimeViewController 负责在按钮被按下时更新
   label,所以TimeViewController 要作为 UIButton 的
   target,并且要具有⼀一个指向 UILabel 的指针
 • 在 TimeViewController.h 中声明⼀一个 outlet 和⼀一个
   ⽅方法
#import <UIKit/UIKit.h>

@interface TimeViewController : UIViewController
{
    IBOutlet UILabel *timeLabel;
}

- (IBAction)showCurrentTime:(id)sender;
@end
建⽴立连接
• 打开 TimeViewController.xib,把 File’s Owner 的
 timeLabel outlet 连接到 UILabel
• 然后连接 UIButton 到 File’s Owner,并且选择
 showCurrentTime:
• 这些连接如下⼀一⻚页的图所⽰示:
TimeViewController
在 TimeViewController.m 中实现 action ⽅方法



- (IBAction)showCurrentTime:(id)sender
{
    NSDate *now = [NSDate date];

    NSDateFormatter *formatter = [[NSDateFormatter
alloc] init];
    [formatter
setTimeStyle:NSDateFormatterMediumStyle];

    [timeLabel setText:[formatter
stringFromDate:now]];
}
现在应⽤用的对象⽰示意图
回顾:view controller 和 加载 view
• view controller 控制⼀一个 screen,这个 screen 就是⼀一个
 view
• 这个 view controller 的 view,⼜又将具有 subviews,构
 成了⼀一个 view hierarchy
• ⼀一个 view controller 可以通过两种途径得到它的
 view:
 • 其 view 没有任何 subviews 的 view controller 可以通过重写
  loadView: 来创建⼀一个 view 实例,然后给⾃自⼰己发送
  setView:

 • 具有⾮非常复杂 view hierarchy 的 view controller 可以通过把
  它的 view outlet 连接到 XIB 中 archived 的顶层 view 的⽅方式
  从⼀一个 XIB ⽂文件中加载它的 view
UITabBarController
• view controller 可以根据⽤用户的动作呈现另⼀一个
 view controller 出来

• UITabBarController 就是这类 view controllers 之
 ⼀一,我们将使⽤用⼀一个 UITabBarController 在
 HypnosisViewController 和 TimeViewController 的
 实例之间切换

• UITabBarController 持有⼀一个 UIViewController 的数
 组,它也在屏幕底部维护了⼀一个标签栏(tab bar),
 其数组中的每⼀一个 view controller 都有⼀一个标签

• 点击标签的结果就是和这个标签关联的 view
 controller 的 view 被呈现出来
创建⼀一个 UITabBarController
• 在 HypnoAppDelegate.m 中,创建⼀一个
 UITabBarController 的实例,把两个 view controller
 都给它,然后把它设置成 window 的
 rootViewController
创建 UITabBarController(代码)

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]
bounds]];
    // Override point for customization after application launch.
    HypnoViewController *hvc = [[HypnoViewController alloc] init];
    TimeViewController *tvc = [[TimeViewController alloc] init];

    UITabBarController *tabBarController = [[UITabBarController alloc]
init];

    NSArray *viewControllers = [NSArray arrayWithObjects:hvc, tvc, nil];
    [tabBarController setViewControllers:viewControllers];

    [[self window] setRootViewController:tabBarController];

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
点击底部的⿊黑⾊色标签可以切换 view controller
UITabBarController 的 view
• UITabBarController 是 window 的
 rootViewController。这意味着 UITabBarController
 本⾝身是 UIViewController的⼀一个⼦子类
• 那么它就具有 view 属性,它的 view 是⼀一个具有
 两个 subviews 的空⽩白 UIView
 • tab bar 标签栏
 • 选中的 view controller 的 view
UITabBarController ⽰示意图
tabBarItem 属性和 UITabBarItem
• 标签栏上的每个标签可以显⽰示⼀一个 title 和⼀一个
 image

• 为了这个⺫⽬目的,每个 view controller 维护⼀一个
 tabBarItem 属性

• 当⼀一个 view controller 被⼀一个 UITabBarController
 包含时,它的标签栏条⺫⽬目出现在标签栏上
UITabBarItem ⽰示例
为两个 view controller 设置标题
• 打开 HypnosisViewController.m, 重写
 UIViewController 的 designated initializer,
 initWithNibName:bundle:, 为
 HypnosisViewController 获得然后设置⼀一个 tab bar
 item
• 然后打开 TimeViewController.m, 进⾏行同样的操作
设置 UITabBarItem 的代码
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        // 获取 tab bar item
        UITabBarItem *tbi = [self tabBarItem];
        // 给它⼀一个标题
        [tbi setTitle:@"Hypnosis"];
    }
    return self;
}


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        UITabBarItem *tbi = [self tabBarItem];
        [tbi setTitle:@"Time"];
    }
    return self;
}
设置了标题的 tabBarItem
为每⼀一个标签增加图⽚片
• 把图⽚片⽂文件Hypno.png, Time.png, Hypno@2x.png,
 Time@2x.png 拖到项⺫⽬目中,勾选拷⻉贝到项⺫⽬目⺫⽬目录
 选项
• 然后再编辑 initWithNibName:bundle: 的代码
设置标签栏图⽚片的代码
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        UITabBarItem *tbi = [self tabBarItem];
        [tbi setTitle:@"Hypnosis"];

        UIImage *image = [UIImage imageNamed:@"Hypno.png"];

        [tbi setImage:image];
    }
    return self;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        UITabBarItem *tbi = [self tabBarItem];
        [tbi setTitle:@"Time"];

        UIImage *image = [UIImage imageNamed:@"Time.png"];

        [tbi setImage:image];
    }
    return self;
}
设置了 image 的标签栏
view controller 的⽣生命周期
• ⼀一个 view controller 当被 allocated, 然后发送⼀一
 个 initializer 消息后就开始了它的⽣生命周期

• ⼀一个 view controller 将会看到它的 view 被创建,
 移动到屏幕上,移离屏幕,销毁,以及再次创建 -
 这个过程也许会有很多次以上

• 这些事件构成了 view controller 的⽣生命周期
初始化 view controller
• UIViewController 的 designated initializer 是
 initWithNibName:bundle: 。这个⽅方法使⽤用两个参
 数指定了在 bundle ⾥里⾯面的 view controller 的 XIB
 ⽂文件的名称
• 两个参数都传⼊入 nil 表⽰示:当加载你的 view 的时
 候,在 application bundle 中搜索名称和你的类名
 相同的 XIB ⽂文件
• 例如: TimeViewController 将会加载
 TimeViewController.xib ⽂文件
• 可以把下⾯面的代码添加到 TimeViewController.m 会
 理解的更清楚⼀一点:
nibNameOrNil

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
//    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    // 获得到 application bundle 对象的指针
    NSBundle *appBundle = [NSBundle mainBundle];
    self = [super initWithNibName:@"TimeViewController" bundle:appBundle];

    if (self) {
        // Custom initialization
        UITabBarItem *tbi = [self tabBarItem];
        [tbi setTitle:@"Time"];

        UIImage *image = [UIImage imageNamed:@"Time.png"];

        [tbi setImage:image];
    }
    return self;
}
init
    • 实际开发过程中,我们⼀一般都使⽤用
       UIViewController ⼦子类的名称作为 XIB ⽂文件的名称
    • 因此,当创建⼀一个 view controller 时,我们只需要
       发送 init ,等同于给 initWithNibName:bundle: 的
       两个参数都传⼊入了 nil
    • 需要的话可以通过重写 initWithNibName:bundle:
       来执⾏行⼀一些额外的初始化⼯工作
TimeViewController *tvc = [[TimeViewController alloc] init];
// 等同于
TimeViewController *tvc = [[TimeViewController alloc] initWithNibName:nil bundle:nil];
UIViewController 和延迟加载
• 当⼀一个 view controller 被实例化的时候,它不会⽴立
 即创建和加载它的 view
• 只有当 view 将要在屏幕上显⽰示的时候,⼀一个 view
 controller 才会特意创建它的 view
• 通过在只有需要的时候才加载视图,应⽤用程序不
 会在它不需要的时候占⽤用内存
viewDidLoad
• 所有的 UIViewController 都实现了⼀一个
 viewDidLoad ⽅方法,当它加载了它的 view 以后就
 会⽴立即执⾏行
• 我们在我们的两个 view controller ⾥里⾯面重写这个⽅方
 法在控制台记录⼀一些消息
• 构建并运⾏行,可以看到控制台报告
 HypnosisViewController ⽴立即加载了它的 view;点
 击 TimeViewController 的标签以后,控制台才报告
 view 现在被加载
 - (void)viewDidLoad
 {
     [super viewDidLoad];
     NSLog(@"TimeViewController 加载了它的 view");
 }
Appear 和 Disappear
 • 除了加载和卸载以外,view controller 也会在特定
   的时间出现或者消失
 • view controller 只会被创建⼀一次,但是它的 view
   通常会显⽰示(释放或者隐藏)多次
 • 我们可以在这些⽣生命周期事件发⽣生时做⼀一些⾃自定
   义的操作,⽐比如我们可以让 TimeViewController 的
   view 每次显⽰示时,可以显⽰示最新时间
- (void)viewWillAppear:(BOOL)animated;    // Called when the view is about
to made visible. Default does nothing
- (void)viewDidAppear:(BOOL)animated;     // Called when the view has been
fully transitioned onto the screen. Default does nothing
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is
dismissed, covered or otherwise hidden. Default does nothing
- (void)viewDidDisappear:(BOOL)animated; // Called after the view was
dismissed, covered or otherwise hidden. Default does nothing
重写 viewWillAppear


   - (void)viewWillAppear:(BOOL)animated
   {
       NSLog(@"CurrentTimeViewController will appear");
       [super viewWillAppear:animated];
       [self showCurrentTime:nil];
   }
   - (void)viewWillDisappear:(BOOL)animated
   {
       NSLog(@"CurrentTimeViewController will disappear");
       [super viewWillDisappear:animated];
   }
view controller 中 view 的⽣生命周期
视图控制器的⼦子类和模版
• 实际开发过程中,我们可以在使⽤用
 UIViewController 模版的同时勾选上⽣生成相应的
 XIB ⽂文件
• 这样 XIB ⽂文件的 File’s Owner 已经被设置成
 UIViewController 的类,同时 UIView 的实例已经被
 挂接到 File’s Owner 的 view outlet
练习1:再加⼀一个 Tab
• 创建⼀一个新的 UIViewController ⼦子类,它的 view
 应该是 MKMapView 的⼀一个实例
• 使这个 view controller 成为 UITabBarController 的
 第三个 view controller
练习2:Controller Logic
• 增加⼀一个 UISegmentedControl 到
 HyposisViewController 的 view,带着三个
 segments:Red, Green 和 Blue
• 当⽤用户 tap segmented control 时,改变
 HyposisView 中 circles 的颜⾊色
补充阅读:
main 函数和 UIApplication

 • C 应⽤用程序都是从执⾏行⼀一个 main 函数开始,
  Objective-C 应⽤用程序也是⼀一样的
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
NSStringFromClass([HypnoAppDelegate class]));
    }
}

• UIApplicationMain 函数⽣生成了⼀一个 UIApplication 类
  的实例;对于每⼀一个应⽤用来说,都有⼀一个单独的
  UIApplication 实例,⽤用于维护 run loop
• UIApplicationMain 还⽣生成⼀一个 作为 UIApplication
  的 delegate 类的实例。最后⼀一个参数就是 delegate
  类的名称
application:didFinishLaunchingWithOptions:

 • 在 run loop 将要开始接收事件前,application 会
  发送给它的 delegate ⼀一个消息,告诉它“ 准备好
  了,我们要开始啦!”,这个消息就是
  application:didFinishLaunchingWithOptions:
 • 我们在 HypnosisAppDelegate.m 中实现这个⽅方法
  来创建在应⽤用程序中使⽤用的 window 和 controller
  对象
 • 所有的 iOS 应⽤用程序都遵循这种模式
补充阅读2:
视⺴⽹网膜屏(Retina) 显⽰示

• Retina 和早期设备的 320x480 像素⽐比较⽽而⾔言具有更⾼高的
 分辨率(640x960)
• 如何才能让图形在两种显⽰示上都能得到最佳显⽰示?
• 对于⽮矢量图形,像 HypnosisView 的 drawRect: ⽅方法和绘
 制⽂文本,同样的代码会渲染的和设备允许的⼀一样清晰
• 如果使⽤用 Core Graphics 函数绘制,这些图形在不同的
 设备上会有不同的表现
• 在 Core Graphics 中,也被称作 Quarz,我们使⽤用名词
 points 描述 lines(直线),curves(曲线),text(⽂文
 本) 等。在⾮非 Retina 显⽰示上,⼀一个 point 是1x1像素;
 在 Retina 显⽰示上,⼀一个 point 是 2x2 像素
不同分辨率的渲染
图像拉伸
• 位图(像 JPEG 或 PNG ⽂文件),如果图像没有适
合设备的屏幕类型,将会变得没有吸引⼒力
• 假如你的应⽤用包含了⼀一个 25x25 像素的⼩小图像,
如果它在 Retina 显⽰示器上显⽰示时,图像必须被拉
伸来覆盖 50x50 的区域。
• 从这⼀一点上来讲,系统使⽤用了⼀一种叫做抗锯齿的
平均化的⽅方式来使图像看起来没有锯齿。结果是
图像没有锯齿 - 但是模糊
拉伸图像后引起的模糊
@2x
• 可以使⽤用⼤大⽂文件替代,但是图像缩⼩小时⼜又会在另⼀一
 个⽅方向引起问题
• 唯⼀一的⽅方案是在应⽤用程序绑定两个⽂文件:⼀一种是在
 ⾮非 Retina 屏幕上采⽤用分辨率等于 points 数量,另
 外⼀一种是在 Retina 显⽰示上采⽤用 points 两倍的分辨
 率
• 幸运的是我们不需要写额外的代码来处理在哪种设
 备上加载哪种图⽚片,我们需要做的就是在⾼高分辨率
 图⽚片后⾯面加上 @2x 后缀。然后当使⽤用 UIImage 的
 imageNamed: ⽅方法来加载图⽚片时,这个⽅方法会在
 bundle 中查找,然后拿到适合特定设备的⽂文件

Contenu connexe

Tendances

Script with engine
Script with engineScript with engine
Script with engine
Webrebuild
 
透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps
Eric ShangKuan
 
冲浪 Object-c
冲浪 Object-c冲浪 Object-c
冲浪 Object-c
jeff kit
 
Javascript之昨是今非
Javascript之昨是今非Javascript之昨是今非
Javascript之昨是今非
Tony Deng
 
Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器
Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器
Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器
Justin Lin
 
Servlet & JSP 教學手冊第二版試讀 - 撰寫與設定 Servlet
Servlet & JSP 教學手冊第二版試讀 - 撰寫與設定 ServletServlet & JSP 教學手冊第二版試讀 - 撰寫與設定 Servlet
Servlet & JSP 教學手冊第二版試讀 - 撰寫與設定 Servlet
Justin Lin
 

Tendances (20)

Script with engine
Script with engineScript with engine
Script with engine
 
透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps
 
01 A Simple iOS Application
01 A Simple iOS Application01 A Simple iOS Application
01 A Simple iOS Application
 
React + mobx分享(黄英杰)
React + mobx分享(黄英杰)React + mobx分享(黄英杰)
React + mobx分享(黄英杰)
 
冲浪 Object-c
冲浪 Object-c冲浪 Object-c
冲浪 Object-c
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCell
 
Asp.net開發要注意的是?
Asp.net開發要注意的是?Asp.net開發要注意的是?
Asp.net開發要注意的是?
 
Html 5 native drag
Html 5 native dragHtml 5 native drag
Html 5 native drag
 
Android layout inflate
Android layout inflateAndroid layout inflate
Android layout inflate
 
Javascript之昨是今非
Javascript之昨是今非Javascript之昨是今非
Javascript之昨是今非
 
Servlet & JSP 教學手冊第二版 - 課後練習解答
Servlet & JSP 教學手冊第二版 - 課後練習解答Servlet & JSP 教學手冊第二版 - 課後練習解答
Servlet & JSP 教學手冊第二版 - 課後練習解答
 
前端自動化工具
前端自動化工具前端自動化工具
前端自動化工具
 
Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器
Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器
Servlet & JSP 教學手冊第二版 - 第 5 章:Servlet 進階 API、過濾器與傾聽器
 
04 Delegation and Core Location
04 Delegation and Core Location04 Delegation and Core Location
04 Delegation and Core Location
 
Ch05 Servlet 進階 API、過濾器與傾聽器
Ch05 Servlet 進階 API、過濾器與傾聽器Ch05 Servlet 進階 API、過濾器與傾聽器
Ch05 Servlet 進階 API、過濾器與傾聽器
 
I os 02
I os 02I os 02
I os 02
 
Kissy editor开发与设计
Kissy editor开发与设计Kissy editor开发与设计
Kissy editor开发与设计
 
I os 09
I os 09I os 09
I os 09
 
A
AA
A
 
Servlet & JSP 教學手冊第二版試讀 - 撰寫與設定 Servlet
Servlet & JSP 教學手冊第二版試讀 - 撰寫與設定 ServletServlet & JSP 教學手冊第二版試讀 - 撰寫與設定 Servlet
Servlet & JSP 教學手冊第二版試讀 - 撰寫與設定 Servlet
 

Similaire à 07 View Controllers

KISSY_Component
KISSY_ComponentKISSY_Component
KISSY_Component
yiming he
 
Clojure cnclojure-meetup
Clojure cnclojure-meetupClojure cnclojure-meetup
Clojure cnclojure-meetup
sunng87
 
Vlog02 [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...
Vlog02  [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...Vlog02  [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...
Vlog02 [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...
SernHao TV
 
Asp.net mvc網站的從無到有
Asp.net mvc網站的從無到有Asp.net mvc網站的從無到有
Asp.net mvc網站的從無到有
Wade Huang
 
Introduction to ASP.NET MVC and MVC 5 Features
Introduction to ASP.NET MVC and MVC 5 FeaturesIntroduction to ASP.NET MVC and MVC 5 Features
Introduction to ASP.NET MVC and MVC 5 Features
Jeff Chu
 

Similaire à 07 View Controllers (20)

I os 10
I os 10I os 10
I os 10
 
06 Subclassing UIView and UIScrollView
06 Subclassing UIView and UIScrollView06 Subclassing UIView and UIScrollView
06 Subclassing UIView and UIScrollView
 
13 UIPopoverController and Modal View Controller
13 UIPopoverController and Modal View Controller13 UIPopoverController and Modal View Controller
13 UIPopoverController and Modal View Controller
 
I os 01
I os 01I os 01
I os 01
 
I os 07
I os 07I os 07
I os 07
 
I os 16
I os 16I os 16
I os 16
 
08 Notification and Rotation
08 Notification and Rotation08 Notification and Rotation
08 Notification and Rotation
 
12 Camera
12 Camera12 Camera
12 Camera
 
005
005005
005
 
掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001
 
105-2 iOS程式設計(六)
105-2 iOS程式設計(六)105-2 iOS程式設計(六)
105-2 iOS程式設計(六)
 
KISSY_Component
KISSY_ComponentKISSY_Component
KISSY_Component
 
利用Xfire创建Web Service
利用Xfire创建Web Service利用Xfire创建Web Service
利用Xfire创建Web Service
 
Clojure cnclojure-meetup
Clojure cnclojure-meetupClojure cnclojure-meetup
Clojure cnclojure-meetup
 
Vlog02 [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...
Vlog02  [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...Vlog02  [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...
Vlog02 [eng sub]什麼是controller和如何在asp.net核心中創建controller?-what is controller ...
 
Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1
 
Asp.net mvc網站的從無到有
Asp.net mvc網站的從無到有Asp.net mvc網站的從無到有
Asp.net mvc網站的從無到有
 
ASP.NET Core 2.1設計新思維與新發展
ASP.NET  Core 2.1設計新思維與新發展ASP.NET  Core 2.1設計新思維與新發展
ASP.NET Core 2.1設計新思維與新發展
 
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
使用 TypeScript 駕馭 Web 世界的脫韁野馬:以 Angular 2 開發框架為例
 
Introduction to ASP.NET MVC and MVC 5 Features
Introduction to ASP.NET MVC and MVC 5 FeaturesIntroduction to ASP.NET MVC and MVC 5 Features
Introduction to ASP.NET MVC and MVC 5 Features
 

Plus de Tom Fan

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统
Tom Fan
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workers
Tom Fan
 
Web sockets
Web socketsWeb sockets
Web sockets
Tom Fan
 
Semantics
SemanticsSemantics
Semantics
Tom Fan
 
Multimedia
MultimediaMultimedia
Multimedia
Tom Fan
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5
Tom Fan
 
Html5 history
Html5 historyHtml5 history
Html5 history
Tom Fan
 
Geolocation
GeolocationGeolocation
Geolocation
Tom Fan
 
File api
File apiFile api
File api
Tom Fan
 
Deviceaccess
DeviceaccessDeviceaccess
Deviceaccess
Tom Fan
 
Webstorage
WebstorageWebstorage
Webstorage
Tom Fan
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
Tom Fan
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
Tom Fan
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
Tom Fan
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
Tom Fan
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
Tom Fan
 

Plus de Tom Fan (20)

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workers
 
Web sockets
Web socketsWeb sockets
Web sockets
 
Storage
StorageStorage
Storage
 
Semantics
SemanticsSemantics
Semantics
 
Multimedia
MultimediaMultimedia
Multimedia
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5
 
Html5 history
Html5 historyHtml5 history
Html5 history
 
Geolocation
GeolocationGeolocation
Geolocation
 
File api
File apiFile api
File api
 
Deviceaccess
DeviceaccessDeviceaccess
Deviceaccess
 
Css3
Css3Css3
Css3
 
Webstorage
WebstorageWebstorage
Webstorage
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaults
 
16 CoreData
16 CoreData16 CoreData
16 CoreData
 

07 View Controllers

  • 2. Quiz 和 QuizViewController 回顾 • 在第⼀一个 session 的 Quiz 应⽤用程序中,我们把所 有的代码都写在 QuizViewController 类中。这个类 的⼀一个实例是 Quiz 应⽤用的控制器 • 它有指向屏幕上的 labels 的指针,指向按钮的指 针,这些按钮被按下时会给它发送消息。还有指 向组成应⽤用数据的模型对象的指针 • 对于界⾯面只有⼀一屏的 Quiz 应⽤用,只有⼀一个 controller 对象就⾜足够了 • ⼤大多数应⽤用具有不⽌止⼀一个屏幕的界⾯面,⽽而且有⾮非 常多的对象需要进⾏行管理,设计 iOS 应⽤用时最好 针对每⼀一屏都有⼀一个 controller
  • 4. • ⼀一个 UIViewController 实例专注在控制应⽤用程序内的 ⼀一个单独的屏幕 • 每个 UIViewController 具有⼀一个指向 UIView 或者 UIView ⼦子类的实例的 view 属性,这个 view 就是它 的 screen • 通常情况下,这个 view 是⼀一个全屏视图。⽽而且更多 的时候,这个 view 也具有 subviews。这样⼀一个 view controller 控制的就是⼀一个 view hierarchy • 因为这个 view 属性是整个 hierarchy 的 root,当 view controller 的 view 被作为⼀一个subview 添加到 window 时,整个 view hierarchy 就被添加了
  • 5. 创建 HypoTime 从 Empty Application 模版创建⼀一个新 的 iOS 项⺫⽬目,命名为 HypnoTime。 按右边的图进⾏行配置:
  • 6. 两个 UIViewController 的⼦子类 • 这个应⽤用我们将创建两个 UIViewController 的⼦子类 • HypnosisViewController • TimeViewController • 每个 view controller 将会控制⼀一个 view,⽤用户将 能够在这些 views 之间切换,取决于他们是否想 被催眠或者只是想知道现在是⼏几点 • view 之间的切换将会由另外⼀一个类 UITabBarController 来处理
  • 7. ⼦子类化 UIViewController • 我们不会直接创建 UIViewController 的实 例,⽽而是创建 UIViewController 的⼦子 类来实例化 • ⽣生成 HypnosisViewControlle r(File -> New File -> iOS -> Cocoa Touch -> Objective-C class)
  • 8. HypnosisViewController • view controller 负责创建它的 view hierarchy • HypnosisViewController 的 view hierarchy 将仅由 ⼀一个 view 组成。就是我们前⾯面创建的 UIView 的 ⼦子类 HypnosisView • 在 Finder ⾥里定位到 HypnosisView.h 和 HypnosisView.m ⽂文件,把他们拖到 HypnoTime 项 ⺫⽬目导航器中 • 在出现的窗⼝口中,勾选“Copy items into destination group’s folder” • 同时勾选把它们添加到 target - “HypnoTime”
  • 10. loadView • UIViewController ⼦子类通过重写 loadView ⽅方法来创 建它的 view hierarchy • 这个⽅方法创建⼀一个 view 的实例,并且把它设成 view controller 的 view • 在 HypnosisViewController.m 中重写 loadView,确 保在⽂文件顶部导⼊入了 HypnosisView 的头⽂文件 // 重写 loadView - (void)loadView { // 创建⼀一个 view CGRect frame = [[UIScreen mainScreen] bounds]; HypnosisView *view = [[HypnosisView alloc] initWithFrame:frame]; // 把它设成 view controller 的 view [self setView:view]; }
  • 11. 重写 loadView (代码) #import "HypnoViewController.h" // 导⼊入 HypnosisView 头⽂文件 #import "HypnosisView.h" @interface HypnoViewController () @end @implementation HypnoViewController // 重写 loadView - (void)loadView { // 创建⼀一个 view CGRect frame = [[UIScreen mainScreen] bounds]; HypnosisView *view = [[HypnosisView alloc] initWithFrame:frame]; // 把它设成 view controller 的 view [self setView:view]; }
  • 12. rootViewController • 在 HypnoAppDelegate.m 中,创建⼀一个 HypnosisViewController 的实例并且把它设成 UIWindow 的 rootViewController • 确保在⽂文件顶部导⼊入了 HypnosisViewController.h 头⽂文件
  • 13. 设置 rootViewController(代码) #import "HypnoAppDelegate.h" // 导⼊入 HypnosisViewController 的头⽂文件 #import "HypnoViewController.h" @implementation HypnoAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. // 实例化⼀一个 HypnosisViewController 的实例,并设置成 window 的 rootViewController HypnoViewController *hvc = [[HypnoViewController alloc] init]; [[self window] setRootViewController:hvc]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
  • 15. 应⽤用的对象⽰示意图 • 把⼀一个 view controller 设成 window 的 rootViewController 就把 这个 view controller 的 view 添加成了 window 的 subview • 同时把 view 的⼤大⼩小⾃自 动调整成和 window 同 样⼤大⼩小
  • 16. setRootViewController • 如果我们⾃自⼰己写 UIWindow 的 setRootViewController, ⼤大概是下⾯面这样: // UIWindow setRootViewController - (void)setRootViewController:(UIViewController *)vc { UIView *rootView = [vc view]; CGRect viewFrame = [self bounds]; [rootView setFrame:viewFrame]; [self addSubview:rootView]; }
  • 17. 第⼆二个 UIViewController • HypnoTime 的第⼆二个 UIViewController 的⼦子类 • TimeViewController • TimeViewController 的 screen 将会显⽰示⼀一个 UILabel,和⼀一个把 label 更新到当前时间的 UIButton • 这个 UIView 将会具有两个 subview:按钮和标签 #import <UIKit/UIKit.h> @interface TimeViewController : UIViewController @end
  • 19. 编程或者从 XIB 创建 view • 创建 HypnosisViewController 的 view hierarchy 时, 我们通过编程来实现的:在 loadView 中,实例化了 ⼀一个 HypnosisView, 然后把它设置成 view (view controller 的 view 属性)。这个 view 没有任何的 subview • TimeViewController 的 view 将会有两个 subview。当 ⼀一个 view controller 的 view 具有 subview 的时候, 最好在 XIB ⽂文件中创建和加载它的 view hierarchy, ⽽而不是重写 loadView • 程序运⾏行起来以后,通过编程或者从 XIB ⽂文件创建的 view 没有什么区别,只是 XIB ⽂文件更易于放置多个 view objects
  • 20. TimeViewController.xib • File -> New -> File, iOS, User Interface, Empty 模版 • 弹出窗⼝口的 Device Family 选“iPhone” • 命名为 TimeViewController • 命名假设的重要性 • 在 project navigator 中选中这个⽂文件以使其在 editor area 显⽰示
  • 21. File’s Owner • XIB ⽂文件是如何⼯工作的的? • XIB ⽂文件包含的是对象:把对象拖到画布上就把对 象保存到 XIB ⽂文件了。当 XIB ⽂文件被加载的时候, 这些对象被加载回进内存中
  • 22. TimeViewController 的 XIB ⽂文件 •在 TimeViewController.xib 中,拖拽⼀一个 UIView 到画布上,然后拖拽 ⼀一个 UIButton 和⼀一个 UILabel 到 view 上 • 给按钮⼀一个标题: What time is it? • 给标签⽂文本: ???, 然后 让它居中
  • 23. TimeViewController.xib 中的 Objects • 我们可以看到这些对 象都显⽰示在 Objects 段 下⾯面 • 显⽰示在 Objects 段下⾯面 的对象都是被保存到 XIB ⽂文件中的对象 • 这种类型的存储有⼀一 个专有名词叫做: archiving,这些对象 也就被称作 archived objects
  • 24. placeholder 对象 • XIB ⽂文件中还有另外⼀一 种类型的对象: placeholder objects • Placeholder 区段下⾯面 有两个对象: • File’s Owner(重要!) • First Responder
  • 25. File’s Owner • 为什么需要 File’s Owner? • 当 view controller 加载它的 view 的时候,它会设置 view 属性,这样它就知道它的 view 是什么然后可以把 它放到屏幕上 • 对于 HypnosisViewController,我们通过编程来做这 些,这些操作在 loadView 中实现并且所有的设置都是 在编译期间完成的 • TimeViewController 并⾮非如此,当⼀一个 TimeViewController 的实例需要加载 view 时,它将会加 载 XIB ⽂文件,这时 XIB ⽂文件中的所有的 archived objects 都会被创建,然⽽而 TimeViewController 并不知道这些对 象中的哪⼀一个是它的 view
  • 26. 作为 hole 的 File’s Owner • File’s Owner 是 XIB ⽂文件的⼀一个 hole • 在配置界⾯面的时候,我们在 XIB ⽂文件中的对象和 File’s Owner 之间建⽴立连接 • 当 XIB ⽂文件被加载时,TimeViewController 把它⾃自 ⼰己置⼊入 File’s Owner 的 hole,然后所有的在 File’s Owner 和 archived objects 之间的连接都成了到 TimeViewController 的 • 为了能够设置 TimeViewController 需要的连接,我 们必须告诉 Xcode TimeViewController 是要把⾃自⼰己 放到这个 hole ⾥里的对象的类
  • 27. Identity inspector for File’s Owner • 在 outline view 选中 File’s Owner 对象,然 后点击 inspector 区域 的“Identity inspector”,把 Custom Class 下⾯面的 class 改成 “TimeViewController”
  • 28. view outlet • Control-click File’s Owner 弹出可⽤用连接的⾯面板,我 们指定的类是⼀一个 UIViewController 的⼦子 类,我们就被提供了⼀一个 view outlet • 把 view outlet 连到 XIB ⽂文 件的 UIView 对象 • 现在当 TimeViewController 加载 XIB ⽂文件时,就具有了适 当的连接,并且可以加载 它的 view 了
  • 30. outlet • 这样在 XIB ⽂文件中的⼀一个 outlet 连接就等同于给 outlet 对应的对象发送⼀一个基于 outlet 名称的 setter 消息 • 例如,当 XIB ⽂文件被加载时,把⼀一个对象设置成 view outlet,将会发送⼀一个 setView: 消息给那个 对象,这个⽅方法的参数是连接那头的对象
  • 31. TimeViewController 作为 rootViewController • 打开 HypnoAppDelegate.m, 创建⼀一个 TimeViewController 的实例,把它设成 window 的 rootViewController。确保导⼊入 TimeViewController.h 头⽂文件 • 构建并运⾏行,我们可以看到在 TimeViewController.xib 中创建的 TimeViewController 的 view
  • 32. TimeViewController 作为 rootViewController #import "HypnoAppDelegate.h" // 导⼊入 TimeViewController 头⽂文件 #import "TimeViewController.h" @implementation HypnoAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]]; TimeViewController *tvc = [[TimeViewController alloc] init]; [[self window] setRootViewController:tvc]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
  • 33. outlet 和 method • TimeViewController 在控制这个 view hierarchy, TimeViewController 负责在按钮被按下时更新 label,所以TimeViewController 要作为 UIButton 的 target,并且要具有⼀一个指向 UILabel 的指针 • 在 TimeViewController.h 中声明⼀一个 outlet 和⼀一个 ⽅方法 #import <UIKit/UIKit.h> @interface TimeViewController : UIViewController { IBOutlet UILabel *timeLabel; } - (IBAction)showCurrentTime:(id)sender; @end
  • 34. 建⽴立连接 • 打开 TimeViewController.xib,把 File’s Owner 的 timeLabel outlet 连接到 UILabel • 然后连接 UIButton 到 File’s Owner,并且选择 showCurrentTime: • 这些连接如下⼀一⻚页的图所⽰示:
  • 36. 在 TimeViewController.m 中实现 action ⽅方法 - (IBAction)showCurrentTime:(id)sender { NSDate *now = [NSDate date]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setTimeStyle:NSDateFormatterMediumStyle]; [timeLabel setText:[formatter stringFromDate:now]]; }
  • 38. 回顾:view controller 和 加载 view • view controller 控制⼀一个 screen,这个 screen 就是⼀一个 view • 这个 view controller 的 view,⼜又将具有 subviews,构 成了⼀一个 view hierarchy • ⼀一个 view controller 可以通过两种途径得到它的 view: • 其 view 没有任何 subviews 的 view controller 可以通过重写 loadView: 来创建⼀一个 view 实例,然后给⾃自⼰己发送 setView: • 具有⾮非常复杂 view hierarchy 的 view controller 可以通过把 它的 view outlet 连接到 XIB 中 archived 的顶层 view 的⽅方式 从⼀一个 XIB ⽂文件中加载它的 view
  • 40. • view controller 可以根据⽤用户的动作呈现另⼀一个 view controller 出来 • UITabBarController 就是这类 view controllers 之 ⼀一,我们将使⽤用⼀一个 UITabBarController 在 HypnosisViewController 和 TimeViewController 的 实例之间切换 • UITabBarController 持有⼀一个 UIViewController 的数 组,它也在屏幕底部维护了⼀一个标签栏(tab bar), 其数组中的每⼀一个 view controller 都有⼀一个标签 • 点击标签的结果就是和这个标签关联的 view controller 的 view 被呈现出来
  • 41. 创建⼀一个 UITabBarController • 在 HypnoAppDelegate.m 中,创建⼀一个 UITabBarController 的实例,把两个 view controller 都给它,然后把它设置成 window 的 rootViewController
  • 42. 创建 UITabBarController(代码) - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. HypnoViewController *hvc = [[HypnoViewController alloc] init]; TimeViewController *tvc = [[TimeViewController alloc] init]; UITabBarController *tabBarController = [[UITabBarController alloc] init]; NSArray *viewControllers = [NSArray arrayWithObjects:hvc, tvc, nil]; [tabBarController setViewControllers:viewControllers]; [[self window] setRootViewController:tabBarController]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
  • 44. UITabBarController 的 view • UITabBarController 是 window 的 rootViewController。这意味着 UITabBarController 本⾝身是 UIViewController的⼀一个⼦子类 • 那么它就具有 view 属性,它的 view 是⼀一个具有 两个 subviews 的空⽩白 UIView • tab bar 标签栏 • 选中的 view controller 的 view
  • 47. • 标签栏上的每个标签可以显⽰示⼀一个 title 和⼀一个 image • 为了这个⺫⽬目的,每个 view controller 维护⼀一个 tabBarItem 属性 • 当⼀一个 view controller 被⼀一个 UITabBarController 包含时,它的标签栏条⺫⽬目出现在标签栏上
  • 49. 为两个 view controller 设置标题 • 打开 HypnosisViewController.m, 重写 UIViewController 的 designated initializer, initWithNibName:bundle:, 为 HypnosisViewController 获得然后设置⼀一个 tab bar item • 然后打开 TimeViewController.m, 进⾏行同样的操作
  • 50. 设置 UITabBarItem 的代码 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization // 获取 tab bar item UITabBarItem *tbi = [self tabBarItem]; // 给它⼀一个标题 [tbi setTitle:@"Hypnosis"]; } return self; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization UITabBarItem *tbi = [self tabBarItem]; [tbi setTitle:@"Time"]; } return self; }
  • 52. 为每⼀一个标签增加图⽚片 • 把图⽚片⽂文件Hypno.png, Time.png, Hypno@2x.png, Time@2x.png 拖到项⺫⽬目中,勾选拷⻉贝到项⺫⽬目⺫⽬目录 选项 • 然后再编辑 initWithNibName:bundle: 的代码
  • 53. 设置标签栏图⽚片的代码 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { UITabBarItem *tbi = [self tabBarItem]; [tbi setTitle:@"Hypnosis"]; UIImage *image = [UIImage imageNamed:@"Hypno.png"]; [tbi setImage:image]; } return self; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization UITabBarItem *tbi = [self tabBarItem]; [tbi setTitle:@"Time"]; UIImage *image = [UIImage imageNamed:@"Time.png"]; [tbi setImage:image]; } return self; }
  • 56. • ⼀一个 view controller 当被 allocated, 然后发送⼀一 个 initializer 消息后就开始了它的⽣生命周期 • ⼀一个 view controller 将会看到它的 view 被创建, 移动到屏幕上,移离屏幕,销毁,以及再次创建 - 这个过程也许会有很多次以上 • 这些事件构成了 view controller 的⽣生命周期
  • 57. 初始化 view controller • UIViewController 的 designated initializer 是 initWithNibName:bundle: 。这个⽅方法使⽤用两个参 数指定了在 bundle ⾥里⾯面的 view controller 的 XIB ⽂文件的名称 • 两个参数都传⼊入 nil 表⽰示:当加载你的 view 的时 候,在 application bundle 中搜索名称和你的类名 相同的 XIB ⽂文件 • 例如: TimeViewController 将会加载 TimeViewController.xib ⽂文件 • 可以把下⾯面的代码添加到 TimeViewController.m 会 理解的更清楚⼀一点:
  • 58. nibNameOrNil - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { // self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; // 获得到 application bundle 对象的指针 NSBundle *appBundle = [NSBundle mainBundle]; self = [super initWithNibName:@"TimeViewController" bundle:appBundle]; if (self) { // Custom initialization UITabBarItem *tbi = [self tabBarItem]; [tbi setTitle:@"Time"]; UIImage *image = [UIImage imageNamed:@"Time.png"]; [tbi setImage:image]; } return self; }
  • 59. init • 实际开发过程中,我们⼀一般都使⽤用 UIViewController ⼦子类的名称作为 XIB ⽂文件的名称 • 因此,当创建⼀一个 view controller 时,我们只需要 发送 init ,等同于给 initWithNibName:bundle: 的 两个参数都传⼊入了 nil • 需要的话可以通过重写 initWithNibName:bundle: 来执⾏行⼀一些额外的初始化⼯工作 TimeViewController *tvc = [[TimeViewController alloc] init]; // 等同于 TimeViewController *tvc = [[TimeViewController alloc] initWithNibName:nil bundle:nil];
  • 60. UIViewController 和延迟加载 • 当⼀一个 view controller 被实例化的时候,它不会⽴立 即创建和加载它的 view • 只有当 view 将要在屏幕上显⽰示的时候,⼀一个 view controller 才会特意创建它的 view • 通过在只有需要的时候才加载视图,应⽤用程序不 会在它不需要的时候占⽤用内存
  • 61. viewDidLoad • 所有的 UIViewController 都实现了⼀一个 viewDidLoad ⽅方法,当它加载了它的 view 以后就 会⽴立即执⾏行 • 我们在我们的两个 view controller ⾥里⾯面重写这个⽅方 法在控制台记录⼀一些消息 • 构建并运⾏行,可以看到控制台报告 HypnosisViewController ⽴立即加载了它的 view;点 击 TimeViewController 的标签以后,控制台才报告 view 现在被加载 - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"TimeViewController 加载了它的 view"); }
  • 62. Appear 和 Disappear • 除了加载和卸载以外,view controller 也会在特定 的时间出现或者消失 • view controller 只会被创建⼀一次,但是它的 view 通常会显⽰示(释放或者隐藏)多次 • 我们可以在这些⽣生命周期事件发⽣生时做⼀一些⾃自定 义的操作,⽐比如我们可以让 TimeViewController 的 view 每次显⽰示时,可以显⽰示最新时间 - (void)viewWillAppear:(BOOL)animated; // Called when the view is about to made visible. Default does nothing - (void)viewDidAppear:(BOOL)animated; // Called when the view has been fully transitioned onto the screen. Default does nothing - (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing - (void)viewDidDisappear:(BOOL)animated; // Called after the view was dismissed, covered or otherwise hidden. Default does nothing
  • 63. 重写 viewWillAppear - (void)viewWillAppear:(BOOL)animated { NSLog(@"CurrentTimeViewController will appear"); [super viewWillAppear:animated]; [self showCurrentTime:nil]; } - (void)viewWillDisappear:(BOOL)animated { NSLog(@"CurrentTimeViewController will disappear"); [super viewWillDisappear:animated]; }
  • 64. view controller 中 view 的⽣生命周期
  • 65. 视图控制器的⼦子类和模版 • 实际开发过程中,我们可以在使⽤用 UIViewController 模版的同时勾选上⽣生成相应的 XIB ⽂文件 • 这样 XIB ⽂文件的 File’s Owner 已经被设置成 UIViewController 的类,同时 UIView 的实例已经被 挂接到 File’s Owner 的 view outlet
  • 66. 练习1:再加⼀一个 Tab • 创建⼀一个新的 UIViewController ⼦子类,它的 view 应该是 MKMapView 的⼀一个实例 • 使这个 view controller 成为 UITabBarController 的 第三个 view controller
  • 67. 练习2:Controller Logic • 增加⼀一个 UISegmentedControl 到 HyposisViewController 的 view,带着三个 segments:Red, Green 和 Blue • 当⽤用户 tap segmented control 时,改变 HyposisView 中 circles 的颜⾊色
  • 68. 补充阅读: main 函数和 UIApplication • C 应⽤用程序都是从执⾏行⼀一个 main 函数开始, Objective-C 应⽤用程序也是⼀一样的 int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([HypnoAppDelegate class])); } } • UIApplicationMain 函数⽣生成了⼀一个 UIApplication 类 的实例;对于每⼀一个应⽤用来说,都有⼀一个单独的 UIApplication 实例,⽤用于维护 run loop • UIApplicationMain 还⽣生成⼀一个 作为 UIApplication 的 delegate 类的实例。最后⼀一个参数就是 delegate 类的名称
  • 69. application:didFinishLaunchingWithOptions: • 在 run loop 将要开始接收事件前,application 会 发送给它的 delegate ⼀一个消息,告诉它“ 准备好 了,我们要开始啦!”,这个消息就是 application:didFinishLaunchingWithOptions: • 我们在 HypnosisAppDelegate.m 中实现这个⽅方法 来创建在应⽤用程序中使⽤用的 window 和 controller 对象 • 所有的 iOS 应⽤用程序都遵循这种模式
  • 70. 补充阅读2: 视⺴⽹网膜屏(Retina) 显⽰示 • Retina 和早期设备的 320x480 像素⽐比较⽽而⾔言具有更⾼高的 分辨率(640x960) • 如何才能让图形在两种显⽰示上都能得到最佳显⽰示? • 对于⽮矢量图形,像 HypnosisView 的 drawRect: ⽅方法和绘 制⽂文本,同样的代码会渲染的和设备允许的⼀一样清晰 • 如果使⽤用 Core Graphics 函数绘制,这些图形在不同的 设备上会有不同的表现 • 在 Core Graphics 中,也被称作 Quarz,我们使⽤用名词 points 描述 lines(直线),curves(曲线),text(⽂文 本) 等。在⾮非 Retina 显⽰示上,⼀一个 point 是1x1像素; 在 Retina 显⽰示上,⼀一个 point 是 2x2 像素
  • 72. 图像拉伸 • 位图(像 JPEG 或 PNG ⽂文件),如果图像没有适 合设备的屏幕类型,将会变得没有吸引⼒力 • 假如你的应⽤用包含了⼀一个 25x25 像素的⼩小图像, 如果它在 Retina 显⽰示器上显⽰示时,图像必须被拉 伸来覆盖 50x50 的区域。 • 从这⼀一点上来讲,系统使⽤用了⼀一种叫做抗锯齿的 平均化的⽅方式来使图像看起来没有锯齿。结果是 图像没有锯齿 - 但是模糊
  • 74. @2x • 可以使⽤用⼤大⽂文件替代,但是图像缩⼩小时⼜又会在另⼀一 个⽅方向引起问题 • 唯⼀一的⽅方案是在应⽤用程序绑定两个⽂文件:⼀一种是在 ⾮非 Retina 屏幕上采⽤用分辨率等于 points 数量,另 外⼀一种是在 Retina 显⽰示上采⽤用 points 两倍的分辨 率 • 幸运的是我们不需要写额外的代码来处理在哪种设 备上加载哪种图⽚片,我们需要做的就是在⾼高分辨率 图⽚片后⾯面加上 @2x 后缀。然后当使⽤用 UIImage 的 imageNamed: ⽅方法来加载图⽚片时,这个⽅方法会在 bundle 中查找,然后拿到适合特定设备的⽂文件