在没有 UINavigationController 的情况下推送 Segue 动画

Posted

技术标签:

【中文标题】在没有 UINavigationController 的情况下推送 Segue 动画【英文标题】:Push Segue animation without UINavigationController 【发布时间】:2013-06-29 00:21:49 【问题描述】:

我正在尝试使用自定义 segue 在 UIViewControllers 之间添加一个简单的推送动画。

(没有将我的控制器转换为使用 UINavigationController)

到目前为止找到的答案适用于导航控制器,但在不使用导航控制器时会失败。 (我仍在阅读并尝试在堆栈溢出中看到的其他答案)

感谢 2012 年 7 月 5 日 (ZHCustomSegue.m) Zakir,我的自定义 segue .m

#import "SFCustomSegue.h"
#import "QuartzCore/QuartzCore.h"

@implementation SFCustomSegue


-(void)perform 

UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];

CATransition* transition = [CATransition animation];
transition.duration = 4.0;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;

[sourceViewController.view.layer addAnimation:transition forKey:kCATransition];

[sourceViewController presentViewController:[self destinationViewController] animated:NO completion:nil];


如果我按照 Zakir 的原始示例使用导航控制器并将最后两行替换为:

    [sourceViewController.navigationController.view.layer addAnimation:transition
                                                            forKey:kCATransition];

[sourceViewController.navigationController pushViewController:destinationController animated:NO];    

它有效....

Apple 的文档说(注意不是导航控制器,而是使用模态 segue):

- (void)perform

// Add your own animation code here.

[[self sourceViewController] presentModalViewController:[self destinationViewController] animated:NO];

不工作,我没有动画。在 Zakir 的代码中,使用 4.0 秒需要 4.0 秒,我得到了我想要的动画。当然,如果我使用导航控制器,则默认是推送转换。在这段代码中,我没有动画,它需要 0 秒。

有没有人使用 UIViewControllers 获得自定义 segue 动画?

【问题讨论】:

【参考方案1】:

由于您正在调用presentViewController,因此源视图控制器一直存在。如果你真的想用目标视图控制器替换源视图控制器,以便释放源,你可以这样做:

static UIImageView *screenShotOfView(UIView *view)

    // Create a snapshot for animation
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [view.layer renderInContext:context];
    UIImageView *screenShot = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];
    UIGraphicsEndImageContext();
    return screenShot;


- (void)perform

    UIViewController *source = (UIViewController *) self.sourceViewController;
    UIViewController *destination = (UIViewController *) self.destinationViewController;

    // Swap the snapshot out for the source view controller
    UIWindow *window = source.view.window;
    UIImageView *screenShot = screenShotOfView(source.view);
    CGRect originalFrame = destination.view.frame;

    BOOL animsEnabled = [UIView areAnimationsEnabled];
    [UIView setAnimationsEnabled:NO];
    
        window.rootViewController = destination;
        [window addSubview:screenShot];
        [source.view removeFromSuperview];
        CGRect frame = destination.view.frame;
        frame.origin.x += source.view.bounds.size.width;
        destination.view.frame = frame;
    
    [UIView setAnimationsEnabled:animsEnabled];

    [UIView animateWithDuration:kAnimationDuration
                     animations:^
                         destination.view.frame = originalFrame;
                         CGRect frame = screenShot.frame;
                         frame.origin.x -= screenShot.bounds.size.width;
                         screenShot.frame = frame;

                     
                     completion:^(BOOL finished) 
                         [screenShot removeFromSuperview];
                     ];

【讨论】:

【参考方案2】:

看来我上面的代码“几乎”可以工作,但并不完全正常。 手头的主要问题可能是何时调用最后一步。

我的工作代码如下所示:

#import "SFCustomSegue.h"
#import "QuartzCore/QuartzCore.h"

@implementation SFCustomSegue

static const float delay = 0.5f;

-(void)perform 

    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];

    [sourceViewController.view addSubview:destinationController.view];

    CATransition* transition = [CATransition animation];
    transition.duration = delay;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromRight;

    [sourceViewController.view.layer addAnimation:transition forKey:kCATransition];

    [self performSelector:@selector(performDone:) withObject:destinationController afterDelay:delay];


- (void)performDone:(id)viewController
    UIViewController *destination = (UIViewController*)viewController;
    [destination.view removeFromSuperview];
    [[self sourceViewController] presentViewController:destination animated:NO completion:nil];



@end

解决这个问题的关键是添加过渡视图(第 13 行),然后删除它(第 28 行)并使用 presentViewController:animated:completion: 将其添加回来(第 29 行)。

我仍然“卡在”只使用常规 CATransition 转换类型,但现在这对我来说已经足够了,因为我想使用 kCATransitionPush

【讨论】:

如果有人可以将其改写为使用完成块,我希望更好,但这可行。【参考方案3】:

我已经合并了两个答案的想法以获得此代码(运行良好): (例如,上面的答案不计入设备的方向,因此它们不能在横向上工作)

(带有 CATransitions 的变体不合适,因为两个视图之间存在间隙,而一个视图推动另一个视图)

#import "PushSegue.h"

/*
@interface TransitionDelegate : NSObject
- (id) initWithScreenshot: (UIView*) screenshot;
@end
*/

@implementation PushSegue
- (void) perform

    UIViewController* source = (UIViewController*) self.sourceViewController;
    UIViewController* destination = (UIViewController*) self.destinationViewController;

    const BOOL iPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
    float animationDuration = iPad ? 0.5 : 0.3;

    // Swap the snapshot out for the source view controller
    UIWindow* window = source.view.window;
    UIImageView* screenShot = screenShotOfView(source.view);

    // accord to device orientation

    float rotation;
    //NSString* transitionType;

    CGRect originalFrame = destination.view.frame;
    CGRect destinationFrame = destination.view.frame;

    switch ([UIApplication sharedApplication].statusBarOrientation)
    
        case UIInterfaceOrientationPortraitUpsideDown:
            rotation = M_PI;
            destinationFrame.origin.x -= source.view.bounds.size.width;
            //transitionType = kCATransitionFromLeft;
            break;

        case UIInterfaceOrientationLandscapeLeft:
            rotation = M_PI + M_PI_2;
            destinationFrame.origin.y -= source.view.bounds.size.width;
            //transitionType = kCATransitionFromBottom;
            break;

        case UIInterfaceOrientationLandscapeRight:
            rotation = M_PI_2;
            destinationFrame.origin.y += source.view.bounds.size.width;
            //transitionType = kCATransitionFromTop;
            break;

        default:
            rotation = 0;
            destinationFrame.origin.x += source.view.bounds.size.width;
            //transitionType = kCATransitionFromRight;
            break;
    

    screenShot.transform = CGAffineTransformMakeRotation(rotation);

    // reposition after rotation
    CGRect screenshotFrame = screenShot.frame;
    screenshotFrame.origin.x = 0;
    screenshotFrame.origin.y = 0;
    screenShot.frame = screenshotFrame;

    switch ([UIApplication sharedApplication].statusBarOrientation)
    
        case UIInterfaceOrientationPortraitUpsideDown:
            screenshotFrame.origin.x += screenShot.bounds.size.width;
            break;

        case UIInterfaceOrientationLandscapeLeft:
            screenshotFrame.origin.y += screenShot.bounds.size.width;
            break;

        case UIInterfaceOrientationLandscapeRight:
            screenshotFrame.origin.y -= screenShot.bounds.size.width;
            break;

        default:
            screenshotFrame.origin.x -= screenShot.bounds.size.width;
            break;
    

    // swap the view with its screenshot
    window.rootViewController = destination;
    [window addSubview:screenShot];
    [source.view removeFromSuperview];

    /*
    CATransition* transition = [CATransition animation];

    transition.duration = animationDuration;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush;
    transition.subtype = transitionType;
    transition.delegate = [[TransitionDelegate alloc] initWithScreenshot:screenShot];

    [window addSubview:destination.view];

    [screenShot.window.layer addAnimation:transition forKey:nil];
    */

    const BOOL animationsEnabled = [UIView areAnimationsEnabled];
    [UIView setAnimationsEnabled:NO];
    
        destination.view.frame = destinationFrame;
    
    [UIView setAnimationsEnabled:animationsEnabled];

    [UIView animateWithDuration:animationDuration
                     animations:^
    
        destination.view.frame = originalFrame;
        screenShot.frame = screenshotFrame;
    
                     completion:^(BOOL finished)
    
        [screenShot removeFromSuperview];
    ];

    /*
    const BOOL animationsEnabled = [UIView areAnimationsEnabled];
    [UIView setAnimationsEnabled:NO];
    
        CGRect frame = destination.view.frame;
        frame.origin.x += source.view.bounds.size.width;
        destination.view.frame = frame;
    
    [UIView setAnimationsEnabled:animationsEnabled];

    [UIView animateWithDuration:animationDuration
                     animations:^
    
        destination.view.frame = originalFrame;
        CGRect frame = screenShot.frame;
        frame.origin.x -= screenShot.bounds.size.width;
        screenShot.frame = frame;
    
    completion:^(BOOL finished)
    
        [screenShot removeFromSuperview];
    ];
    */

    /*
    CATransition* transition = [CATransition animation];

    transition.duration = animationDuration;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush;

    switch ([UIApplication sharedApplication].statusBarOrientation)
    
        case UIInterfaceOrientationPortraitUpsideDown:
            transition.subtype = kCATransitionFromLeft;
            break;

        case UIInterfaceOrientationLandscapeLeft:
            transition.subtype = kCATransitionFromBottom;
            break;

        case UIInterfaceOrientationLandscapeRight:
            transition.subtype = kCATransitionFromTop;
            break;

        default:
            transition.subtype = kCATransitionFromRight;
            break;
    

    [source.view.window.layer addAnimation:transition forKey:nil];

    [source presentViewController:destination animated:NO completion:nil];
    */


static UIImageView* screenShotOfView(UIView* view)

    // Create a snapshot for animation
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    [view.layer renderInContext:context];
    UIImageView* screenShot = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];

    UIGraphicsEndImageContext();

    return screenShot;

@end

/*
@implementation TransitionDelegate

    UIView* screenshot;


- (id) initWithScreenshot: (UIView*) screenshot

    if (self = [super init])
    
        self->screenshot = screenshot;
    
    return self;


- (void) animationDidStop: (CAAnimation*) theAnimation
                 finished: (BOOL) flag

    [screenshot removeFromSuperview];

@end
*/

【讨论】:

以上是关于在没有 UINavigationController 的情况下推送 Segue 动画的主要内容,如果未能解决你的问题,请参考以下文章

将 managedObjectContext 发送到 viewController 崩溃

如何正确关闭作为模式呈现的 UINavigationController?

ID:[...] 的 NSManagedObject 已失效

使用 UINavigationController 从另一个控制器更新或重新加载 UIViewController?

为啥我似乎必须在 iOS 应用程序委托中键入 window.rootViewController?

UINavigationController - 何时释放推送的视图控制器等