SlideShare uma empresa Scribd logo
1 de 61
Baixar para ler offline
UIPopoverController 和 Modal View
Controller




范圣刚,princetoad@gmail.com, www.tfan.org
• 截⾄至⺫⽬目前我们已经看到了有四种⽅方法可以⽤用来显
⽰示⼀一个 view controller 的 view:
• 第⼀一种是可以设成 window 的 root view controller,
 view 就变成了 window 的 subview

• 第⼆二种是把 view controller 压⼊入⼀一个
 UINavigationController 的堆栈

• 第三种是我们可以把它添加到⼀一个 UITabBarController
• 以及模态化的把它呈现出来
• 在这个主题我们来看⼀一下 UIPopoverController, 以
 及更多⽤用来呈现模态化 view controller 的选项

• 有些选项只对 iPad ⽣生效,我们第⼀一步先把
 Homepwner 改成 universal appication - 就是可以
 本地化的在iPad,以及iPhone,iPod touch上运⾏行
Universalizing Homepwner
• Device -> Universal
改进界⾯面的两种⽅方式
• 改成 Universal 之后,ItemsViewController 视图在
 iPad 上看起来很好,但是如果选择⼀一⾏行的话,可
 以看到 DetailViewController 的界⾯面可能需要⼀一些
 加⼯工(⽽而且轻击获取图⽚片的按钮会抛出异常,后
 ⾯面⻢马上会纠正这个问题)
• 改进 DetailViewController 界⾯面效果的⼀一种⽅方法是
 设置 autosizing mask,让他的 iPad 上⾃自动调整来
 适合 iPad

• 另外⼀一种是创建两个完全独⽴立的 XIB ⽂文件,⼀一个
 针对 iPhone,⼀一个针对 iPad(加上~ipad 后缀)
 (要在iPad版本上重新创建视图和重新连接)。
背景颜⾊色
• 现在我们先不关⼼心 DetailViewController 视图的显
 ⽰示。我们先来处理⼀一下 iPhone 下的纯⽩白背景
• 如果是 iPad 的话,我们把背景⾊色设成
 groupTableViewBackgroundColor 颜⾊色;如果是
 iPhone 的话,给它设定⼀一个和
 ItemsViewController接近的颜⾊色
确定 device family
 • ⾸首先我们要能够判断设备类型
 • 使⽤用 UIDevice 类的类⽅方法 currentDevice 可以得到当前
  设备,然后通过检查它的 userInterfaceIdiom 属性判断
  是何种设备。⺫⽬目前有两个可能的值:
  • UIUserInterfaceIdiomPad - iPad
  • UIUserInterfaceIdiomPhone - iPhone 或 iPod touch
userInterfaceIdiom:
 • 在 DetailViewController.m 中修改 viewDidLoad:
 • 我们在后⾯面会经常⽤用到这种判断
- (void)viewDidLoad
{
    [super viewDidLoad];
//    [[self view] setBackgroundColor:[UIColor
groupTableViewBackgroundColor]];

    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];
}
⾃自动翻转设置
 • 我们要让应⽤用在 iPad 上⽀支持所有⽅方向的翻转,在
    iPhone 上只⽀支持 Portrait
 • 在 ItemsViewController.m 和
    DetailViewController.m 中都增加下⾯面的⽅方法
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad) {
        return YES;
    } else {
        return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
    }
}

构建并运⾏行可以看到在 iPad 上视图可以进⾏行翻转
UIPopoverController
• 现在 Homepwner 已经可以在 iPad 上运⾏行了,我们
 可以充分利⽤用 iPad 特有的⽅方法来呈现 view controller

• ⾸首先我们来看⼀一下 UIPopoverController
• 应⽤用程序经常需要呈现⼀一个 view controller 给⽤用户,
 让⽤用户选择下⼀一步做什么。⽐比如 image picker。

• 在 iPhone 和 iPod touch 上,这类 view controller 通
 常是模态化呈现,占满整个屏幕。

• 有更多屏幕空间的 iPad 有另外⼀一种选择:
 UIPopoverController
• popover controller 在应⽤用程序界⾯面的上⾯面浮动显
 ⽰示另⼀一个 view controller 的 view,显⽰示在⼀一个边
 框窗⼝口中。

• 当我们⽣生成⼀一个 UIPopoverController 时,我们把
 这另⼀一个 view controller 设成了 popover controller
 的 contentViewController

• 我们要实现的是,⽤用户轻击 camera 按钮时,我们
 在⼀一个 UIPopoverController ⾥里⾯面把
 UIImagePickerController 呈现出来
popover controller
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;
}
创建,设置和呈现 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:sender
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    } else {
        [self presentViewController:imagePicker animated:YES
completion:nil];
    }
}
释放 popover
• 构建并在 iPad 模拟器中运⾏行,导航到
 DetailViewController 然后点击 camera 按钮,pop
 over 会出现并且显⽰示 image picker。选择⼀一个图
 ⽚片,会出现在 DetailViewController 的 view 上。
• 我们可以点击屏幕上的任何位置来释放这个
 popover controller。当 popover 被以这种⽅方式释
 放时,它发送⼀一个
 popoverControllerDidDismissPopover: 消息给它
 的 delegate
popoverControllerDidDismissPopover:
- (void)popoverControllerDidDismissPopover:(UIPopoverController
*)popoverController
{
    NSLog(@"释放 popover");
    imagePickerPopover = nil;
}


 • 在 DetailViewController.m 中实现这个⽅方法
 • 注意这⾥里我们把 imagePickerPopover 设成 nil 来销
    毁它,我们每次点击 camera 按钮时都会⽣生成⼀一个
    新的
选完图⽚片后释放 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
销毁不可⻅见的 popover controller
• 还有⼀一个⼩小 bug 要处理,UIPopoverController 可
 ⻅见的情况下,再次点击 camera 按钮,应⽤用会崩溃
• 原因是点击 camera 按钮,在 takePicture: 中
 imagePickerPopover 被设成了新的
 UIPopoverController,这时屏幕上的
 UIPopoverController 就被销毁。
• 已经销毁的对象还是可⻅见的,就出现异常了。我
 们要确保销毁的是不可⻅见的 UIPopoverController
已有,先释放
 • 在 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];
更多关于模态化 View Controller
• 在这个部分,我们更新 Homepwner,让它在⽤用户
新建⼀一个 BNRItem 时模态化的呈现
DetailViewController

• 当⽤用户选择⼀一个已经存在的 BNRItem 时,我们和
之前⼀一样把 DetailViewController 压⼊入
UINavigationController 的堆栈
新建⼀一个 BNRItem
双重⽤用法
 • 要实现 DetailViewController 的双重⽤用法(new 或
    edit),我们要给它⼀一个名为 initForNewItem: 的新
    的 designated initializer
 • 在这个 initializer 中检查这个实例是被⽤用来⽣生成⼀一
    个新的 BNRItem 还是⽤用来显⽰示⼀一个已经存在的,
    然后对应的来设置⽤用户界⾯面
 • ⾸首先在 DetailViewController.h 中声明这个
    initializer
- (id)initForNewItem:(BOOL)isNew;

@property (nonatomic, strong) BNRItem *item;
Done 和 Cancel
• 如果 DetailViewController 被⽤用来⽣生成⼀一个新的
 BNRItem,我们希望在它的 navigation item 上显⽰示
 ⼀一个 Done 按钮和⼀一个 Cancel 按钮
• 在 DetailViewController.m 中实现这个⽅方法
实现 initForNewItem:
- (id)initForNewItem:(BOOL)isNew
{
    self = [super initWithNibName:@"DetailViewController" bundle:nil];

    if (self) {
        if (isNew) {
            UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self
action:@selector(save:)];
            [[self navigationItem] setRightBarButtonItem:doneItem];

            UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self
action:@selector(cancel:)];
            [[self navigationItem] setLeftBarButtonItem:cancelItem];
        }
    }

    return self;
}



    • 如果是新建的话,设置左右按钮
重写超类 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 会在控制台
 显⽰示
确认异常
• ⺫⽬目前在 ItemsViewController 的
 tableView:didSelectRowAtIndexPath: ⽅方法中调⽤用
 initWithNibName:bundle: ⽅方法(对 init ⽅方法的调⽤用
 最终会调⽤用 initWithNibName:bundle :⽅方法)
• 构建并运⾏行,在表格中选择⼀一⾏行的结果是 “Wrong
 initializer”异常被抛出,程序挂起,可以在控制台
 中看到⼀一个异常(name 和 reason)
使⽤用新的 initializer
 • 为了消除这个异常,我们在
    ItemsViewController.m 中更新
    tableView:didSelectRowAtIndexPath: 以使⽤用新的
    initializer
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath
{
//    DetailViewController *detailViewController = [[DetailViewController
alloc] init];
    DetailViewController *detailViewController = [[DetailViewController
alloc] initForNewItem:NO];

   NSArray *items = [[BNRItemStore defaultStore] allItems];
• 现在我们已经能让新的 initializer ⼯工作了,下⾯面更
 改⼀一下⽤用户增加⼀一个新的 item 时我们的操作

• 在 ItemsViewController.m 中,编辑 addNewItem:
 来在 UINavigationController 中创建⼀一个
 DetailViewController 的实例,并且把这个
 navigation controller 模态化的呈现
修改 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 = [[DetailViewController
alloc] initForNewItem:YES];
    [detailViewController setItem:newItem];

    UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:detailViewController];


     [self presentViewController:navController animated:YES completion:nil];
}

    • 构建并运⾏行,DetailViewController 从底部滑出
模态化 view controllers 的释放
• 要释放(dismiss)⼀一个模态化呈现的 view
 controller,必须给呈现它的 view controller 发送
 dismissViewControllerAnimated:completion: 消息

• 我们在之前的 DetailViewController 中,当
 UIImagePickerController 完成⼯工作并告诉
 DetailViewController 以后, DetailViewController 就
 把它释放了
• 现在的情况稍有不同。当⼀一个新的 item 被⽣生成
 时,ItemsViewController 模态化的呈现这个
 DetailViewController。

• 这个 DetailViewController 在它的 navigationItem
 上有两个按钮:Cancel 和 Done,按钮按下时
 DetailViewController 将被释放
• 问题是,这些按钮的 action message 是发给
 DetailViewController 的,DetailViewController 需
 要⼀一种⽅方法来告诉呈现它的并且负责释放它的
 ItemViewController,它已经完⼯工可以被释放了
presentingViewController 属性
• 幸运的是,每个 UIViewController 都有⼀一个
 presentingViewController 属性,这个属性指向呈
 现它的 view controller
• DetailViewController 将会获取⼀一个指向它的
 presentingViewController 的指针,并把
 dismissViewControllerAnimated:completion: 消息
 发送给它
实现 save action ⽅方法
 • 在 DetailViewController.m 中实现为 Done 按钮实
   现它的 save action ⽅方法
- (void)save:(id)sender
{
    [[self presentingViewController] dismissViewControllerAnimated:YES
completion:nil];
}
Cancel -> 删除新建的 item
• Cancel 按钮需要多做⼀一点⼯工作。
• ⽤用户点击 ItemViewController 的按钮添加⼀一个新的
 item 到列表,就创建了⼀一个新的 BNRItem 实例,
 添加到 store,然后 DetailViewController 才滑动出
 来供我们编辑
• 如果⽤用户取消了这个 item 的⽣生成,那么这个
 BNRItem 就需要从 store 中删除
• 所以我们需要在 Cancel 结束后多做⼀一些⼯工作
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:YES
completion:nil];
}


• 构建并运⾏行,新建然后cancel,不会有内容添加到
  table view;新建,然后 Done,
  DetailViewController 将滑出屏幕
action message 的验证
• 注意 cancel: 和 save: ⽅方法没有在任何地⽅方做声
 明,这并没有什么问题
• 声明⼀一个⽅方法是为了让编译器知道这个⽅方法的存
 在,在设置 UIBarButtonItem 和 UIControl 的 action
 时,编译器不会验证 action message,因为此时
 并没有调⽤用。消息是在运⾏行时实际被发送时才被
 验证
• 如果这个⽅方法被定义了,将会运⾏行良好;否则就
 会得到⼀一个 unrecognized selector exception
真正的 presentingViewController
• UINavigationController
Modal view controller 的样式
• 在 iPhone 和 iPod touch上,⼀一个模态化 view
 controller 会展满整个屏幕,是默认的⽽而且也只能
 这样

• 在 iPad 上,我们有两个附加选项:
 • form sheet style
 • 和 page sheet style
• 我们可以通过设置modal view controller 的
 modalPresentationStyle 属性为⼀一个预设的常量:
 • UIModalPresentationFormSheet
 • 或 UIModalPresentationPageSheet 来更改这个呈现
form sheet style
• form sheet style 在 iPad
 屏幕中间的⼀一个矩形
 内显⽰示模态化 view
 controller,并且把呈
 现它的 view controller
 的 view 变暗
page sheet style
• 在 portrait 模式下和默
 认全屏相同

• 在 landscape 模式下,
 保持它的宽度和
 portrait 模式下相同,
 并且把贴在它下⾯面的
 呈现它的 view
 controller 的左右边变
 暗
改变 presentation style
 • 在 ItemsViewController.m 中修改 addNewItem: ⽅方
   法来变更被呈现的 UINavigationController 的
   presentation style

    UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:detailViewController];

    [navController setModalPresentationStyle:UIModalPresentationFormSheet];

    [self presentViewController:navController animated:YES completion:nil];
新的 item 不⻅见了?
• 再次构建并运⾏行,点击按钮增加⼀一个新的item,
 modal view controller 滑⼊入屏幕。然后点击 Done
 按钮,table view 重新出来了,但是新的 BNRItem
 并没有显⽰示出来!??
• 变更 presentation style 前,modal view controller
 是全屏显⽰示的,这引起了 ItemsViewController 的
 view disappear。

• 当 modal view controller 被 dismiss 后,
 ItemsViewController 被发送 viewWillAppear: 消
 息,然后我们是在 viewWillAppear: 中重新加载所
 有新加的数据
table view 重新加载数据
• 当使⽤用新的 presentation style 时,
 ItemsViewController 的 view 在它呈现 view
 controller 的时候并没有消失(只是颜⾊色变暗
 了)。
• 因⽽而在 modal view controller 被 dismissed 的时候
 没有被发送重新出现的消息,因此也没有机会重
 新加载数据。
• ItemsViewController 重新加载它的 table view 的代
 码很简单:
 • 类似:[[self tableview] reloadData]
 • 但是我们必须找到⼀一个机会来重新加载这些数据
• 我们需要做的是打包上⾯面的代码,然后恰好在
modal view controller 被 dismissed 的时候让它执
⾏行

• 我们可以使⽤用
dismissViewControllerAnimated:completion: 中⼀一
个内置的机制来完成这个
Completion blocks
• 我们可以把重新加载 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;
传递 block
 • 我们不能在 DetailViewController ⾃自⾝身中创建这个
    block,⽽而只能在 ItemsViewController 中创建,只
    有 ItemsViewController 才知道它的 tableView
 • 在 ItemsViewController.m 中的 addNewItem: ⽅方法
    中,⽣生成⼀一个重新加载 ItemsViewController 的表
    格的 block,并把它传给 DetailViewController
- (IBAction)addNewItem:(id)sender
{
    BNRItem *newItem = [[BNRItemStore defaultStore] createItem];

    DetailViewController *detailViewController = [[DetailViewController
alloc] initForNewItem:YES];
    [detailViewController setItem:newItem];

    [detailViewController setDismissBlock:^{
        [[self tableView] reloadData];
    }];

    UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:detailViewController];
• 这时当⽤用户再点击按钮添加⼀一个新的 item 时,⼀一
 个重新加载 ItemsViewController 的表格的 block 会
 被创建,并被设成 DetailViewController 的
 dismissBlock。

• DetailViewController 将会持有这个block 直到
 DetailViewController 需要被释放

• 在被释放时,DetailViewController 将把这个 block
 传给 dismissViewControllerAnimated:completion:
• 在 DetailViewController.m 中修改 save: 和 cancel:
    的实现,使⽤用 dismissBlock 作为⼀一个参数发送
    dismissViewControllerAnimated:completion: 消息

 • 再构建并运⾏行,新增加的 BNRItem 就会出现在表
    格中了
- (void)cancel:(id)sender
{
    [[BNRItemStore defaultStore] removeItem:item];

//    [[self presentingViewController] dismissViewControllerAnimated:YES
completion:nil];
    [[self presentingViewController] dismissViewControllerAnimated:YES
completion:dismissBlock];
}
- (void)save:(id)sender
{
//    [[self presentingViewController] dismissViewControllerAnimated:YES
completion:nil];
    [[self presentingViewController] dismissViewControllerAnimated:YES
completion:dismissBlock];
}
Modal view controller 的转换
• 最后我们看⼀一下 modal view controller 的
 transitions。

• 除了可以更改 modal view controller 的
 presentation style 之外,我们也可以更改把它放
 置到屏幕上时的动画效果。
• 使⽤用 modalTransitonStyle 属性,可以设置⼀一个预
   定义的常量。

 • 默认是将从屏幕底部滑出到屏幕
 • 在 ItemsViewController.m 中更新 addItmName: ⽅方
   法使⽤用⼀一个不同的 transition(横向翻动)
    [navController setModalPresentationStyle:UIModalPresentationFormSheet];
    [navController
setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];

    [self presentViewController:navController animated:YES completion:nil];

Mais conteúdo relacionado

Destaque

07 View Controllers
07 View Controllers07 View Controllers
07 View ControllersTom Fan
 
14 Saving Loading and Application States
14 Saving Loading and Application States14 Saving Loading and Application States
14 Saving Loading and Application StatesTom Fan
 
02 Objective-C
02 Objective-C02 Objective-C
02 Objective-CTom Fan
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分Tom Fan
 
17 Localization
17 Localization17 Localization
17 LocalizationTom Fan
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状Tom Fan
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Tom Fan
 
05 MapKit and Text Input
05 MapKit and Text Input05 MapKit and Text Input
05 MapKit and Text InputTom Fan
 

Destaque (8)

07 View Controllers
07 View Controllers07 View Controllers
07 View Controllers
 
14 Saving Loading and Application States
14 Saving Loading and Application States14 Saving Loading and Application States
14 Saving Loading and Application States
 
02 Objective-C
02 Objective-C02 Objective-C
02 Objective-C
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
 
17 Localization
17 Localization17 Localization
17 Localization
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
 
05 MapKit and Text Input
05 MapKit and Text Input05 MapKit and Text Input
05 MapKit and Text Input
 

Semelhante a 13 UIPopoverController and Modal View Controller

10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableViewTom Fan
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCellTom Fan
 
Core data lightweight_migration
Core data lightweight_migrationCore data lightweight_migration
Core data lightweight_migrationMichael Pan
 
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
 
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPiOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPMing-Sian Lin
 
Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1modou li
 
製作 Unity Plugin for iOS
製作 Unity Plugin for iOS製作 Unity Plugin for iOS
製作 Unity Plugin for iOSJohnny Sung
 
Wxpython In Action
Wxpython In ActionWxpython In Action
Wxpython In Action智锋 范
 
React + mobx分享(黄英杰)
React + mobx分享(黄英杰)React + mobx分享(黄英杰)
React + mobx分享(黄英杰)kkxx1254
 

Semelhante a 13 UIPopoverController and Modal View Controller (17)

I os 01
I os 01I os 01
I os 01
 
10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableView
 
005
005005
005
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCell
 
Core data lightweight_migration
Core data lightweight_migrationCore data lightweight_migration
Core data lightweight_migration
 
I os 16
I os 16I os 16
I os 16
 
I os 09
I os 09I os 09
I os 09
 
I os 14
I os 14I os 14
I os 14
 
View Animation
View AnimationView Animation
View Animation
 
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 ...
 
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAPiOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP
 
Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1
 
I os 05
I os 05I os 05
I os 05
 
製作 Unity Plugin for iOS
製作 Unity Plugin for iOS製作 Unity Plugin for iOS
製作 Unity Plugin for iOS
 
Wxpython In Action
Wxpython In ActionWxpython In Action
Wxpython In Action
 
Ios
IosIos
Ios
 
React + mobx分享(黄英杰)
React + mobx分享(黄英杰)React + mobx分享(黄英杰)
React + mobx分享(黄英杰)
 

Mais de Tom Fan

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统Tom Fan
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workersTom Fan
 
Web sockets
Web socketsWeb sockets
Web socketsTom Fan
 
Semantics
SemanticsSemantics
SemanticsTom Fan
 
Multimedia
MultimediaMultimedia
MultimediaTom Fan
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5Tom Fan
 
Html5 history
Html5 historyHtml5 history
Html5 historyTom Fan
 
Geolocation
GeolocationGeolocation
GeolocationTom Fan
 
File api
File apiFile api
File apiTom Fan
 
Deviceaccess
DeviceaccessDeviceaccess
DeviceaccessTom Fan
 
Webstorage
WebstorageWebstorage
WebstorageTom Fan
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发Tom Fan
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型Tom Fan
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaultsTom Fan
 
16 CoreData
16 CoreData16 CoreData
16 CoreDataTom Fan
 

Mais de Tom Fan (17)

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
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaults
 
16 CoreData
16 CoreData16 CoreData
16 CoreData
 

13 UIPopoverController and Modal View Controller

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