Contenu connexe Similaire à 07 View Controllers (20) 07 View Controllers2. 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 就被添加了
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];
}
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?
• 给标签⽂文本: ???, 然后
让它居中
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
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;
}
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];
}
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 中查找,然后拿到适合特定设备的⽂文件