iOS应用开发视频教程笔记(七)iPad Apps
这节课的主要内容包括iPad、Split Views、Popovers及做一个universal的应用并在两种设备上运行。
UIToolbar
通常在屏幕的顶部或底部,长得像钢筋一样。一个工具栏,是一个UIBarButtonItems的集合。UIBarButtonItems不是按钮,有点像是简化过的按钮。UINavigationController有一个在底部的工具栏,可以通过在xcode里inspect Navigation Controller中的Shows Toolbar小开关打开,但工具栏上的按钮和Navigation Controller本身没有任何关系,它只和当时显示的ViewController有关。
这节课的主要内容包括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; } } //.......
运行结果如下所示: