如何在基于导航的应用程序中更改推送和弹出动画
Posted
技术标签:
【中文标题】如何在基于导航的应用程序中更改推送和弹出动画【英文标题】:How to change the Push and Pop animations in a navigation based app 【发布时间】:2011-01-14 00:18:55 【问题描述】:我有一个基于导航的应用程序,我想更改推送和弹出动画的动画。我该怎么做?
编辑 2018
这个问题已经有很多答案了,现在已经有一段时间了,我重新选择了我认为现在最相关的答案。如果有人不这么认为,请在 cmets 中告诉我
【问题讨论】:
从 ios 7 开始,有官方 API;请参阅UINavigationControllerDelegate 的自定义过渡动画支持。还有一个关于这个的WWDC 2013 Video。 我已经为在 Swift 中执行此操作添加了一个答案(如下) - 我遇到了这个问题,询问有关 Swift 实现的问题,所以我想我会加入我的后续实现。 有关官方(iOS 7+)API 的非常好的教程,请参阅:bradbambara.wordpress.com/2014/04/11/… @JesseRusak 更新了 WWDC 2013 视频链接:developer.apple.com/videos/play/wwdc2013-218 改变了我接受的答案,伙计们。希望这可以帮助! GLHF 【参考方案1】:我做了以下,效果很好..简单易懂..
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];
push 也是一样。
Swift 3.0 版本:
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)
【讨论】:
+1,这确实是最明智的解决方案。对未来的访客来说只是一个小提示:Animated:NO
部分至关重要。如果 YES
被传递,动画会混合并产生有趣的效果。
目前为止最好的解决方案。对于初学者,不要忘记包含 QuartCore (#import setViewControllers:
【参考方案2】:
这就是我一直设法完成这项任务的方式。
对于推送:
MainView *nextView=[[MainView alloc] init];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];
对于流行音乐:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];
我仍然从中得到很多反馈,所以我将继续更新它以使用动画块,这是 Apple 推荐的动画制作方式。 对于推送:
MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
animations:^
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
];
对于流行音乐:
[UIView animateWithDuration:0.75
animations:^
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
];
[self.navigationController popViewControllerAnimated:NO];
【讨论】:
谢谢。但是弹出是由 UINavigationController 自动完成的。如何覆盖该行为以便调用自定义弹出逻辑? @stuckj 实际上它确实有效!!!您只需将super
替换为self.navigationController
有什么方法可以从左侧获取幻灯片而不是从右侧获取默认幻灯片?
第一个根本不显示新视图。第二个不显示动画。非常糟糕的答案! iOS 7。
为什么你给UIViewController
子类一个没有“ViewController”部分的名字。这个名字更适合 UIView。【参考方案3】:
如何在基于导航的应用中更改 Push 和 Pop 动画...
序言:
假设您是 iOS 开发新手。令人困惑的是,Apple 提供了两种可以轻松使用的过渡。它们是:“crossfade”和“flip”。
当然,“crossfade”和“flip”是没用的。它们从不使用。没有人知道为什么 Apple 提供了这两个无用的过渡!
所以:
假设你想做一个普通的、常见的、过渡,比如“幻灯片”。在这种情况下,你必须做大量的工作!。
这篇文章解释了这项工作。
重复一遍:
令人惊讶的是:在 iOS 中,如果您想要最简单、最常见的日常过渡(例如普通幻灯片),您确实需要所有的工作来实现完全自定义过渡。
这是怎么做的......
1。您需要自定义UIViewControllerAnimatedTransitioning
您需要一个自己的布尔值,例如 popStyle
。 (是弹出还是弹出?)
您必须包含transitionDuration
(琐碎)和主要调用animateTransition
事实上,您必须为animateTransition
内部编写两个不同的例程。一个用于推送,一个用于流行。可能将它们命名为animatePush
和animatePop
。在animateTransition
内部,只需在popStyle
上分支到两个例程
下面的例子做了一个简单的移动/移动
在您的 animatePush
和 animatePop
例程中。您必须获得“视图”和“视图”。 (如何做到这一点,在代码示例中显示。)
您必须 addSubview
获得新的“to”视图。
而你必须在动画结束时致电completeTransition
所以..
class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning
var popStyle: Bool = false
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
return 0.20
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
if popStyle
animatePop(using: transitionContext)
return
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.finalFrame(for: tz)
let fOff = f.offsetBy(dx: f.width, dy: 55)
tz.view.frame = fOff
transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations:
tz.view.frame = f
, completion: _ in
transitionContext.completeTransition(true)
)
func animatePop(using transitionContext: UIViewControllerContextTransitioning)
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.initialFrame(for: fz)
let fOffPop = f.offsetBy(dx: f.width, dy: 55)
transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations:
fz.view.frame = fOffPop
, completion: _ in
transitionContext.completeTransition(true)
)
然后……
2。在您的视图控制器中使用它。
注意:奇怪的是,您只需在“第一个”视图控制器中执行此操作。 (“下面”的那个。)
对于您在 top 上弹出的那个,什么都不做。很简单。
所以你的班级...
class SomeScreen: UIViewController
变成……
class FrontScreen: UIViewController,
UIViewControllerTransitioningDelegate, UINavigationControllerDelegate
let simpleOver = SimpleOver()
override func viewDidLoad()
super.viewDidLoad()
navigationController?.delegate = self
func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
simpleOver.popStyle = (operation == .pop)
return simpleOver
就是这样。
完全像往常一样推送和弹出,没有变化。推...
let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
.instantiateViewController(withIdentifier: "nextScreenStoryboardID")
as! NextScreen
navigationController?.pushViewController(n, animated: true)
要弹出它,您可以在下一个屏幕上执行此操作:
class NextScreen: TotallyOrdinaryUIViewController
@IBAction func userClickedBackOrDismissOrSomethingLikeThat()
navigationController?.popViewController(animated: true)
呼。
3。还可以享受此页面上的其他答案,这些答案解释了如何覆盖 AnimatedTransitioning!
滚动至@AlanZeino 和@elias 答案,了解更多关于这些天如何在 iOS 应用中AnimatedTransitioning
的讨论!
【讨论】:
太棒了!如果我希望导航向后滑动手势也支持相同的 AnimatedTransitioning。有什么想法吗? 感谢@samchiwen - 实际上这正是animatePush
和animatePop
的含义......两个不同的方向!
这个回答伤了我的眼睛
当我弹出导航控制器时,屏幕变黑。在调试视图中只有转换视图:(
这可以用来创建像我在这个问题中想要的汉堡菜单效果:***.com/questions/65869373/…【参考方案4】:
推送
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];
流行音乐
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];
【讨论】:
【参考方案5】:请记住,在 Swift 中,extension 绝对是你的朋友!
public extension UINavigationController
/**
Pop current view controller to previous view controller.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3)
self.addTransition(transitionType: type, duration: duration)
self.popViewControllerAnimated(false)
/**
Push a new view controller on the view controllers's stack.
- parameter vc: view controller to push.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3)
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3)
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = type
self.view.layer.addAnimation(transition, forKey: nil)
【讨论】:
【参考方案6】:@Magnus 回答,仅适用于 Swift (2.0)
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
self.navigationController?.pushViewController(writeView, animated: false)
一些旁注:
您也可以使用 Segue 执行此操作,只需在 prepareForSegue
或 shouldPerformSegueWithIdentifier
中实现即可。 但是,这也会保留默认动画。要解决此问题,您必须转到情节提要,单击 Segue,然后取消选中“动画”框。但这会限制您的应用程序适用于 IOS 9.0 及更高版本(至少当我在 Xcode 7 中执行此操作时)。
在 segue 中执行时,最后两行应替换为:
self.navigationController?.popViewControllerAnimated(false)
即使我设置为 false,它也会忽略它。
【讨论】:
如何在动画结束时去除背景中的黑色。 不适用于推送视图控制器动画适用于弹出视图控制器【参考方案7】:使用私人电话是个坏主意,因为 Apple 不再批准这样做的应用程序。 也许你可以试试这个:
//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];
//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];
[self.navigationController pushViewController:myVC animated:NO];
[myVC release];
//Start Animation
[UIView commitAnimations];
【讨论】:
它只有“一半”的作用——它不能解决流行动画的难题。 我更喜欢这个解决方案,是的,它有效。使用私有方法肯定会被拒绝。 @nicktmro 这是私有 api 调用。我没有注意到。 @Franklin 前段时间在这里讨论过使用-pushViewController:transition:forceImmediate:
,这将是一个坏主意。【参考方案8】:
由于这是 Google 上的最高结果,我想我会分享我认为最理智的方式;这是使用 iOS 7+ 转换 API。我用 Swift 3 为 iOS 10 实现了这个。
如果您创建UINavigationController
的子类并返回符合UIViewControllerAnimatedTransitioning
协议的类的实例,那么将其与UINavigationController
在两个视图控制器之间的动画结合起来非常简单。
例如这里是我的UINavigationController
子类:
class NavigationController: UINavigationController
init()
super.init(nibName: nil, bundle: nil)
delegate = self
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
extension NavigationController: UINavigationControllerDelegate
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
return NavigationControllerAnimation(operation: operation)
您可以看到我将UINavigationControllerDelegate
设置为自身,并在我的子类的扩展中实现UINavigationControllerDelegate
中的方法,该方法允许您返回自定义动画控制器(即NavigationControllerAnimation
)。此自定义动画控制器将为您替换库存动画。
您可能想知道为什么我通过其初始化程序将操作传递给NavigationControllerAnimation
实例。我这样做是为了在NavigationControllerAnimation
的UIViewControllerAnimatedTransitioning
协议的实现中我知道操作是什么(即“推送”或“弹出”)。这有助于了解我应该做什么样的动画。大多数时候,您希望根据操作执行不同的动画。
其余的都很标准。在UIViewControllerAnimatedTransitioning
协议中实现两个必需的功能,并随心所欲地制作动画:
class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning
let operation: UINavigationControllerOperation
init(operation: UINavigationControllerOperation)
self.operation = operation
super.init()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
return 0.3
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else return
let containerView = transitionContext.containerView
if operation == .push
// do your animation for push
else if operation == .pop
// do your animation for pop
重要的是要记住,对于每种不同类型的操作(即“推送”或“弹出”),to 和 from 视图控制器将是不同的。当您进行推送操作时,视图控制器将是被推送的控制器。当您处于弹出操作时,to 视图控制器将是被转换到的控制器,而 from 视图控制器将是被弹出的控制器。
另外,to
视图控制器必须作为 containerView
的子视图添加到转换上下文中。
动画完成后,您必须调用transitionContext.completeTransition(true)
。如果您正在执行交互式过渡,则必须动态地将 Bool
返回到 completeTransition(didComplete: Bool)
,具体取决于过渡是否在动画结束时完成。
最后(可选阅读),您可能想看看我是如何完成我正在处理的转换的。这段代码有点hacky,我写得很快,所以我不会说它是很棒的动画代码,但它仍然展示了如何做动画部分。
我的过渡非常简单;我想模仿 UINavigationController 通常所做的相同动画,但我想在新视图的同时实现旧视图控制器的 1:1 动画,而不是“顶部的下一页”动画控制器出现。这样可以使两个视图控制器看起来好像是相互固定的。
对于推送操作,首先需要将toViewController
的视图原点设置在屏幕外的x轴上,将其添加为containerView
的子视图,通过将origin.x
设置为将其动画到屏幕上零。同时,我将fromViewController
的视图设置为远离屏幕的动画:
toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations:
toViewController.view.frame = containerView.bounds
fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
,
completion: (finished) in
transitionContext.completeTransition(true)
)
pop 操作基本上是相反的。将toViewController
添加为containerView
的子视图,并将fromViewController
移动到右侧,就像您在左侧的toViewController
中设置动画一样:
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations:
fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
toViewController.view.frame = containerView.bounds
,
completion: (finished) in
transitionContext.completeTransition(true)
)
这是整个 swift 文件的要点:
https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be
【讨论】:
太棒了!我想做的只是向相反的方向制作动画。我检查了其他一些解决方案,但都在左右屏幕上显示闪烁。看起来隐式 alpha 更改动画无法与它们一起删除。只有这个解决方案解决了这个问题。 是的,这是唯一正确的现代解决方案。 (我不介意,但这与我在下面输入的解决方案完全相同!:)) @AlanZeino 如果在同一个 ViewController 中需要为不同的按钮单击设置不同的动画怎么办?因此,对于 button1,您需要一个溶解动画,对于 button2,您需要默认过渡。【参考方案9】:很简单
self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
【讨论】:
欢迎来到 ***:如果您发布代码、XML 或数据示例,请在文本编辑器中突出显示这些行,然后单击编辑器工具栏上的“代码示例”按钮 ( ) 或使用 Ctrl键盘上的 +K 可以很好地格式化和语法突出显示它! 这是一个很好的方法!【参考方案10】:@Luca Davanzo 在 Swift 4.2
中的回答public extension UINavigationController
/**
Pop current view controller to previous view controller.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3)
self.addTransition(transitionType: type, duration: duration)
self.popViewController(animated: false)
/**
Push a new view controller on the view controllers's stack.
- parameter vc: view controller to push.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3)
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3)
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = type
self.view.layer.add(transition, forKey: nil)
【讨论】:
【参考方案11】:那里有 UINavigationControllerDelegate 和 UIViewControllerAnimatedTransitioning 你可以改变任何你想要的动画。
例如这是 VC 的垂直弹出动画:
@objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval
return 0.5
func animateTransition(transitionContext: UIViewControllerContextTransitioning)
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view)
toViewController.view.alpha = 0.5
let finalFrameForVC = fromViewController.view.frame
UIView.animateWithDuration(transitionDuration(transitionContext), animations:
fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height)
toViewController.view.alpha = 1.0
, completion:
finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
)
然后
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
if operation == .Pop
return PopAnimator()
return nil;
有用的教程 https://www.objc.io/issues/5-ios7/view-controller-transitions/
【讨论】:
【参考方案12】:基于 jordanperry
answer 为 swift 4 更新
用于推送UIViewController
let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
UIView.animate(withDuration: 0.75, animations: () -> Void in
UIView.setAnimationCurve(.easeInOut)
self.navigationController?.pushViewController(terms, animated: true)
UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
)
流行音乐
UIView.animate(withDuration: 0.75, animations: () -> Void in
UIView.setAnimationCurve(.easeInOut)
UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
)
navigationController?.popViewController(animated: false)
【讨论】:
【参考方案13】:这是我在 Swift 中的做法:
对于推送:
UIView.animateWithDuration(0.75, animations: () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
self.navigationController!.pushViewController(nextView, animated: false)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
)
流行音乐:
实际上,我的做法与上面的一些回复略有不同——但由于我是 Swift 开发的新手,所以这可能不正确。我已经覆盖了viewWillDisappear:animated:
并在其中添加了弹出代码:
UIView.animateWithDuration(0.75, animations: () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
)
super.viewWillDisappear(animated)
【讨论】:
【参考方案14】:您现在可以使用UIView.transition
。请注意animated:false
。这适用于任何转换选项、弹出、推送或堆栈替换。
if let nav = self.navigationController
UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations:
_ = nav.popViewController(animated:false)
, completion:nil)
【讨论】:
@Fattie,这种特殊方法只适用于developer.apple.com/documentation/uikit/uiviewanimationoptions中列出的任何标准动画,例如翻转和卷发【参考方案15】:我最近正在尝试做类似的事情。我决定我不喜欢 UINavigationController 的滑动动画,但我也不想做 UIView 给你的动画,比如 curl 或类似的东西。当我推送或弹出时,我想在视图之间进行交叉淡入淡出。
问题在于视图实际上是在删除视图或在当前视图的顶部弹出一个视图,因此淡入淡出不起作用。我提出的解决方案涉及获取我的新视图并将其作为子视图添加到 UIViewController 堆栈上的当前顶视图中。我用 0 的 alpha 添加它,然后进行淡入淡出。当动画序列完成时,我将视图推送到堆栈上而不对其进行动画处理。然后我回到旧的 topView 并清理我改变的东西。
它比这更复杂一些,因为您必须调整导航项以使过渡看起来正确。此外,如果您进行任何旋转,则必须在将视图添加为子视图时调整帧大小,以便它们正确显示在屏幕上。这是我使用的一些代码。我继承了 UINavigationController 并覆盖了 push 和 pop 方法。
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
UIViewController *currentViewController = [self.viewControllers lastObject];
//if we don't have a current controller, we just do a normal push
if(currentViewController == nil)
[super pushViewController:viewController animated:animated];
return;
//if no animation was requested, we can skip the cross fade
if(!animation)
[super pushViewController:viewController animated:NO];
return;
//start the cross fade. This is a tricky thing. We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.
viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
NSString *title = [currentViewController.title retain];
//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;
NSArray *array = nil;
//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context. I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];
//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];
[currentViewController setTitle:viewController.title];
[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
r = [(NSArray *)context objectAtIndex:4];
//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
[super pushViewController:n animated:NO];
正如我在代码中提到的,我总是有一个左栏按钮项,因此在将它放入作为动画委托的上下文传递的数组之前,我不会检查它是否为 nil。如果您这样做,您可能需要进行检查。
我发现的问题是,如果您在委托方法中完全崩溃,它不会使程序崩溃。它只是阻止委托完成,但您没有收到任何警告。 因此,由于我在该委托例程中进行清理,它导致了一些奇怪的视觉行为,因为它没有完成清理。
我创建的后退按钮调用“goBack”方法,而该方法只调用 pop 例程。
-(void)goBack
[self popViewControllerAnimated:YES];
另外,这是我的流行音乐例程。
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
//get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];
//if no animation was requested, we can skip the cross fade
if(!animated)
[super popViewControllerAnimated:NO];
return topViewController;
//start of the cross fade pop. A bit tricky. We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0. We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;
//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
[topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
[topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];
NSArray *array = nil;
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;
-(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;
//Not all views have a right bar button. If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
r = [(NSArray *)context objectAtIndex:3];
//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];
//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
//remove the subview we added for the transition
[[c.view.subviews lastObject] removeFromSuperview];
//reset the title we changed
c.title = title;
[title release];
//replace the left bar button that we changed
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//if we were passed a right bar button item, replace that one as well
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
else
[c.navigationItem setRightBarButtonItem:nil animated:NO];
差不多就是这样。如果你想实现旋转,你需要一些额外的代码。在显示它们之前,您需要设置作为子视图添加的视图的框架大小,否则您会遇到方向是横向的问题,但最后一次看到前一个视图时它是纵向的。因此,然后您将其添加为子视图并将其淡入,但它显示为纵向,然后当我们在没有动画的情况下弹出时,相同的视图,但在堆栈中的视图,现在是横向的。整个事情看起来有点时髦。每个人对轮换的实现都有点不同,所以我没有在这里包含我的代码。
希望它可以帮助一些人。我已经到处寻找类似的东西,但找不到任何东西。我认为这不是完美的答案,但目前对我来说效果很好。
【讨论】:
虽然令人钦佩,但这确实不是 7 年后的解决方案! 你是对的。这个答案来自 2011 年。它当时有效,但从那以后情况发生了很大变化。 =)【参考方案16】:以 iJordan 的回答为灵感,为什么不简单地在 UINavigationController 上创建一个 Category 以在整个应用程序中使用,而不是在各处复制/粘贴此动画代码?
UINavigationController+Animation.h
@interface UINavigationController (Animation)
- (void) pushViewControllerWithFlip:(UIViewController*) controller;
- (void) popViewControllerWithFlip;
@end
UINavigationController+Animation.m
@implementation UINavigationController (Animation)
- (void) pushViewControllerWithFlip:(UIViewController *) controller
[UIView animateWithDuration:0.50
animations:^
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self pushViewController:controller animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
];
- (void) popViewControllerWithFlip
[UIView animateWithDuration:0.5
animations:^
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
];
[self popViewControllerAnimated:NO];
@end
然后简单的导入UINavigationController+Animation.h文件并正常调用:
[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];
[self.navigationController popViewControllerWithFlip];
【讨论】:
聪明。但是为什么不添加带有 UIViewAnimationTransition 参数而不是硬编码的 push/pop 方法到 flipFromRight 呢? @Jef 这些是方便的方法 - 这样,实现者不需要记住为每个特定动画类型传递哪个 UIViewAnimationTransition 值,他们只需调用具有“英文”名称的方法他们想要完成什么。 @Jef 同样,您的建议绝对有效-如果我仍在使用 Objective-c 并且需要支持许多过渡样式(绝对不推荐,因为许多不同的过渡样式会使用户感到困惑)我会有 1采用 UIViewAnimationTransition 类型的方法,然后是几个方便的方法,使开发更容易。【参考方案17】:看看ADTransitionController,它是我们在 Applidium 创建的带有自定义过渡动画(其 API 与 UINavigationController 的 API 匹配)的 UINavigationController 的替代品。
您可以为 push 和 pop 动作使用不同的预定义动画,例如 Swipe、Fade、 Cube、Carrousel、Zoom等等。
【讨论】:
【参考方案18】:虽然这里的所有答案都很棒,而且大多数都很好,但是有一个稍微简单的方法可以达到相同的效果......
对于推送:
NextViewController *nextViewController = [[NextViewController alloc] init];
// Shift the view to take the status bar into account
CGRect frame = nextViewController.view.frame;
frame.origin.y -= 20;
frame.size.height += 20;
nextViewController.view.frame = frame;
[UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished)
[self.navigationController pushViewController:nextViewController animated:NO];
];
对于流行音乐:
int numViewControllers = self.navigationController.viewControllers.count;
UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];
[UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished)
[self.navigationController popViewControllerAnimated:NO];
];
【讨论】:
这会在弹出到根视图控制器时崩溃。【参考方案19】:我不知道您可以通过任何方式公开更改过渡动画。
如果不需要“返回”按钮,您应该使用modal view controllers 来实现“从底部推送”/“翻转”/“淡入淡出”/(≥3.2)“页面卷曲”过渡。
在 private 方面,方法 -pushViewController:animated:
调用未记录的方法 -pushViewController:transition:forceImmediate:
,例如如果你想要一个从左到右翻转的过渡,你可以使用
[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];
但是,您不能以这种方式更改“pop”过渡。
【讨论】:
【参考方案20】:请参阅我的回答 to this question,以了解用更少的代码行来完成此操作的方法。此方法允许您以任何您喜欢的方式对新视图控制器的伪“推送”进行动画处理,并且当动画完成时,它会设置导航控制器,就像您使用标准推送方法一样。我的示例允许您从左侧或右侧为滑入设置动画。 为方便起见,此处重复代码:
-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft
[self addChildViewController:neighbor];
CGRect offscreenFrame = self.view.frame;
if(rightToLeft)
offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
else if(direction == MyClimbDirectionRight)
offscreenFrame.origin.x = offscreenFrame.size.width;
[[neighbor view] setFrame:offscreenFrame];
[self.view addSubview:[neighbor view]];
[neighbor didMoveToParentViewController:self];
[UIView animateWithDuration:0.5 animations:^
[[neighbor view] setFrame:self.view.frame];
completion:^(BOOL finished)
[neighbor willMoveToParentViewController:nil];
[neighbor.view removeFromSuperview];
[neighbor removeFromParentViewController];
[[self navigationController] pushViewController:neighbor animated:NO];
NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
[newStack removeObjectAtIndex:1]; //self, just below top
[[self navigationController] setViewControllers:newStack];
];
【讨论】:
【参考方案21】:我知道这个帖子很旧,但我想我会投入两分钱。您不需要制作自定义动画,有一种简单(也许是 hacky)的方法。不要使用 push,而是创建一个新的导航控制器,使新的视图控制器成为该导航控制器的根视图控制器,然后从原始导航控制器呈现导航控制器。 Present 可以轻松定制多种样式,无需制作自定义动画。
例如:
UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)
您可以随意更改演示风格。
【讨论】:
注意:如果使用present
,滑动到返回手势将不起作用。【参考方案22】:
从示例应用中查看此变体。 https://github.com/mpospese/MPFoldTransition/
#pragma mark - UINavigationController(MPFoldTransition)
@implementation UINavigationController(MPFoldTransition)
//- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:viewController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished)
[self pushViewController:viewController animated:NO];
];
- (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style
UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2];
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:toController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished)
[self popViewControllerAnimated:NO];
];
return toController;
【讨论】:
【参考方案23】:只需使用:
ViewController *viewController = [[ViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;
[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];
【讨论】:
【参考方案24】:意识到这是一个老问题。我仍然想发布这个答案,因为我在提出几个viewControllers
时遇到了一些问题。我的解决方案是继承 UINavigationController
并覆盖所有的 pop 和 push 方法。
翻转导航控制器.h
@interface FlippingNavigationController : UINavigationController
@end
翻转导航控制器.m:
#import "FlippingNavigationController.h"
#define FLIP_DURATION 0.5
@implementation FlippingNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
animations:^ [super pushViewController:viewController
animated:NO];
completion:nil];
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
animated:animated] lastObject];
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
return [self popToViewController:[self.viewControllers firstObject]
animated:animated];
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
__block NSArray* viewControllers = nil;
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
animations:^ viewControllers = [super popToViewController:viewController animated:NO];
completion:nil];
return viewControllers;
@end
【讨论】:
【参考方案25】:我找到了一种适合我的目的的温和递归方式。我有一个实例变量 BOOL 用于阻止正常的弹出动画并替换我自己的非动画弹出消息。该变量最初设置为 NO。当点击后退按钮时,委托方法将其设置为 YES 并向导航栏发送新的非动画弹出消息,从而再次调用相同的委托方法,这次将变量设置为 YES。将变量设置为 YES 后,委托方法将其设置为 NO 并返回 YES 以允许发生非动画弹出。在第二个委托调用返回后,我们最终回到第一个,返回 NO,阻止了原始动画弹出!它实际上并不像听起来那么混乱。我的 shouldPopItem 方法如下所示:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
if ([[navigationBar items] indexOfObject:item] == 1)
[expandedStack restack];
if (!progPop)
progPop = YES;
[navBar popNavigationItemAnimated:NO];
return NO;
else
progPop = NO;
return YES;
为我工作。
【讨论】:
以上是关于如何在基于导航的应用程序中更改推送和弹出动画的主要内容,如果未能解决你的问题,请参考以下文章
链 UIViewControllers 推送和弹出动画与 UINavigationController 关闭/呈现模式视图?