Más contenido relacionado
La actualidad más candente (9)
Similar a 11 UINavigationController (20)
11 UINavigationController
- 2. • 前⾯面我们看到了 UITabBarController 能够允许⽤用户访问不
同的 screen,标签栏控件适⽤用于⼏几个 screen 之间互不依
赖的情况
• 如果我们想让⽤用户在⼀一些相关联的 screen 之间移动,我
们就可以使⽤用 UINavigationController
• ⽐比如 iOS 的 “设置”应⽤用有多个相关的 screen:⼀一系列的
设置和针对每个设置的详细⻚页⾯面,对于每个详细项还有
⼀一个选项⻚页⾯面,这种类型的界⾯面称作:drill-down
interface
• 这⼀一个主题我们就使⽤用 UINavigationController 给
Homepwner 添加⼀一个 drill-down interface, 让⽤用户能够查
看和编辑⼀一个 BNRItem 的详细信息
- 6. • 在初始化⼀一个 UINavigationController 的实例时,
我们给它⼀一个 UIViewController。这个
UIViewController 是 navigation controller 的 root
view controller。
• 这个 root view controller 总是在堆栈的底部。应⽤用
程序运⾏行时,可以往堆栈中压⼊入更多的 view
controller.
- 7. • UITabBarController 是在初始化的时候就拿到了它
所有的 view controller,⽽而对于 navigation
viewcontroller ,只有它的 root view controller 是保
证⼀一直在堆栈中的
• 当 UIViewController 被压⼊入 stack 时,它的 view 是
从右边滑⼊入屏幕;当堆栈弹出的时候,顶层的
view controller 被移出堆栈,它的 view 是从左边
滑⼊入屏幕的
- 8. 堆栈
• 这是⼀一个带有两个 view controller 的 navigation
controller:⼀一个 root view controller,⼀一个是在它之上
的其他 view controller,可⻅见的是上⾯面的 view controller
- 9. viewControllers 和 topViewController
• 和 UITabBarController 类似,
UINavigationController 有⼀一个 viewControllers 数
组。它的 root view controller 是数组中的第⼀一个对
象。当更多的 view controller 被压⼊入堆栈时,他们
被添加到了这个数组的结尾。这样数组中的最后
⼀一个 view controller 就在堆栈的顶部
• UINavigationController 的 topViewController 属性
保持了⼀一个到堆栈顶部的指针
- 10. UINavigationController 的 view
• UINavigationController 是 UIViewController 的⼦子
类,所以它有⼀一个⾃自⼰己的 view 。它的 view 总是拥
有两个 subview:
• ⼀一个是 UINavigationBar
• 以及 topViewController 的 view
• 也可以把 navigation controller 设成 window 的
rootViewController 来把它的 view 作为 window 的
subview
- 12. 升级 Homepwner 应⽤用
• 这⼀一节我们增加⼀一个 UINavigationController 到
Homepwner 应⽤用并把 ItemsViewController 作为
UINavigationController 的 rootViewController
• 然后我们创建另外⼀一个可以压⼊入到
UINavigationController 堆栈的 UIViewController 的
⼦子类
- 13. 升级 Homepwner 应⽤用
• 当⽤用户选择其中⼀一⾏行时,新的 UIViewController 的
view 将会滑⼊入屏幕。这个 view controller 将会允
许⽤用户查看和编辑选中的 BNRItem 的属性
• 下⼀一⻚页是更新后的 Homepwner 应⽤用的对象⽰示意图
- 15. 增加 UINavigationController
• 修改 HomepwnerAppDelegate.m ⽣生成⼀一个
UINavigationController,给它⼀一个 root view
controller,并把它设成 window 的 root view
controller
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ItemsViewController *ivc = [[ItemsViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:ivc];
// [[self window] setRootViewController:ivc];
[[self window] setRootViewController:navController];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
- 18. • 添加完了 UINavigationController 之后,我们来添
加⽤用来压⼊入 navigation controller 堆栈的另外⼀一个
UIViewController
• 创建⼀一个新的 UIViewController ⼦子类,命名为:
DetailViewController, 勾选上“With XIB for user
interface”
- 19. • 在 Homepwner 中,我们希望⽤用户能够轻击⼀一个
item,然后跳到另外⼀一个屏幕.
• 在这个新的屏幕上有能够编辑那个 BNRItem 每⼀一
个属性的⽂文本字段。
• 这个 view 将会被 DetailViewController 的实例控制
- 20. 更简便建⽴立连接的⽅方式
• detail view 需要 4 个 subview - 每个针对⼀一个
BNRItem 实例的实例变量
• 因为我们需要在运⾏行时访问这些 subview,
DetailViewController 需要这些 subviews 的
outlets。这样我们需要添加四个新的 outlets 到
DetailViewController
• 可以使⽤用⼀一种稍微简化的⽅方式合并”声明 outlets,
再建⽴立连接的⽅方式”
- 24. 设置完的 DetailViewController.h
#import <UIKit/UIKit.h>
@interface DetailViewController : UIViewController
{
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *serialNumberField;
__weak IBOutlet UILabel *dateLabel;
__weak IBOutlet UITextField *valueField;
}
• 注意 XIB ⽂文件不要有坏连接,否则在 XIB ⽂文件被加
载时,应⽤用会崩溃。
• 另外针对 XIB ⽂文件中的每个 UITextField,连接
delegate 属性到 File’s Owner(从 UITextField
Control-drag 到 File’s Owner, 然后从列表中选择
delegate)
- 26. • 现在我们已经有了 ⼀一个 navigation controller,以
及两个 view controller 的⼦子类
( ItemViewController 和 DetailViewController),
下⾯面要把它们连起来⼯工作
• 我们要让⽤用户点击 ItemViewController 的 table
view 的⼀一⾏行,然后 DetailViewController 的 view 就
可以滑动到屏幕,并且显⽰示选中的 BNRItem 实例
的属性
- 28. • 例如在tab bar controller ⼀一节,我们创建了两个
view controller,然后⻢马上把它们加到了 tab bar
controller 的 viewControllers 数组
• 在使⽤用 navigation controller 时,我们不能简单的
把所有可能⽤用到的 view controller 都存到 stack 中
- 29. • navigation controller 的数组是动态的,我们开始
于⼀一个 root view controller,然后根据⽤用户的需要
添加 view controller。
• 这样navigation controller 之外的⼀一些对象就需要
创建 DetailViewController 的实例,并且负责把它
加⼊入到堆栈中
- 30. • 这个对象必须满⾜足两个要求:
• 它需要知道什么时候把 DetailViewController 压⼊入堆栈
• 它需要⼀一个指向 navigation controller 的指针来给
navigation controller 发送⼀一个名为:
pushViewController:animated: 的消息
- 31. • ItemsViewController 这两个条件都满⾜足:
• 第⼀一,它知道table view 中的⾏行什么时候被轻击,作
为 table view 的 delegate,当这个事件发⽣生时,它会
收到 tableView:didSelectRowAtIndexPath: 消息
• 第⼆二,在 navigation controller 堆栈中的 view
controller 能够通过给它⾃自⼰己发送
navigationController 消息得到⼀一个指向这个
navigation controller 的指针
• 作为 root view controller,ItemsViewController 总是
位于 navigation controller 堆栈中,因此也总是可以
拿到这个指针
- 33. tableView:didSelectRowAtIndexPath:
• 这样就由 ItemsViewController 负责⽣生成
DetailViewController 的实例,并把它加⼊入到堆栈
• ⾸首先在 ItemsViewController.h 中导⼊入
DetailViewController 的头⽂文件
• 当⼀一⾏行被轻击时,它的 delegate 被发送
tableView:didSelectRowAtIndexPath: 消息,这个消
息包含选中⾏行的 index path
• 我们在 ItemsViewController.m 中实现这个⽅方法来
⽣生成 DetailViewController 然后把它压⼊入
navigation controller 的堆栈
- 34. ItemsViewController 中的
didSelectRowAtIndexPath:
#import "ItemsViewController.h"
#import "BNRItemStore.h"
#import "BNRItem.h"
#import "DetailViewController.h"
@implementation ItemsViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath
{
DetailViewController *detailViewController = [[DetailViewController
alloc] init];
// 压⼊入 navigation controller stack 的顶部
[[self navigationController] pushViewController:detailViewController
animated:YES];
}
- 35. 栈内 view controller 的⽣生命周期
• 因为 UINavigationController 的堆栈是⼀一个数组,
它将会拥有所有添加到它⾥里⾯面的 view controller 的
所有权。
• 这样在 tableView:didSelectRowAtIndexPath: 结束
之后(⽅方法对它的持有消失),
DetailViewController 只会被 UINavigationController
所拥有。
• 当堆栈弹出时,DetailViewController 被销毁;当
下⼀一次⼀一⾏行被轻击时,⼀一个新的
DetailViewController 的实例被⽣生成
- 36. push view controller 的⼀一般模式
• 让⼀一个 view controller 压⼊入新的 view controller 是
⼀一种常⻅见的模式。
• ⼀一般是由 root view controller 创建下⼀一个 view
controller,然后这个 view controller 在这之后再创
建下⼀一个 view controller...
- 37. • 我么也可以让 view controller 能够压⼊入不同类型的
view controller。
• ⽐比如 Photos 应⽤用,根据选中的媒体类型的不同,
可以往 navigation controller 的堆栈中分别压⼊入
video view controller 或者 image view controller
- 39. • ⺫⽬目前屏幕上的 UITextField 都是空的。要填充这些
字段,我们需要⼀一种⽅方法来把选中的 BNRItem 从
ItemsViewController 中传到 DetailViewController
• 要成功实现此功能,我们需要给
DetailViewController ⼀一个属性来持有 BNRItem。
当⼀一⾏行被轻击时,ItemsViewController 将提供相
应的的 BNRItem 给被压⼊入堆栈的
DetailViewController 的实例。
- 40. • 这个 DetailViewController 将把这个 BNRItem 的属
性填充到它的⽂文本字段。
• 在 DetailViewController 的 view 上编辑 UITextFields
中的⽂文本将改变 BNRItem 的属性
- 41. BNRItem *item;
• 在 DetailViewController.h 中我们增加⼀一个这个属
性
• 同时在⽂文件顶部,前置声明 BNRItem
#import <UIKit/UIKit.h>
@class BNRItem;
@interface DetailViewController : UIViewController
{
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *serialNumberField;
__weak IBOutlet UILabel *dateLabel;
__weak IBOutlet UITextField *valueField;
}
@property (nonatomic, strong) BNRItem *item;
@end
- 42. • 在 DetailViewController.m 中为 item sythesize
accessor,并且导⼊入 BNRItem 头⽂文件
#import "DetailViewController.h"
#import "BNRItem.h"
@interface DetailViewController ()
@end
@implementation DetailViewController
@synthesize item;
- 43. item 内容的显⽰示
• 当 DetailViewController 的 view 显⽰示在屏幕上时,
它需要设置它的 subview 来显⽰示 item 的属性
• 这样我们可以在 DetailViewController.m 中重写
viewWillAppear: 来给各种 UITextFields 传递 item 的
属性
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[nameField setText:[item itemName]];
[serialNumberField setText:[item serialNumber]];
[valueField setText:[NSString stringWithFormat:@"%d", [item valueInDollars]]];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateLabel setText:[dateFormatter stringFromDate:[item dateCreated]]];
}
- 44. 压⼊入堆栈前给 item 赋值
• 下⾯面我们在 ItemViewController.m 中的
tableView:didSelectRowAtIndexPath: 中增加⼀一段代
码,压⼊入堆栈前把选中的 BNRItem 传给
DetailViewController,这样 DetailViewController
就能在 viewWillAppear: 被调⽤用前拿到它的 item
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath
{
DetailViewController *detailViewController = [[DetailViewController
alloc] init];
NSArray *items = [[BNRItemStore defaultStore] allItems];
BNRItem *selectedItem = [items objectAtIndex:[indexPath row]];
[detailViewController setItem:selectedItem];
// 压⼊入 navigation controller stack 的顶部
[[self navigationController] pushViewController:detailViewController
animated:YES];
}
- 45. view controller 间传递数据的⽅方法
• 在 view controller 之间传递数据:把所有的数据都
放到 root view controller,然后传递这些数据的
⼀一个⼦子集给下⼀一个 UIViewController,是执⾏行这
类任务⽐比较简洁⽽而且⾼高效的⽅方法
- 46. • 构建运⾏行,然后选择 UITableView 中的⼀一⾏行,出现
的视图将包含选中的 BNRItem 的信息。但是现在
我们编辑这些数据的时候,UITableView 并不会反
映出这些修改
• 我们需要实现⼀一些代码在 BNRItem 被编辑时来更
新它的属性
• 这些代码放到什么地⽅方呢?
- 47. 视图的出现和消失
• 在 UINavigationController 将要切换 view 时,它会
发出两个消息:
• viewWillDisappear:
• viewWillAppear:
• 将被弹出堆栈的 UIViewController 会被发送
viewWillDisappear: 消息
• 将要被置顶到堆栈的 UIViewController 被发送
viewWillAppear: 消息
• 我们可以在 DetailViewController 被弹出堆栈时,
把它的 item 的属性设置成 UITextFields 的内容
- 48. viewWillDisappear: 及超类
• 当实现这些针对 view 的 appearing 和
disappearing 的⽅方法时,⾮非常重要的⼀一点是要调
⽤用他们超类的实现 - 这些超类⾥里⾯面也有⼀一些⼯工作
要做。我们在 DetailViewController.m 中实现
viewWillDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// clear first responder
[[self view] endEditing:YES];
// 保存变更到 item
[item setItemName:[nameField text]];
[item setSerialNumber:[serialNumberField text]];
[item setValueInDollars:[[valueField text] intValue]];
}
- 49. endEditing:
• 注意 endEditing: 的使⽤用。当 endEditing: 消息被发
送给⼀一个 view,如果它或者它的任何 subview 当
前是 first responder,它将放弃它的 first responder
状态,并且键盘将被释放。
• 传递给它的参数决定了 first responder 是否应该被
强制解除。有些 first responder 可能会拒绝放弃,
传递 YES 将会忽略这个拒绝。
[super viewWillDisappear:animated];
// clear first responder
[[self view] endEditing:YES];
// 保存变更到 item
[item setItemName:[nameField text]];
[item setSerialNumber:[serialNumberField text]];
[item setValueInDollars:[[valueField text] intValue]];
- 50. viewWillAppear: 和 reloadData:
• 现在当⽤用户在 UINavigationBar 上轻击 Back 按钮的
时候,BNRItem 的值将被更新。
• 当 ItemViewController 重新出现在屏幕上时,它将
被发送 viewWillAppear: 消息。抓住这个机会来重
新加载 UITableView 这样⽤用户就可以⽴立即看到这些
变更。在 ItemViewController.m 中重写
viewWillAppear:
• 构建并运⾏行,切换和数据变更都⾮非常流畅了
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self tableView] reloadData];
}
- 52. • UINavigationBar 应该显⽰示当前在
UINavigationController 堆栈顶部的
UIViewController 的标题
• 每个 UIViewController 都有⼀一个 UINavigationItem
类型的 navigationItem 属性。但是不像
UINavigationBar,UINavigationItem 不是 UIView
的⼀一个⼦子类,所以它不能出现在屏幕上。
navigation item 提供了navigation bar 它需要绘制
的内容
- 53. • 当⼀一个 UIViewController 到达
UINavigationController 堆栈的顶部时,
UINavigationBar 使⽤用 UIViewController 的
navigationItem 来配置它⾃自⼰己。
• 如下图所⽰示:
- 55. UINavigationItem 的 title 字符串
• 默认情况下,UINavigationItem 是空的。从最基本
的层⾯面上来讲,⼀一个 UINavigationItem 应该有⼀一
个简单的 title 字符串。
• 当 UIViewController 被移到 navigation 堆栈的顶部
并且它的 navigationItem 的 title 属性有⼀一个有效
的字符串,navigation bar 将显⽰示这个字符串。
• 如下图所⽰示:
- 57. 设置 ItemViewController 的 title
• 在 ItemViewController.m 中修改 init 来设置
navigationItem 的 title 以显⽰示 Homepwner
- (id)init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
UINavigationItem *n = [self navigationItem];
[n setTitle:@"Homepwner"];
}
return self;
}
• 构建并运⾏行我们注意到在 navigation bar 上的
Homepwner 字符串。但是点击⼀一⾏行以后 title 不在
了,我们需要也给 DetailViewController ⼀一个 title。
- 58. 实现 setItem: 并设置 title
• 我们要把 DetailViewController 的 navigation item
的 title 设置成 BNRItem 显⽰示的名称⼀一致。
• 但是我们不能在 DetailViewController 的 init 中设
置,因为这时候还不知道它的 item 会是什么
• 我们要在 DetailViewController 设置它的 item 属性
的时候设置它的 title。在 DetailViewController.m 中
实现 setItem: 以替换 item 的synthesized 默认的
setter ⽅方法
- (void)setItem:(BNRItem *)i
{
item = i;
[[self navigationItem] setTitle:[item itemName]];
}
- 60. UINavigationItem 的三个区域
• ⼀一个 navigation item 可以持有不仅只是⼀一个 title
字符串,如后⾯面的图所⽰示,每个 UINavigationItem
有三个可⾃自定义的区域:
• ⼀一个 leftBarButtonItem
• ⼀一个 rightBarButtonItem
• 和⼀一个 titleView
• 左右导航按钮条⺫⽬目被指向 UIBarButtonItem 的实
例,包含的是针对仅可以在 UINavigationBar 或
UIToolbar 上按显⽰示的⼀一个按钮的信息
- 62. 容器的概念
• 和 UINavigationItem ⼀一样,UIBarButtonItem 也不
是 UIView 的⼀一个⼦子类,也只是提供了⼀一个
UINavigationBar 需要绘制的内容
• 可以认为 UINavigationItem 和 UIBarButtonItem 是
字符串,图⽚片以及其他内容的容器。
• ⼀一个 UINavigationBar 知道如何在这些容器中进⾏行
查找并绘制它找到的内容
- 64. • 如果给⼀一个特定的 view controller 设置⼀一个⾃自定义
的view(像按钮,滑块,图⽚片,甚⾄至⼀一个地图)
适合上下⽂文,我们就可以把 navigation item 的
titleView 设成这个⾃自定义的 view。⼀一般情况下使
⽤用⼀一个 title 字符串就⾜足够了。
• 前⼀一个图是就是⼀一个使⽤用⾃自定义视图作为
titleView 的例⼦子
- 65. UIBarButtonItem 的 target-action
• 我们来给 UINavigationBar 添加⼀一个
UIBarButtonItem。
• 我们希望这个 UIBarButtonItem 当
ItemsViewController 在堆栈顶部时出现在导航栏
的右侧。
• 轻击它的时候,它将往列表中添加⼀一个新的
BNRItem。
- 66. • bar button item 具有⼀一个 target-action 对,作⽤用类
似 UIControl 的 target-action 机制:当被轻击时,
它发送 action 消息到它的 target。
• 当我们在⼀一个 XIB ⽂文件中设置 target-action 对时,
通过从按钮 Control-drag 到它的 target,然后从
IBActions 列表中选择⼀一个⽅方法。
- 67. 编程设置 target 和 action
• 要编程设置 target-action 对,我们把 target 和
action 传递给按钮
• 我们在 ItemsViewController.m 中⽣生成⼀一个
UIBarButtonItem 的实例并且把它的 target 和
action 传递给它
- 68. 修改 ItemsViewController.m 中的init
- (id)init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
UINavigationItem *n = [self navigationItem];
[n setTitle:@"Homepwner"];
// 创建⼀一个会发送 addNewItem: 到ItemsViewController 的导航栏按钮
UIBarButtonItem *bbi = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:@selector(addNewItem:)];
// 设成右边
[[self navigationItem] setRightBarButtonItem:bbi];
}
return self;
}
• 构建并运⾏行,点击+按钮,表格中将出现⼀一个新⾏行
• 令,还有其他的 initialization 消息可以发送给
UIBarButtonItem 的实例
- 69. 加⼀一个左按钮
• 现在我们添加另⼀一个 UIBarButtonItem 来替换掉表
格头部中 Edit 按钮。在 ItemViewController.m 中继
续修改 init ⽅方法
- (id)init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
UINavigationItem *n = [self navigationItem];
[n setTitle:@"Homepwner"];
UIBarButtonItem *bbi = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:@selector(addNewItem:)];
[[self navigationItem] setRightBarButtonItem:bbi];
// 加⼀一个编辑按钮
[[self navigationItem] setLeftBarButtonItem:[self editButtonItem]];
}
return self;
}
- 70. editButtonItem:
[[self navigationItem] setLeftBarButtonItem:[self editButtonItem]];
• 在 navigation bar 中获得⼀一个编辑按钮只需要⼀一⾏行
代码
- 71. • editButtonItem 是从哪⾥里来的呢?
• UIViewController 有⼀一个 editButtonItem 属性,⽽而
且当被发送 editButtonItem 时,view controller 就
⽣生成⼀一个标题为“Edit”的 UIBarButtonItem。
• 更给⼒力的是,这个按钮⾃自带⼀一个 target-action
对:当轻击时,它发送 setEditing:animated: 消息
给它的 UIViewController。
• 构建并运⾏行确认⼀一下 navigation bar 的效果
- 72. 删除 header view 相关代码
• 现在 Homepwner 已经有了⼀一个全功能的导航栏,可以
把项⺫⽬目中有关 header view 及其相关的代码都删除掉了
• ItemsViewController.m 中的
• tableView:viewForHeaderInSection:
• tableView:heightForHeaderInSection:
• headerView:
• toggleEditingMode:
• ItemsViewController.h 中的
• IBOutlet UIView *headerView;
• - (UIView *)headerView;
• - (IBAction)toggleEditingMode:
• 还可以把 HeaderView.xib ⽂文件从 project navigator 中删除