在没有 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?