13 UIPopoverController and Modal View Controller

879 views
649 views

Published on

UIPopoverController 和 模态化视图控制器

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
879
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

13 UIPopoverController and Modal View Controller

  1. 1. UIPopoverController 和 Modal ViewController范圣刚,princetoad@gmail.com, www.tfan.org
  2. 2. • 截⾄至⺫⽬目前我们已经看到了有四种⽅方法可以⽤用来显⽰示⼀一个 view controller 的 view:• 第⼀一种是可以设成 window 的 root view controller, view 就变成了 window 的 subview• 第⼆二种是把 view controller 压⼊入⼀一个 UINavigationController 的堆栈• 第三种是我们可以把它添加到⼀一个 UITabBarController• 以及模态化的把它呈现出来
  3. 3. • 在这个主题我们来看⼀一下 UIPopoverController, 以 及更多⽤用来呈现模态化 view controller 的选项• 有些选项只对 iPad ⽣生效,我们第⼀一步先把 Homepwner 改成 universal appication - 就是可以 本地化的在iPad,以及iPhone,iPod touch上运⾏行
  4. 4. Universalizing Homepwner• Device -> Universal
  5. 5. 改进界⾯面的两种⽅方式• 改成 Universal 之后,ItemsViewController 视图在 iPad 上看起来很好,但是如果选择⼀一⾏行的话,可 以看到 DetailViewController 的界⾯面可能需要⼀一些 加⼯工(⽽而且轻击获取图⽚片的按钮会抛出异常,后 ⾯面⻢马上会纠正这个问题)
  6. 6. • 改进 DetailViewController 界⾯面效果的⼀一种⽅方法是 设置 autosizing mask,让他的 iPad 上⾃自动调整来 适合 iPad• 另外⼀一种是创建两个完全独⽴立的 XIB ⽂文件,⼀一个 针对 iPhone,⼀一个针对 iPad(加上~ipad 后缀) (要在iPad版本上重新创建视图和重新连接)。
  7. 7. 背景颜⾊色• 现在我们先不关⼼心 DetailViewController 视图的显 ⽰示。我们先来处理⼀一下 iPhone 下的纯⽩白背景• 如果是 iPad 的话,我们把背景⾊色设成 groupTableViewBackgroundColor 颜⾊色;如果是 iPhone 的话,给它设定⼀一个和 ItemsViewController接近的颜⾊色
  8. 8. 确定 device family • ⾸首先我们要能够判断设备类型 • 使⽤用 UIDevice 类的类⽅方法 currentDevice 可以得到当前 设备,然后通过检查它的 userInterfaceIdiom 属性判断 是何种设备。⺫⽬目前有两个可能的值: • UIUserInterfaceIdiomPad - iPad • UIUserInterfaceIdiomPhone - iPhone 或 iPod touch
  9. 9. userInterfaceIdiom: • 在 DetailViewController.m 中修改 viewDidLoad: • 我们在后⾯面会经常⽤用到这种判断- (void)viewDidLoad{ [super viewDidLoad];// [[self view] setBackgroundColor:[UIColorgroupTableViewBackgroundColor]]; UIColor *clr = nil; if ([[UIDevice currentDevice] userInterfaceIdiom] ==UIUserInterfaceIdiomPad) { clr = [UIColor groupTableViewBackgroundColor]; } else { clr = [UIColor colorWithRed:0.875 green:0.88 blue:0.91 alpha:1]; } [[self view] setBackgroundColor:clr];}
  10. 10. ⾃自动翻转设置 • 我们要让应⽤用在 iPad 上⽀支持所有⽅方向的翻转,在 iPhone 上只⽀支持 Portrait • 在 ItemsViewController.m 和 DetailViewController.m 中都增加下⾯面的⽅方法- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{ if ([[UIDevice currentDevice] userInterfaceIdiom] ==UIUserInterfaceIdiomPad) { return YES; } else { return (toInterfaceOrientation == UIInterfaceOrientationPortrait); }}构建并运⾏行可以看到在 iPad 上视图可以进⾏行翻转
  11. 11. UIPopoverController
  12. 12. • 现在 Homepwner 已经可以在 iPad 上运⾏行了,我们 可以充分利⽤用 iPad 特有的⽅方法来呈现 view controller• ⾸首先我们来看⼀一下 UIPopoverController• 应⽤用程序经常需要呈现⼀一个 view controller 给⽤用户, 让⽤用户选择下⼀一步做什么。⽐比如 image picker。• 在 iPhone 和 iPod touch 上,这类 view controller 通 常是模态化呈现,占满整个屏幕。• 有更多屏幕空间的 iPad 有另外⼀一种选择: UIPopoverController
  13. 13. • popover controller 在应⽤用程序界⾯面的上⾯面浮动显 ⽰示另⼀一个 view controller 的 view,显⽰示在⼀一个边 框窗⼝口中。• 当我们⽣生成⼀一个 UIPopoverController 时,我们把 这另⼀一个 view controller 设成了 popover controller 的 contentViewController• 我们要实现的是,⽤用户轻击 camera 按钮时,我们 在⼀一个 UIPopoverController ⾥里⾯面把 UIImagePickerController 呈现出来
  14. 14. popover controller
  15. 15. popover controller 声明 • 在 DetailViewController 中增加⼀一个实例变量来持 有 popover controller;同时声明 DetailViewController 符合 UIPopoverControllerDelegate@interface DetailViewController : UIViewController<UINavigationControllerDelegate, UIImagePickerControllerDelegate,UITextFieldDelegate, UIPopoverControllerDelegate>{ __weak IBOutlet UITextField *nameField; __weak IBOutlet UITextField *serialNumberField; __weak IBOutlet UILabel *dateLabel; __weak IBOutlet UITextField *valueField; __weak IBOutlet UIImageView *imageView; UIPopoverController *imagePickerPopover;}
  16. 16. 创建,设置和呈现 popover • 如果是 iPad 的话,获取图⽚片时⽤用 popover controller;在 DetailViewController.m 中,在 takePicture: 末尾增加下列代码 [imagePicker setDelegate:self]; // 把 image picker 放到屏幕上// [self presentViewController:imagePicker animated:YES completion:nil]; if ([[UIDevice currentDevice] userInterfaceIdiom] ==UIUserInterfaceIdiomPad) { // 创建⼀一个新的将要显⽰示 imagePicker 的 popover control imagePickerPopover = [[UIPopoverController alloc]initWithContentViewController:imagePicker]; [imagePickerPopover setDelegate:self]; [imagePickerPopover presentPopoverFromBarButtonItem:senderpermittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } else { [self presentViewController:imagePicker animated:YEScompletion:nil]; }}
  17. 17. 释放 popover• 构建并在 iPad 模拟器中运⾏行,导航到 DetailViewController 然后点击 camera 按钮,pop over 会出现并且显⽰示 image picker。选择⼀一个图 ⽚片,会出现在 DetailViewController 的 view 上。• 我们可以点击屏幕上的任何位置来释放这个 popover controller。当 popover 被以这种⽅方式释 放时,它发送⼀一个 popoverControllerDidDismissPopover: 消息给它 的 delegate
  18. 18. popoverControllerDidDismissPopover:- (void)popoverControllerDidDismissPopover:(UIPopoverController*)popoverController{ NSLog(@"释放 popover"); imagePickerPopover = nil;} • 在 DetailViewController.m 中实现这个⽅方法 • 注意这⾥里我们把 imagePickerPopover 设成 nil 来销 毁它,我们每次点击 camera 按钮时都会⽣生成⼀一个 新的
  19. 19. 选完图⽚片后释放 popover • 在DetailViewController.m 中 imagePickerController:didFinishPickingMediaWithInfo : 末尾增加代码来释放 popover [imageView setImage:image]; // 把 image picker 从屏幕上移⾛走,必须调⽤用这个 dismiss ⽅方法// [self dismissViewControllerAnimated:YES completion:nil]; if ([[UIDevice currentDevice] userInterfaceIdiom] ==UIUserInterfaceIdiomPhone) { [self dismissViewControllerAnimated:YES completion:nil]; } else { [imagePickerPopover dismissPopoverAnimated:YES]; imagePickerPopover = nil; }}• 因为显式发送 dismissPopoverAnimated: 消息释放时,不会 发送 popoverControllerDidDismissPopover: 给它的 delegate,所有这⾥里要设置 imagePickerPopover 为 nil
  20. 20. 销毁不可⻅见的 popover controller• 还有⼀一个⼩小 bug 要处理,UIPopoverController 可 ⻅见的情况下,再次点击 camera 按钮,应⽤用会崩溃• 原因是点击 camera 按钮,在 takePicture: 中 imagePickerPopover 被设成了新的 UIPopoverController,这时屏幕上的 UIPopoverController 就被销毁。• 已经销毁的对象还是可⻅见的,就出现异常了。我 们要确保销毁的是不可⻅见的 UIPopoverController
  21. 21. 已有,先释放 • 在 DetailViewController.m 中,takePicker: 顶部增 加下⾯面的代码,增加对 imagePickerPopover 是否 可⻅见的判断 • 这就实现了点击 camera,如果当前有显⽰示 popover,先释放- (IBAction)takePicture:(id)sender { // 避免多次点击camera按钮引起异常 if ([imagePickerPopover isPopoverVisible]) { [imagePickerPopover dismissPopoverAnimated:YES]; imagePickerPopover = nil; return; } UIImagePickerController *imagePicker = [[UIImagePickerController alloc]init];
  22. 22. 更多关于模态化 View Controller
  23. 23. • 在这个部分,我们更新 Homepwner,让它在⽤用户新建⼀一个 BNRItem 时模态化的呈现DetailViewController• 当⽤用户选择⼀一个已经存在的 BNRItem 时,我们和之前⼀一样把 DetailViewController 压⼊入UINavigationController 的堆栈
  24. 24. 新建⼀一个 BNRItem
  25. 25. 双重⽤用法 • 要实现 DetailViewController 的双重⽤用法(new 或 edit),我们要给它⼀一个名为 initForNewItem: 的新 的 designated initializer • 在这个 initializer 中检查这个实例是被⽤用来⽣生成⼀一 个新的 BNRItem 还是⽤用来显⽰示⼀一个已经存在的, 然后对应的来设置⽤用户界⾯面 • ⾸首先在 DetailViewController.h 中声明这个 initializer- (id)initForNewItem:(BOOL)isNew;@property (nonatomic, strong) BNRItem *item;
  26. 26. Done 和 Cancel• 如果 DetailViewController 被⽤用来⽣生成⼀一个新的 BNRItem,我们希望在它的 navigation item 上显⽰示 ⼀一个 Done 按钮和⼀一个 Cancel 按钮• 在 DetailViewController.m 中实现这个⽅方法
  27. 27. 实现 initForNewItem:- (id)initForNewItem:(BOOL)isNew{ self = [super initWithNibName:@"DetailViewController" bundle:nil]; if (self) { if (isNew) { UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:selfaction:@selector(save:)]; [[self navigationItem] setRightBarButtonItem:doneItem]; UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:selfaction:@selector(cancel:)]; [[self navigationItem] setLeftBarButtonItem:cancelItem]; } } return self;} • 如果是新建的话,设置左右按钮
  28. 28. 重写超类 designated initializer • 之前我们更改 designated initializer 的话,会重写 超类的 initializer 来调⽤用新的。这⾥里我们通过抛出 ⼀一个异常来使对超类 designated initializer 的调⽤用 ⾮非法 • 在 DetailViewController.m 中,重写 UIViewController 的 designated initializer - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { @throw [NSException exceptionWithName:@"Wrong initializer" reason:@"Use initForNewItem" userInfo:nil]; return nil; }• NSException。异常时,name 和 reason 会在控制台 显⽰示
  29. 29. 确认异常• ⺫⽬目前在 ItemsViewController 的 tableView:didSelectRowAtIndexPath: ⽅方法中调⽤用 initWithNibName:bundle: ⽅方法(对 init ⽅方法的调⽤用 最终会调⽤用 initWithNibName:bundle :⽅方法)• 构建并运⾏行,在表格中选择⼀一⾏行的结果是 “Wrong initializer”异常被抛出,程序挂起,可以在控制台 中看到⼀一个异常(name 和 reason)
  30. 30. 使⽤用新的 initializer • 为了消除这个异常,我们在 ItemsViewController.m 中更新 tableView:didSelectRowAtIndexPath: 以使⽤用新的 initializer- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{// DetailViewController *detailViewController = [[DetailViewControlleralloc] init]; DetailViewController *detailViewController = [[DetailViewControlleralloc] initForNewItem:NO]; NSArray *items = [[BNRItemStore defaultStore] allItems];
  31. 31. • 现在我们已经能让新的 initializer ⼯工作了,下⾯面更 改⼀一下⽤用户增加⼀一个新的 item 时我们的操作• 在 ItemsViewController.m 中,编辑 addNewItem: 来在 UINavigationController 中创建⼀一个 DetailViewController 的实例,并且把这个 navigation controller 模态化的呈现
  32. 32. 修改 ItemsViewController.m 中addNewItem- (IBAction)addNewItem:(id)sender{ BNRItem *newItem = [[BNRItemStore defaultStore] createItem];// int lastRow = [[[BNRItemStore defaultStore] allItems]indexOfObject:newItem];//// NSIndexPath *ip = [NSIndexPath indexPathForItem:lastRow inSection:0];//// // 把这个新⾏行插⼊入 table// [[self tableView] insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]withRowAnimation:UITableViewRowAnimationTop]; DetailViewController *detailViewController = [[DetailViewControlleralloc] initForNewItem:YES]; [detailViewController setItem:newItem]; UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:detailViewController]; [self presentViewController:navController animated:YES completion:nil];} • 构建并运⾏行,DetailViewController 从底部滑出
  33. 33. 模态化 view controllers 的释放
  34. 34. • 要释放(dismiss)⼀一个模态化呈现的 view controller,必须给呈现它的 view controller 发送 dismissViewControllerAnimated:completion: 消息• 我们在之前的 DetailViewController 中,当 UIImagePickerController 完成⼯工作并告诉 DetailViewController 以后, DetailViewController 就 把它释放了
  35. 35. • 现在的情况稍有不同。当⼀一个新的 item 被⽣生成 时,ItemsViewController 模态化的呈现这个 DetailViewController。• 这个 DetailViewController 在它的 navigationItem 上有两个按钮:Cancel 和 Done,按钮按下时 DetailViewController 将被释放
  36. 36. • 问题是,这些按钮的 action message 是发给 DetailViewController 的,DetailViewController 需 要⼀一种⽅方法来告诉呈现它的并且负责释放它的 ItemViewController,它已经完⼯工可以被释放了
  37. 37. presentingViewController 属性• 幸运的是,每个 UIViewController 都有⼀一个 presentingViewController 属性,这个属性指向呈 现它的 view controller• DetailViewController 将会获取⼀一个指向它的 presentingViewController 的指针,并把 dismissViewControllerAnimated:completion: 消息 发送给它
  38. 38. 实现 save action ⽅方法 • 在 DetailViewController.m 中实现为 Done 按钮实 现它的 save action ⽅方法- (void)save:(id)sender{ [[self presentingViewController] dismissViewControllerAnimated:YEScompletion:nil];}
  39. 39. Cancel -> 删除新建的 item• Cancel 按钮需要多做⼀一点⼯工作。• ⽤用户点击 ItemViewController 的按钮添加⼀一个新的 item 到列表,就创建了⼀一个新的 BNRItem 实例, 添加到 store,然后 DetailViewController 才滑动出 来供我们编辑• 如果⽤用户取消了这个 item 的⽣生成,那么这个 BNRItem 就需要从 store 中删除• 所以我们需要在 Cancel 结束后多做⼀一些⼯工作
  40. 40. Cancel 的实现 • ⾸首先要在 DetailViewController.m 中导⼊入 BNRItemStore 的头⽂文件#import "DetailViewController.h"#import "BNRItem.h"#import "BNRImageStore.h"#import "BNRItemStore.h"- (void)cancel:(id)sender{ [[BNRItemStore defaultStore] removeItem:item]; [[self presentingViewController] dismissViewControllerAnimated:YEScompletion:nil];}• 构建并运⾏行,新建然后cancel,不会有内容添加到 table view;新建,然后 Done, DetailViewController 将滑出屏幕
  41. 41. action message 的验证• 注意 cancel: 和 save: ⽅方法没有在任何地⽅方做声 明,这并没有什么问题• 声明⼀一个⽅方法是为了让编译器知道这个⽅方法的存 在,在设置 UIBarButtonItem 和 UIControl 的 action 时,编译器不会验证 action message,因为此时 并没有调⽤用。消息是在运⾏行时实际被发送时才被 验证• 如果这个⽅方法被定义了,将会运⾏行良好;否则就 会得到⼀一个 unrecognized selector exception
  42. 42. 真正的 presentingViewController• UINavigationController
  43. 43. Modal view controller 的样式
  44. 44. • 在 iPhone 和 iPod touch上,⼀一个模态化 view controller 会展满整个屏幕,是默认的⽽而且也只能 这样• 在 iPad 上,我们有两个附加选项: • form sheet style • 和 page sheet style
  45. 45. • 我们可以通过设置modal view controller 的 modalPresentationStyle 属性为⼀一个预设的常量: • UIModalPresentationFormSheet • 或 UIModalPresentationPageSheet 来更改这个呈现
  46. 46. form sheet style• form sheet style 在 iPad 屏幕中间的⼀一个矩形 内显⽰示模态化 view controller,并且把呈 现它的 view controller 的 view 变暗
  47. 47. page sheet style• 在 portrait 模式下和默 认全屏相同• 在 landscape 模式下, 保持它的宽度和 portrait 模式下相同, 并且把贴在它下⾯面的 呈现它的 view controller 的左右边变 暗
  48. 48. 改变 presentation style • 在 ItemsViewController.m 中修改 addNewItem: ⽅方 法来变更被呈现的 UINavigationController 的 presentation style UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:detailViewController]; [navController setModalPresentationStyle:UIModalPresentationFormSheet]; [self presentViewController:navController animated:YES completion:nil];
  49. 49. 新的 item 不⻅见了?• 再次构建并运⾏行,点击按钮增加⼀一个新的item, modal view controller 滑⼊入屏幕。然后点击 Done 按钮,table view 重新出来了,但是新的 BNRItem 并没有显⽰示出来!??
  50. 50. • 变更 presentation style 前,modal view controller 是全屏显⽰示的,这引起了 ItemsViewController 的 view disappear。• 当 modal view controller 被 dismiss 后, ItemsViewController 被发送 viewWillAppear: 消 息,然后我们是在 viewWillAppear: 中重新加载所 有新加的数据
  51. 51. table view 重新加载数据• 当使⽤用新的 presentation style 时, ItemsViewController 的 view 在它呈现 view controller 的时候并没有消失(只是颜⾊色变暗 了)。• 因⽽而在 modal view controller 被 dismissed 的时候 没有被发送重新出现的消息,因此也没有机会重 新加载数据。
  52. 52. • ItemsViewController 重新加载它的 table view 的代 码很简单: • 类似:[[self tableview] reloadData] • 但是我们必须找到⼀一个机会来重新加载这些数据
  53. 53. • 我们需要做的是打包上⾯面的代码,然后恰好在modal view controller 被 dismissed 的时候让它执⾏行• 我们可以使⽤用dismissViewControllerAnimated:completion: 中⼀一个内置的机制来完成这个
  54. 54. Completion blocks
  55. 55. • 我们可以把重新加载 table view 的代码放⼊入⼀一个 block,然后传给 dismissViewControllerAnimated:completion: , 然 后代码就正好在 modal view controller 被 dismissed 时候执⾏行 • 在 DetailViewController.h 中,增加⼀一个指向⼀一个 block 的属性,在 .m 中 synthesize@property (nonatomic, copy) void (^dismissBlock)(void);@implementation DetailViewController@synthesize item;@synthesize dismissBlock;
  56. 56. 传递 block • 我们不能在 DetailViewController ⾃自⾝身中创建这个 block,⽽而只能在 ItemsViewController 中创建,只 有 ItemsViewController 才知道它的 tableView • 在 ItemsViewController.m 中的 addNewItem: ⽅方法 中,⽣生成⼀一个重新加载 ItemsViewController 的表 格的 block,并把它传给 DetailViewController- (IBAction)addNewItem:(id)sender{ BNRItem *newItem = [[BNRItemStore defaultStore] createItem]; DetailViewController *detailViewController = [[DetailViewControlleralloc] initForNewItem:YES]; [detailViewController setItem:newItem]; [detailViewController setDismissBlock:^{ [[self tableView] reloadData]; }]; UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:detailViewController];
  57. 57. • 这时当⽤用户再点击按钮添加⼀一个新的 item 时,⼀一 个重新加载 ItemsViewController 的表格的 block 会 被创建,并被设成 DetailViewController 的 dismissBlock。• DetailViewController 将会持有这个block 直到 DetailViewController 需要被释放• 在被释放时,DetailViewController 将把这个 block 传给 dismissViewControllerAnimated:completion:
  58. 58. • 在 DetailViewController.m 中修改 save: 和 cancel: 的实现,使⽤用 dismissBlock 作为⼀一个参数发送 dismissViewControllerAnimated:completion: 消息 • 再构建并运⾏行,新增加的 BNRItem 就会出现在表 格中了- (void)cancel:(id)sender{ [[BNRItemStore defaultStore] removeItem:item];// [[self presentingViewController] dismissViewControllerAnimated:YEScompletion:nil]; [[self presentingViewController] dismissViewControllerAnimated:YEScompletion:dismissBlock];}- (void)save:(id)sender{// [[self presentingViewController] dismissViewControllerAnimated:YEScompletion:nil]; [[self presentingViewController] dismissViewControllerAnimated:YEScompletion:dismissBlock];}
  59. 59. Modal view controller 的转换
  60. 60. • 最后我们看⼀一下 modal view controller 的 transitions。• 除了可以更改 modal view controller 的 presentation style 之外,我们也可以更改把它放 置到屏幕上时的动画效果。
  61. 61. • 使⽤用 modalTransitonStyle 属性,可以设置⼀一个预 定义的常量。 • 默认是将从屏幕底部滑出到屏幕 • 在 ItemsViewController.m 中更新 addItmName: ⽅方 法使⽤用⼀一个不同的 transition(横向翻动) [navController setModalPresentationStyle:UIModalPresentationFormSheet]; [navControllersetModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; [self presentViewController:navController animated:YES completion:nil];

×