在不使用导航控制器堆栈,子视图或模式控制器的情况下对视图控制器进行动画更改?


142

NavigationController具有ViewController堆栈来管理和有限的动画过渡。

将视图控制器作为子视图添加到现有视图控制器时,需要将事件传递给子视图控制器,这很麻烦,几乎没有烦恼,并且在实现时通常感觉很糟糕(Apple也建议不要这样做)。这样做)。

再次提供一个模态视图控制器,将一个视图控制器放在另一个之上,尽管它没有上述事件传递问题,但实际上并没有“交换”视图控制器,而是将其堆叠。

情节提要仅限于iOS 5,并且几乎是理想选择,但不能在所有项目中使用。

有人可以在没有上述限制的情况下以一种固体代码示例的方式更改视图控制器,并允许它们之间进行动画转换吗?

一个接近的示例,但没有动画: 如何在没有导航控制器的情况下使用多个iOS自定义视图控制器

编辑:导航控制器的使用很好,但是需要有动画过渡样式(不仅仅是滑动效果),显示的视图控制器需要完全交换(而不是堆叠)。如果第二个视图控制器必须从堆栈中删除另一个视图控制器,则它的封装程度不够。

编辑2:iOS 4应该是此问题的基本操作系统,我应该在提到情节提要(上)时澄清一下。


1
您可以使用导航控制器进行自定义动画过渡。如果可以接受,请从您的问题中删除该约束,然后我将发布一个代码示例。
理查德·布莱特韦尔

@Richard如果跳过了管理堆栈的麻烦,并且在视图控制器之间适应了不同的动画过渡样式,那么可以使用导航控制器!
TigerCoding

好的 我不耐烦并发布了代码。试试看。为我工作。
理查德·布莱特韦尔

@RichardBrightwell您在这里说过,可以使用导航控制器在视图控制器之间进行自定义动画过渡...怎么做?你能举个例子吗?谢谢。
鸭子2012年

Answers:


108

编辑:可以在任何方向上工作的新答案。 原始答案仅在界面为纵向时有效。这是b / c视图转换动画,它替换了一个视图w /一个不同的视图,并且必须在添加到窗口(例如window.rootViewController.view.anotherView)的第一个视图以下至少一个级别的视图下进行。

我实现了一个称为的简单容器类TransitionController。您可以在https://gist.github.com/1394947上找到它。

顺便说一句,我更喜欢在单独的类b / c中实现,它更易于重用。如果您不希望这样做,则可以直接在应用程序委托中直接实现相同的逻辑,而无需使用TransitionController类。但是,您需要的逻辑是相同的。

如下使用它:

在您的应用程序委托中

// add a property for the TransitionController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    MyViewController *vc = [[MyViewContoller alloc] init...];
    self.transitionController = [[TransitionController alloc] initWithViewController:vc];
    self.window.rootViewController = self.transitionController;
    [self.window makeKeyAndVisible];
    return YES;
}

从任何视图控制器过渡到新的视图控制器

- (IBAction)flipToView
{
    anotherViewController *vc = [[AnotherViewController alloc] init...];
    MyAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    [appDelegate.transitionController transitionToViewController:vc withOptions:UIViewAnimationOptionTransitionFlipFromRight];
}

编辑:下面的原始答案-仅适用于纵向

对于此示例,我做出了以下假设:

  1. 您已将一个视图控制器分配为rootViewController您的窗口的

  2. 当您切换到新视图时,您想用拥有新视图的viewController替换当前的viewController。在任何时候,只有当前的viewController处于活动状态(例如,已分配)。

可以轻松地修改代码以使其与众不同,关键是动画过渡和单视图控制器。确保不要将视图控制器分配给之外的任何地方window.rootViewController

用于在应用程序委托中动画化过渡的代码

- (void)transitionToViewController:(UIViewController *)viewController
                    withTransition:(UIViewAnimationOptions)transition
{
    [UIView transitionFromView:self.window.rootViewController.view
                        toView:viewController.view
                      duration:0.65f
                       options:transition
                    completion:^(BOOL finished){
                        self.window.rootViewController = viewController;
                    }];
}

在视图控制器中使用的示例

- (IBAction)flipToNextView
{
    AnotherViewController *anotherVC = [[AnotherVC alloc] init...];
    MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate;
    [appDelegate transitionToViewController:anotherVC
                             withTransition:UIViewAnimationOptionTransitionFlipFromRight];
}

1
是的,很好!它不仅可以解决问题,而且是一个非常简单干净的代码示例。非常感谢!
TigerCoding 2011年

他不是为您造成景观问题吗?另外:这会触发viewControllers的willAppear和didAppear方法吗?
Felix Lamouroux 2011年

1
我知道出现呼叫已完成,因为我已记录它们。我也看不出为什么这会影响方向变化。您能详细说明为什么会这样吗?
TigerCoding

1
@XJones很棒的解决方案,在我的iPad应用程序中运行良好,除了在首次初始化之后我面临的一个问题之外,就是transVC = [TransitionController ... initWithViewController:aUINavCtrlObj]; window.rootVC = transVC; aUINavCtrlObj中的视图从顶部填充20像素(即,底部20像素在所有方向上都超出屏幕(UIWindow)边界),但是在执行[transVC transitionToViewController:anotherVC]之后,填充消失了。我在TransitionController的loadView中尝试了wantFullScreenLayout = NO,它的作用是在statusBar下添加了一个20 px的黑色区域。
Abduliam Rehmanius,2012年

1
@AbduliamRehmanius:我有同样的问题。我通过将第25行更改为TransitionController.m来修复它UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];,但是我仅在最新版本的iOS上使用过此功能,因此请仔细测试。
菲尔·加尔文

67

您可以使用Apple的新viewController包含系统。有关更深入的信息,请查看WWDC 2011会议视频“实施UIViewController围堵”。

iOS5的新增功能,UIViewControllerContainment允许您在其中包含一个父viewController和许多子viewController。这就是UISplitViewController的工作方式。这样做可以将视图控制器堆叠在父级中,但是对于特定的应用程序,您仅使用父级来管理从一个可见viewController到另一个可见viewController的过渡。这是苹果公司认可的做事方式,从一个孩子的viewController进行动画制作就很轻松。另外,您还可以使用所有各种不同的UIViewAnimationOption过渡!

另外,使用UIViewContainment,您不必担心(除非您愿意)在定向事件期间管理子viewController的麻烦。您可以简单地使用以下命令来确保您的parentViewController将旋转事件转发到子viewControllers。

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
    return YES;
}

您可以在父级的viewDidLoad方法中执行以下操作或类似操作来设置第一个childViewController:

[self addChildViewController:self.currentViewController];
[self.view addSubview:self.currentViewController.view];
[self.currentViewController didMoveToParentViewController:self];
[self.currentViewController.swapViewControllerButton setTitle:@"Swap" forState:UIControlStateNormal];

然后,当您需要更改子viewController时,可以在父viewController中按以下方式调用:

-(void)swapViewControllers:(childViewController *)addChildViewController:aNewViewController{
     [self addChildViewController:aNewViewController];
     __weak __block ViewController *weakSelf=self;
     [self transitionFromViewController:self.currentViewController
                       toViewController:aNewViewController
                               duration:1.0
                                options:UIViewAnimationOptionTransitionCurlUp
                             animations:nil
                             completion:^(BOOL finished) {
                                   [aNewViewController didMoveToParentViewController:weakSelf];

                                   [weakSelf.currentViewController willMoveToParentViewController:nil];
                                   [weakSelf.currentViewController removeFromParentViewController];

                                   weakSelf.currentViewController=[aNewViewController autorelease];
                             }];
 }

我在这里发布了完整的示例项目:https : //github.com/toolmanGitHub/stackedViewControllers。这个另一个项目展示了如何UIViewController在某些不占用整个屏幕的输入viewController类型上使用Containment。祝好运


1
一个很好的例子。感谢您抽出宝贵的时间发布代码。另一方面,它仅限于iOS 5。正如问题中提到的那样:“ [Storyboards]限于iOS 5,几乎是理想的,但不能在所有项目中使用。” 考虑到很大一部分客户(约40%?)仍在使用iOS 4,目标是提供在iOS 4及更高版本中可以使用的功能。
TigerCoding 2011年

[self.currentViewController willMoveToParentViewController:nil];转换前不应该打来电话 吗?
Felix Lamouroux 2011年

@FelixLam-根据UIViewController包含的文档,仅当您重写addChildViewController方法时,才调用willMoveToParentViewController。在此示例中,我正在调用它,但不覆盖它。
timthetoolman 2011年

2
在WWDC的演示中,我似乎记得他们在调用开始过渡之前就已经调用了它,因为过渡并不意味着currentVC将变为nil。在使用UITabBarController的情况下,过渡不会更改任何vc的父级。从父项移除会调用didMoveToParentViewController:nil,但不会进行...调用。恕我直言
Felix Lamouroux

7

好的,我知道问题是不使用导航控制器,但没有理由不这样做。OP没有及时回复评论,我无法入睡。别对我投反对票。:)

以下是弹出当前视图控制器并使用导航控制器翻转到新视图控制器的方法:

UINavigationController *myNavigationController = self.navigationController;
[[self retain] autorelease];

[myNavigationController popViewControllerAnimated:NO];

PreferencesViewController *controller = [[PreferencesViewController alloc] initWithNibName:nil bundle:nil];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.65];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:myNavigationController.view cache:YES];
[myNavigationController pushViewController:controller animated:NO];
[UIView commitAnimations];

[controller release];

这不是堆栈视图控制器吗?
TigerCoding

1
是的,因为我们使用的是导航控制器。但是,它绕开了您可以执行哪种过渡的限制,我认为这是您问题的核心。
理查德·布莱特韦尔

关闭,但是最大的问题之一是在堆栈上要管理多个视图控制器。我正在寻找完全改变视图控制器的方法。=)
TigerCoding

啊。我可能也有类似的东西...给我一点时间。如果没有,我将删除此答案。
理查德·布莱特韦尔

好的,我将两个不同的代码拼凑在一起。我想这会做您想要的。
理查德·布莱特韦尔

3

由于我只是偶然遇到了这个确切的问题,并尝试对所有先前存在的有限成功的答案进行了变体,因此我将发布最终解决方案:

本篇文章中关于定制segues所述,制作定制segues实际上非常容易。它们也非常容易挂接到Interface Builder,它们使IB中的关系保持可见,并且不需要segue的源/目标视图控制器的大量支持。

上面链接的文章提供了iOS 4代码,以使用顶部滑动动画将新的顶部控制器替换为navigationController堆栈上的当前顶部视图控制器。

就我而言,我希望发生类似的替换任务,但要FlipFromLeft过渡。我还只需要对iOS 5+的支持。码:

从RAFlipReplaceSegue.h:

#import <UIKit/UIKit.h>

@interface RAFlipReplaceSegue : UIStoryboardSegue
@end

从RAFlipReplaceSegue.m:

#import "RAFlipReplaceSegue.h"

@implementation RAFlipReplaceSegue

-(void) perform
{
    UIViewController *destVC = self.destinationViewController;
    UIViewController *sourceVC = self.sourceViewController;
    [destVC viewWillAppear:YES];

    destVC.view.frame = sourceVC.view.frame;

    [UIView transitionFromView:sourceVC.view
                        toView:destVC.view
                      duration:0.7
                       options:UIViewAnimationOptionTransitionFlipFromLeft
                    completion:^(BOOL finished)
                    {
                        [destVC viewDidAppear:YES];

                        UINavigationController *nav = sourceVC.navigationController;
                        [nav popViewControllerAnimated:NO];
                        [nav pushViewController:destVC animated:NO];
                    }
     ];
}

@end

现在,按住Control并拖动以设置其他任何类型的segue,然后将其设置为Custom segue,然后键入custom segue类的名称,等等。


有没有办法在没有情节提要的情况下以编程方式实现此目的?
zakdances

据我所知,要使用segue,您必须对其进行定义,并在情节提要中为其指定标识符。您可以使用UIViewController的–performSegueWithIdentifier:sender:方法在代码中调用segue 。
Ryan Artecona 2013年

1
您永远不应直接调用viewDidXXX和viewWillXXX。
本·辛克莱

2

我为此苦苦挣扎了很长时间,这里列出了我的一个问题,我不确定您是否遇到了这个问题。但是,如果它必须与iOS 4配合使用,我会建议您这样做。

首先,创建一个新NavigationController类。这是我们将完成所有肮脏工作的地方-其他类将能够“干净地”调用诸如此类的实例方法pushViewController:。在您的.h

@interface NavigationController : UIViewController {
    NSMutableArray *childViewControllers;
    UIViewController *currentViewController;
}

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion;
- (void)addChildViewController:(UIViewController *)childController;
- (void)removeChildViewController:(UIViewController *)childController;

子视图控制器数组将用作堆栈中所有视图控制器的存储。我们将自动将所有旋转和调整大小的代码从NavigationController的视图转发到currentController

现在,在我们的实现中:

- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL))completion
{
    currentViewController = [toViewController retain];
    // Put any auto- and manual-resizing handling code here

    [UIView animateWithDuration:duration animations:animations completion:completion];

    [fromViewController.view removeFromSuperview];
}

- (void)addChildViewController:(UIViewController *)childController {
    [childViewControllers addObject:childController];
}

- (void)removeChildViewController:(UIViewController *)childController {
    [childViewControllers removeObject:childController];
}

现在,您可以实现自己的自定义pushViewController:popViewController并且这样的,使用这些方法调用。

祝您好运,希望对您有所帮助!


同样,我们必须求助于将视图控制器添加为现有视图控制器的子视图。当然,这是现有导航控制器的功能,但这意味着我们基本上必须重写它及其所有方法。实际上,我们应该避免不得不分发viewWillAppear和类似的方法。我开始认为没有干净的方法可以做到这一点。但是,我感谢您花费时间和精力!
TigerCoding 2011年

我认为,有了这个根据需要添加和删除视图控制器的系统,该解决方案可以避免您转发这些方法。
aopsfan 2011年

你确定吗?我以前曾经以类似的方式交换视图控制器,而且我总是不得不转发消息。可以确认吗?
TigerCoding 2011年

不,我不确定,但是我会假定,只要您删除视图控制器消失的视图并按其出现时添加它们,就应该自动触发viewWillAppearviewDidAppear等等。
aopsfan

-1
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UINavigationController *viewController = (UINavigationController *)[storyboard instantiateViewControllerWithIdentifier:@"storyBoardIdentifier"];
viewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentViewController:viewController animated:YES completion:nil];

尝试此代码。


此代码提供了从视图控制器到具有导航控制器的另一个视图控制器的过渡。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.