iOS 5 故事板进阶(3)

来源:互联网 发布:淘宝开网店怎么找货源 编辑:程序博客网 时间:2024/04/30 00:30

自定义 Segue

你已经看过两种类型的 segue 了:Modal 和 Push。这对大部分程序来说足够了,但有时候你可能感到腻味。幸运的是,你可以创建自己的 segue 动画来稍微调剂一下。

让我们来试试如何将从 Gestures 到 Ranking 屏的转换动画定义为自己的动画效果。选中 BestPlayers segue,将它的 Style 设置为Custom。接下来会让你输入 segue 类名,输入“SuperCoolSegue”。同样,在 WorstPlayers segue 上也做一遍。

创建自己的 segue,需要子类化 UIStoryboardSegue 类。添加新的O-C 类,命名为 SuperCoolSegue,继承 UIStoryboardSegue。

在这个类中,我们需要做的就是实现一个 perform 方法:

@implementationSuperCoolSegue

- (void)perform

{
[self.sourceViewController presentViewController:self.

destinationViewControlleranimated:NO completion:nil];

}

@end

在这个方法中我们在源controller 上呈现了目标 ViewController(模式化窗体),不使用任何动画过渡。过去,我们可以使用presentModalViewController:animated:方法,但 iOS5 以后,最好是使用新方法呈现 ViewController。

运行 app。当你在屏幕上划动手势,Ranking 窗口不经任何动画过渡直接出现了。整个过程是如此突兀,我们应该给它加上一点动画效果。

修改 SuperCoolSegue.m为:

#import<QuartzCore/QuartzCore.h>

#import"SuperCoolSegue.h" @implementation SuperCoolSegue

- (void)perform

{
UIViewController*source = self.sourceViewController;

UIViewController *destination= self.destinationViewController;

//  以目标窗口创建 Image

UIGraphicsBeginImageContext(destination.view.bounds.size);

[destination.view.layer renderInContext:

UIGraphicsGetCurrentContext()];

UIImage *destinationImage=

UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

//  将 Image 加到 tab barcontroller 的 subview

UIImageView *destinationImageView=
[[UIImageView alloc]initWithImage:destinationImage];

[source.parentViewController.viewaddSubview:

destinationImageView];

// 缩小 Image并旋转 180 度 (颠倒)

CGAffineTransform scaleTransform= CGAffineTransformMakeScale(0.1, 0.1);

CGAffineTransform rotateTransform= CGAffineTransformMakeRotation(M_PI);

destinationImageView.transform= CGAffineTransformConcat(scaleTransform,rotateTransform);

// image 移到屏幕之外

CGPoint oldCenter= destinationImageView.center;

CGPoint newCenter= CGPointMake(oldCenter.x -

destinationImageView.bounds.size.width,oldCenter.y);

destinationImageView.center= newCenter;

// 启动动画

[UIView animateWithDuration:0.5fdelay:0 options:UIViewAnimationOptionCurveEaseOut

animations:^(void)

{
destinationImageView.transform = CGAffineTransformIdentity;

destinationImageView.center = oldCenter;

}
completion: ^(BOOLdone)

{

// 移除Image,我们用不到了

[destinationImageView removeFromSuperview];

// 呈现新窗口

[source presentViewController:destinationanimated:NO completion:nil];

}];

}

@end

在开始动画之前,先为新的 ViewController 做一份屏幕拷贝,得到一个UIImage,然后用这个 UIImage 进行动画。也可以直接用 view 进行动画,但效率低而且不一定达到你想要的效果。像 navigationcontroller 这样的控制器在进行这样的操作时不太容易。

我们将 UIImage 添加到 TabBarController 的 subview,将它绘制在视图的最上层。在开始动画之前,UIImage是以缩小和翻转的形式位于屏幕可视区域之外。

动画完成之后,我们会移除 UIImage 并呈现目标ViewController。UIImage 形成的动画和真正的 view 在用户看来天衣无缝,因为二者的显示内容是完全相同的。

顺便多说两句。如果你觉得这个动画效果还不够炫,你完全可以自己换一个。看看你可以使用什么效果……这会是一个有趣的工作。如果你还想对源ViewController 进行动画,我还是建议你从它获得一个 UIImage 来进行。

在你关闭 Ranking 界面时,会显示一个从上滑到底部的默认动画。你仍然可以定制这个动画,但这不属于segue 的一部分,而是由委托对象来负责,上面的原则仍然适用。将 animated 参数设置为 NO,然后定义你自己的动画。

故事板和 iPad

我们打算做一个 universal 版本,以便我们的 app 支持 iPad。

打开 Target 的 Summary 页,在 iOSApplication Target 下找到 Devices 一项,将它修改为 Universal。这将新增一个 iPad Deployment Info 小节。

在项目中新建一个 Storyboard 文件。选择 New File,选择User Interface 中的 Storyboard 模板,Device Family 设置为 iPad。保存文件为 en.lproj 目录下的MainStoryboard~ipad.storyboard。

打开新建的故事板文件,拖一个 ViewController 到上面。你会看到一个与iPad 屏幕尺寸同样的 View Controller。拖一个 Label 到上面,随便输入一些文字,例如 testing。

返回 Target 设置窗口,在 Summary 的 iPadDeployment Info 小节,选择 MainStoryboard~ipad 作为 Main Storyboard。

然后修改 AppDelegate.m:

- (BOOL)application:(UIApplication*)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{
players =[NSMutableArray arrayWithCapacity:20]; // ...existingcode ...

if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {

UITabBarController *tabBarController =

(UITabBarController *)self.window.rootViewController;

UINavigationController *navigationController =

[[tabBarController viewControllers] objectAtIndex:0];

PlayersViewController *playersViewController =

[[navigationControllerviewControllers] objectAtIndex:0];

playersViewController.players = players;

GesturesViewController*gesturesViewController = [[tabBarController viewControllers] objectAtIndex:1];

gesturesViewController.players = players;

}

return YES;

}

我们对程序是否运行在 iPad 上进行了判断,但我们还没有做任何特殊的事情,现在iPad 故事板中还没有 Players 和 Gestures 这两个 ViewController。

在 iPad 模拟器上运行程序。现在看见的不再是原来的那个 tab bar窗口了,而是一个包含了 testing 字样的空白窗口。

iPad 版成功地加载了属于它的故事板。

iPad 的故事板除了比较大些外,其它和 iPhone的并没有太多不同。iPad中还有两种 segue 可选:Popover 和 Replace。

删除 iPad 版中的 ViewController,然后拖入一个Split View Controller 到画布中。这个 Split View Controller 又会附带另外 3 个 ViewController……你该换一台大点的显示器!

默认的 Split View Controller 是竖向的,当你将它的 Oritentation(在Simulated Metrics下面)设置为横向,就可以看到 iPad 屏幕被分为左右两栏。

在这些 ViewController 之间的由箭头来表示相互关系。类似于导航控制器和TabBar 控制器,Split View 控制器也是一种 ViewController 容器。在程序运行时,iPhone 一次只能看到一个ViewController,但 iPad 可以同时看到多个 ViewController,例如 Split View 控制器的主/细窗口。

现在运行程序,它还会有些问题。当你翻转屏幕(或者模拟器),仍然只能看到一个不会旋转的空白窗口。在项目中添加新的UIViewController 子类 DetailViewController,用于作为SplitViewController 中位于右侧的较大的窗口。

编辑 DetailViewController.h
@interface DetailViewController: UIViewController

<UISplitViewControllerDelegate>
@property (nonatomic,strong) IBOutletUIToolbar *toolbar;

@end

DetailViewController 类实现了UISplitViewControllerDelegate 委托协议,这样当屏幕发生旋转时就会通知它。

编辑 DetailViewController.m如下:

 #import "DetailViewController.h"

@implementationDetailViewController

{
UIPopoverController*masterPopoverController;

}
@synthesize toolbar;

-  (void)viewDidUnload {
[super viewDidUnload];

self.toolbar= nil;

 }

-  (BOOL)shouldAutorotateToInterfaceOrientation:

(UIInterfaceOrientation)interfaceOrientation

{

return YES;

}

#pragmamark - UISplitViewControllerDelegate

- (void)splitViewController: (UISplitViewController *)splitViewController

willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem

forPopoverController:(UIPopoverController *)popoverController

{
barButtonItem.title= @"Master";
NSMutableArray*items = [[self.toolbar items] mutableCopy];

[items insertObject:barButtonItematIndex:0];
[self.toolbar setItems:itemsanimated:YES];

masterPopoverController =popoverController;

}

- (void)splitViewController:(UISplitViewController*)splitController willShowViewController:(UIViewController*)viewController

invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem

{
NSMutableArray*items = [[self.toolbar items] mutableCopy];

[items removeObject:barButtonItem];
[self.toolbar setItems:itemsanimated:YES];

masterPopoverController= nil;

}

@end

这是为了支持 SplitViewController 所需要做的最少工作。

在故事板编辑器中,设置较大的窗口的类为DetailViewController。拖一个 ToolBar到这个窗口上。将ToolBar 上默认的那个 Bar Button Item 标题(即Item)设置为 Menu(后面我们将为它加上一个 popover),在 Menu 前面插入一个 Flexible Space。

将 Toolbar 连接到 DetailViewController 的toolbar 属性。设置 toolbar的 autosizing 属性如下:

默认 toolbar 是“粘”在屏幕底部,但我们想让它任何时候都居于屏幕顶部——不然当屏幕旋转时它的位置就不对了。

接下来,我们必须为“Master”窗口(就是嵌在 NavigationController中的 TableViewController)也创建一个 ViewController,也就是 split-view 左边的窗口。这样做的唯一目的是为了我们可以覆盖shouldAutorotateToInterfaceOrientation 方法并在里面返回 YES。在 iPad 版本中,所有可视的 ViewController都必须开启旋屏否则程序的旋屏就会有问题。

新建 UITableViewController 子类MasterViewController。如果勾选了 Targeted for iPad 选项,则它的shouldAutorotateToInterfaceOrientation 方法将被自动实现。

打开 MainStoryboard~ipad.storyboard,选择名为 Root View Controller,将 class 设置为 MasterViewController。Xcode会显示缺少数据源方法的警告,暂时不用理会它。

还有一点。DetailViewController 类是 SplitView Controller 的委托,但我们还未为它们创建委托连接。很不幸,你无法直接在故事板中创建这类连接。我们不得不在 AppDelegate 中写一些代码。

打开  AppDelegate.m,修改 didFinishLaunchingWithOptions 方法:

- (BOOL)application:(UIApplication*)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

 

// ...existing code ...

if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {

// ...existing code...

} else {
UISplitViewController *splitViewController=

(UISplitViewController *)self.window.rootViewController;

splitViewController.delegate = [splitViewController.

viewControllerslastObject];

return YES;

}

}

运行程序,你已经拥有一个完整的 Split View Controller了。

Xcode 自带一个 Master-Detail Application 模板,但我们想让你知道如何从零开始搭建一个主细窗口的应用。

可以用故事板编辑器设置 Master 窗口的大小。在 SpliteView Controller 的属性模板中设置 popover size 即可。

也可以直接创建 popover。在故事板中添加一个新的 ViewController,然后用一个Popover Segue 指向它。

向画布中拖入一个 ViewController。这将用于作为popover 的 contentView。它稍微显得大了点,你可以将它的 size 由 Inferred 改为 Freeform,同时将状态栏占据的空间移除。

现在可以在 Size 模板中修改它的大小。改成 400x400 即可。为了清晰地看见popover,我把它的背景色改成了 Scroll View Textured Background。

用右键从 Menu 按钮拖一条线到新的 ViewController 上并选择Segue 类型为 Popover。命名 segue 为 ShowPopover。注意,对于 Popover Segue,有几个属性是专用于UIPopoverController 。

运行 app,你可以看到 popover 工作正常。很简单,是吧……

负责呈现 popover 的 segue 是 UIStoryboardPopoverSegue,它是UIStoryboardSeque 的子类。它在 segue 的基础上增加了一个 popoverController 属性,用于指向一个UIPopoverController。这样,我们就可以通过 popoverController 属性来解散它。在DetailViewController.h 中声明对 UIPopoverControllerDelegate 协议的实现:

@interfaceDetailViewController : UIViewController<UISplitViewControllerDelegate, UIPopoverControllerDelegate>

DetailViewController.m 中加一个新的实例变量:

@implementationDetailViewController

{
UIPopoverController*masterPopoverController;

UIPopoverController *menuPopoverController;

}

接着是你早已熟悉的 prepareForSegue方法:

- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender

{
if ([segue.identifier isEqualToString:@"ShowPopover"])

{
menuPopoverController =

((UIStoryboardPopoverSegue *)segue).popoverController;

menuPopoverController.delegate= self;

}

}

我们将 segue 的 popoverController 属性保存在我们自己的menuPopoverController 变量中,然后让 self 作为这个 popoverController 的委托。

实现委托方法:

#pragmamark - UIPopoverControllerDelegate

- (void)popoverControllerDidDismissPopover:

(UIPopoverController *)thePopoverController

{
menuPopoverController.delegate = nil;

menuPopoverController =nil;

}

在 popover 被解散后,简单地将实例变量设置为 nil。

在我们保存了 segue 的 popoverController 属性后,我们可以在屏幕旋转时解散popover:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) toInterfaceOrientationduration:(NSTimeInterval)duration

{
if (menuPopoverController != nil&&

menuPopoverController.popoverVisible) {

[menuPopoverController dismissPopoverAnimated:YES];

menuPopoverController= nil;

}

}

运行程序,查看效果。

你可能会发现一个小问题:每当你点击 Menu 按钮,一个 popover 会弹出,但之前打开的popover 并没有被关闭。反复点击 Menu 按钮,终将占满整个 popover 栈。这个问题很烦人(可能导致你的 app 被拒绝),但幸运的是:解决的方法倒也简单。

segue 一旦执行就不可能取消它,因此我们不能用取消 segue 的方法。由于popover 弹出时,我们都保存了一个它的引用,因此我们可以在 prepareForSegue 中这样做:

- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender

{
if ([segue.identifier isEqualToString:@"ShowPopover"])

{
if (menuPopoverController!= nil &&

menuPopoverController.popoverVisible) {

[menuPopoverControllerdismissPopoverAnimated:NO];

}

menuPopoverController=
((UIStoryboardPopoverSegue *)segue).popoverController;

menuPopoverController.delegate = self;

}

}



原创粉丝点击