这节课的主要内容包括iPad、Split Views、Popovers及做一个universal的应用并在两种设备上运行。

UIToolbar

通常在屏幕的顶部或底部,长得像钢筋一样。一个工具栏,是一个UIBarButtonItems的集合。UIBarButtonItems不是按钮,有点像是简化过的按钮。UINavigationController有一个在底部的工具栏,可以通过在xcode里inspect Navigation Controller中的Shows Toolbar小开关打开,但工具栏上的按钮和Navigation Controller本身没有任何关系,它只和当时显示的ViewController有关。

UIBarButtonItems

是使UIToolbar真正工作的东西,他们有target action,就像一个按钮。有两个特殊的UIBarButtonItems,Fixed和Flexible space,这些都是用来安排你UIToolbar按钮的显示方式。创建一个UIBarButtonItems的方式是alloc/init,它可以采用文字或图像,或者可以用另一种叫做initWithBarButtonSystemItem的初始化方法,然后你给它在枚举类型中指定一个。

要创建一个工具栏非常简单,只需将其拖动到你的ViewController,通常会连到一个outlet,然后只需从xcode中拖个UIBarButtons到它里面,或者在代码中设置个数组,工具栏对象有个物品数组。

UISplitViewController

创建一个UISplitViewController的方法是在xcode里将它托出来,只能拖动Split View到一个ipad类型的storyboard。Split View通常只是一个基本元素,它填满整个屏幕,不可能把Split View放到其他什么的内部,一般情况下是提供给整个app的。Split View有两个ViewControllers,一个左侧一个右侧,左侧叫Master,右侧叫Detail。SplitViewController有一个property叫做ViewControllers,它是一个数组,这个数组有两个元素,左侧和右侧,左侧是元素0,右侧是元素1,可以在代码中设置你的两个ViewController(通常得同时设置),可以在xcode中设置这两个东西,可以control拖动到左侧来设置左侧或拖动到右侧来设置右侧,然后它要改变就用segue。

@property (nonatomic, copy) NSArray *viewControllers;

此API不希望你传递含有这两个ViewController的可变数组,然后期待如果你改变了一个,它会以某种方式更新Split View。所以这里说的是我要复制你给我的东西,而且我要使用它。所以如果你要改变它,你得再给我一次。就是不想让你传递可变数组,以防止你改变了它导致发生意外。

Split View不能没有delegate,如果没有设置delegate,那么当Split View进入Portrait模式的时候左侧就会消失,你应该在角落里放一个小按钮,使用户可以点击它来让左侧出现在popover里。这是Split View的工作原理。如果不实现delegate就没办法放上那个按钮,就无法在portrait模式下调出左侧。通常情况下,你要在ViewController的viewDidLoad或awakeFromNib方法里设置此delegate。所以Split View的delegate最主要的任务就是处理旋转,delegate是管理左侧的。

- (BOOL)splitViewController:(UISplitViewController *)sender
   shouldHideViewController:(UIViewController *)master
              inOrientation:(UIInterfaceOrientation)orientation
{
     return YES; // always hide it
}

这个delegate方法是被发送到你的delegate询问在特定方向下你想要左侧做什么,因此它把自己传递给你,还有左侧,它会问在这个方向你想要我对左侧做什么。要隐藏就返回YES,要保留在屏幕上就返回NO。

- (BOOL)splitViewController:(UISplitViewController *)sender
   shouldHideViewController:(UIViewController *)master
              inOrientation:(UIInterfaceOrientation)orientation
{
     return UIInterfaceOrientationIsPortrait(orientation);
}

这是如果不实现delegate其余的方法会出现的问题,因为portrait会隐藏左侧,但你没有放那个小按钮,系统不会帮你做这一步。它会给你按钮,但你得把它放到屏幕上。因此,其余的delegate方法就是要把按钮放上去。其中有两个非常重要,一个用来把按钮放上去,一个用来把按钮拿掉。例如旋转回Landscape:

- (void)splitViewController:(UISplitViewController *)sender
     willHideViewController:(UIViewController *)master
          withBarButtonItem:(UIBarButtonItem *)barButtonItem
       forPopoverController:(UIPopoverController *)popover
{
     barButtonItem.title = @“Master”; // use a better word than “Master”!
     // setSplitViewBarButtonItem: must put the bar button somewhere on screen
     // probably in a UIToolbar or a UINavigationBar
     [detailViewController setSplitViewBarButtonItem:barButtonItem];
}

这是用来隐藏左侧的,它给你一个barButton,就是第三个参数,它说把这个barButton放到屏幕上,因为我将要隐藏左侧的ViewController。当这个barButton被按下,它会自动显示左侧,但是必须把barButton放到某处。

当你想要旋转回Landscape,并把左侧放回到屏幕上,它会向你发送此消息:

- (void)splitViewController:(UISplitViewController *)sender
     willShowViewController:(UIViewController *)master
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
   // removeSplitViewBarButtonItem: must remove the bar button from its toolbar
   [detailViewController removeSplitViewBarButtonItem:nil];
}

这就是把按钮从工具栏里移除,因为不想左侧显示时,还有这个按钮。

有很多办法可以用来实现SplitView delegate,这取决于在两侧使用的是什么ViewController。何时该设置delegate:一、你得决定谁是delegate,是左侧的Master还是右侧detail?此外你得去考虑可重用性,两者之一可能是通用的,所以不能在那上面实现SplitView delegate,它是一个通用的可重复使用的view。这有个例子,在Detail一侧总是把按钮放上去,那么按钮就总是在Detail一侧。

- (void)setSplitViewBarButtonItem:(UIBarButtonItem *)barButtonItem
{
     UIToolbar *toolbar = [self toolbar]; // might be outlet or calculated
     NSMutableArray *toolbarItems = [toolbar.items mutableCopy];
     if (_splitViewBarButtonItem) [toolbarItems removeObject:_splitViewBarButtonItem];
     // put the bar button on the left of our existing toolbar
     if (barButtonItem) [toolbarItems insertObject:barButtonItem atIndex:0];
     toolbar.items = toolbarItems;
     _splitViewBarButtonItem = barButtonItem;
}

这个方法会把barButtonItem插入到工具栏的最左侧,不管工具栏里的其他东西它都会被插在左侧。

通常,点击Master里的东西,Detail显示你点击的东西的详细信息。那么当Master改变时,Detail是怎么更新的?有两个选择,一是简单的target action,在左侧的Master View你已经有了一些按钮或什么,当被点击它会发送target action消息给你的Master Controller。当它想更新Detail,它只要发一个消息到Detail。它是如何从Master得到Detail的?例如Master View Controller里一个target action消息叫doit:

- (IBAction)doit
{
     id detailViewController = [[self.splitViewController viewControllers] lastObject];
     [detailViewController setSomeProperty:...];
}

二是也可以segue,在SplitViewController里只有一种segue能用,它被称为replace segue,这是因为它会替换Master或Detail。使用replace segue有个要警告的地方:当你替换Detail View的时候,如果工具栏里有SplitView barButton,该工具栏也被替换,因为整个view都会被替换,所以你得把SplitView barButton转移出来,得把它转移到新的delegate生成的popover上,通常在准备segue的时候去完成做这些。所以如果你要准备segue,你需要问detail关于它的barButton,你设置barButton,然后让segue发生。

Popover

就是漂浮选单,它只是一个NSObject,它控制ViewController,它的工作基本上就是通过contentViewController绘制它的ViewController。通常Popover发生内容,是因为你control拖动它,并有一个segue。Popover segue做的是,你control拖动它到ViewController,它会把ViewController放到Popover里,不管是从按钮或什么拖动,那就是Popover的指向。如果你在代码中做Popover,或使用了storyboard,在storyboard的方法instantiateViewControllerWithIdentifier会给你一个storyboard外部的ViewController。如果得到一个ViewController,然后发消息给UIPopoverController,进行alloc/init就有了PopoverController,你向其发送这两个消息之一,使其出现在屏幕上。

- (void)presentPopoverFromRect:(CGRect)aRect or
                        inView:(UIView *)view
      permittedArrowDirections:(UIPopoverArrowDirection)direction
                      animated:(BOOL)flag;
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)barButtonItem
               permittedArrowDirections:(UIPopoverArrowDirection)direction
                               animated:(BOOL)flag;

得保持一个strong指针指向你的PopoverController,所有这些显示的东西都不会有strong指针指向你的PopoverController。

消除Popover,把它从屏幕上去掉,通常是用户触发的,有两种方式:一是如果点击了屏幕上除Popover以外的地方,它会关闭Popover;当你点击Popover里的东西,它是怎么消除的呢?答案是调用了dismissPopoverAnimated,但你不能从popover里的ViewController调用dismissPopoverAnimated。不管把Popover放上来的是哪个对象,这个对象就应该负责消除Popover。

Popover有delegate,实际上它有两个方法,一是问你是否要消除,另一个是告诉你它何时被消除,但这只发生在有人通过点击Popover以外的地方来消除的情况。

设置Popover大小的方式:一最常用的方法是在xcode里设置,选中一个ViewController并inspect它,通过Popover小开关打开并指定大小;二是通过UIViewController中的contentSizeForViewInPopover方法;第三种方式是UIPopoverController有个方法可以设置Popover的大小。

Universal Applications

一个通用app就是单一一个应用程序,但可以运行在ipad或iphone上,可以共享ViewController。调用这个宏UI_USER_INTERFACE_IDIOM可以知道在ipad上运行,你问它是UIUserInterfaceIdiomPad还是UIUserInterfaceIdiomPhone,这会告诉你是否在ipad上运行:

BOOL iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);

但这不是最好的办法,询问我是否处在SplitViewController里会更好。

Demo

修改上一节的demo,使其成为一个通用app,其中要创建一个ipad的storyboard。

修改spring&structs,如果视图控制器是UIViewController类,则需要一个UIViewController子类重载shouldAutorotateToInterfaceOrientation方法才能进行旋转。创建完UIViewController的子类后只要留下shouldAutorotateToInterfaceOrientation方法,并使它返回yes。

修改app设置使其成为一个通用app,把原来的MainStoryboard.storyboard改为:iPhone.storyboard,修改iPhone的storyboard:

新建iPad.storyboard,并设置iPad的storyboard。

在iPad.storyboard里拖一个SplitViewController进来,删除tableViewController,修改右侧控制器的类为HappinessViewController,再拖一个View到HappinessViewController并把它的类修改为FaceView,要设置outlet和action。在iPhone.storyboard中复制根视图控制器,到iPad.storyboard再粘贴,从导航控制器control拖动到根视图控制器,再把Psychologist View Controller粘贴过来并用segue关联起来:

修改PsychologistViewController.m中的代码如下:

- (HappinessViewController *)splitViewHappinessViewController
{
    id hvc = [self.splitViewController.viewControllers lastObject];
    if (![hvc isKindOfClass:[HappinessViewController class]]) {
        hvc = nil;
    }
    return hvc;
}

- (void)setAndShowDiagnosis:(int)diagnosis
{
    self.diagnosis = diagnosis;
    if ([self splitViewHappinessViewController]) {
        [self splitViewHappinessViewController].happiness = diagnosis;
    }else{
        [self performSegueWithIdentifier:@"ShowDiagnosis" sender:self];
    }
}

把SplitView delegate放进RotatableViewController,让RotatableViewController成为SplitView的delegate。RotatableViewController的代码如下:

#import <UIKit/UIKit.h>

@interface RotatableViewController : UIViewController<UISplitViewControllerDelegate>

@end
#import "RotatableViewController.h"

#import "SplitViewBarButtonItemPresenter.h"

@implementation RotatableViewController

- (void)awakeFromNib{
    [super awakeFromNib];
    self.splitViewController.delegate = self;
}

- (id <SplitViewBarButtonItemPresenter>)splitViewBarButtonItemPresenter
{
    id detailVC = [self.splitViewController.viewControllers lastObject];
    if (![detailVC conformsToProtocol:@protocol(SplitViewBarButtonItemPresenter)]) {
        detailVC = nil;
    }
    return detailVC;
}

- (BOOL)splitViewController:(UISplitViewController *)svc
   shouldHideViewController:(UIViewController *)vc
              inOrientation:(UIInterfaceOrientation)orientation
{
    return [self splitViewBarButtonItemPresenter]? UIInterfaceOrientationIsPortrait(orientation) : NO;
}

- (void)splitViewController:(UISplitViewController *)svc
     willHideViewController:(UIViewController *)aViewController
          withBarButtonItem:(UIBarButtonItem *)barButtonItem
       forPopoverController:(UIPopoverController *)pc
{
    barButtonItem.title = self.title;
    [self splitViewBarButtonItemPresenter].splitViewBarButtonItem = barButtonItem;
}

- (void)splitViewController:(UISplitViewController *)svc
     willShowViewController:(UIViewController *)aViewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    [self splitViewBarButtonItemPresenter].splitViewBarButtonItem = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return YES;
}

@end

创建一个protocol,代码如下:

#import <UIKit/UIKit.h>

@protocol SplitViewBarButtonItemPresenter <NSObject>

@property (nonatomic,strong) UIBarButtonItem *splitViewBarButtonItem;

@end

HappinessViewController .h文件代码:

#import <UIKit/UIKit.h>
#import "SplitViewBarButtonItemPresenter.h"

@interface HappinessViewController : UIViewController<SplitViewBarButtonItemPresenter>

@property (nonatomic) int happiness;  // 0 is sad; 100 is very happy

@end

HappinessViewController.m实现协议:

#import "HappinessViewController.h"
#import "FaceView.h"

@interface HappinessViewController() <FaceViewDataSource>
@property (nonatomic, weak) IBOutlet FaceView *faceView;
@property (nonatomic, weak) IBOutlet UIToolbar *toolbar;
@end

@implementation HappinessViewController

@synthesize happiness = _happiness;
@synthesize faceView = _faceView;
@synthesize splitViewBarButtonItem = _splitViewBarButtonItem;
@synthesize toolbar = _toolbar;

- (void)setSplitViewBarButtonItem:(UIBarButtonItem *)splitViewBarButtonItem
{
    if (_splitViewBarButtonItem != splitViewBarButtonItem) {
        NSMutableArray *toolbarItems = [self.toolbar.items mutableCopy];
        if (_splitViewBarButtonItem) {
            [toolbarItems removeObject:_splitViewBarButtonItem];
        }
        if (splitViewBarButtonItem) {
            [toolbarItems insertObject: splitViewBarButtonItem atIndex:0 ];
        }
        self.toolbar.items = toolbarItems;
        _splitViewBarButtonItem = splitViewBarButtonItem;
    }
}
//.......

运行结果如下所示:

posted on 2013-02-07 23:57  写下一生的程序  阅读(6180)  评论(0编辑  收藏  举报