ナビゲーションベースのアプリケーションがあり、プッシュアニメーションとポップアニメーションのアニメーションを変更したい。どうすればいいですか?
2018年を編集
この質問には多くの回答があり、かなり前からありますが、私が今最も関連性があると思うものへの回答を再選択しました。他に考えている人がいたらコメントで教えてください
ナビゲーションベースのアプリケーションがあり、プッシュアニメーションとポップアニメーションのアニメーションを変更したい。どうすればいいですか?
2018年を編集
この質問には多くの回答があり、かなり前からありますが、私が今最も関連性があると思うものへの回答を再選択しました。他に考えている人がいたらコメントで教えてください
回答:
ナビゲーションベースのアプリでプッシュアニメーションとポップアニメーションを変更する方法...
前文:
iOS開発に慣れていないとします。紛らわしいことに、Appleは簡単に使用できる2つのトランジションを提供しています。これらは、「クロスフェード」と「フリップ」です。
しかし、もちろん、「クロスフェード」と「フリップ」は役に立たない。それらは決して使用されません。なぜAppleがこの2つの役に立たないトランジションを提供したのか誰にもわかりません!
そう:
「スライド」などの通常の一般的な遷移を行いたいとします。その場合、あなたは莫大な量の仕事をしなければなりません!。
その仕事は、この投稿で説明されています。
繰り返すだけ:
驚くべきことに:iOSでは、最も単純で最も一般的な日常のトランジション(通常のスライドなど)が必要な場合、完全なカスタムトランジションを実装するためのすべての作業を行う必要があります。。
これを行う方法は次のとおりです...
UIViewControllerAnimatedTransitioning
あなた自身のようなブール値が必要popStyle
です。(飛び出していますか、飛び出していますか?)
あなたはtransitionDuration
(些細な)とメインコールを含める必要があります、animateTransition
実際、insideには2つの異なるルーチンを記述する必要がありますanimateTransition
。1つはプッシュ用、もう1つはポップ用です。おそらくそれらに名前を付けるanimatePush
とanimatePop
。内部animateTransition
でpopStyle
は、2つのルーチンに分岐します
以下の例では、単純な移動/移動を行っています
あなたanimatePush
とanimatePop
ルーティンで。「from view」と「to view」を取得する必要があります。(その方法は、コード例に示されています。)
そして、あなたは新しい「へ」ビューのために必要 がありaddSubview
ます。
そしてあなたはあなたのアニメの終わりに電話しなければなりません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)
})
}
}
その後 ...
注:奇妙なことに、これを行う必要があるのはがあるのは「最初の」View Controllerだけです。(「下」にあるもの。)
あなたが上にポップしたもので、何もしません。簡単です。
だからあなたのクラス...
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)
}
}
ふew。
@AlanZeinoと@eliasの回答までスクロールして、最近のAnimatedTransitioning
iOSアプリでの使用方法についてのディスカッションをご覧ください!
animatePush
ありanimatePop
、2つの異なる方向です!
私は次のことをしました、そしてそれはうまく働きます...そしてシンプルで理解しやすいです。
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];
プッシュについても同じです。
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)
Animated:NO
部分は重要です。場合YES
渡され、アニメーションが混在して面白い効果を引き起こします。
setViewControllers:
これが私がいつもこのタスクを完了するために管理してきた方法です。
プッシュの場合:
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];
super
でself.navigationController
UIViewController
「ViewController」の部分がない名前をサブクラスに付けたのはなぜですか。この名前は、UIViewに適しています。
プッシュ用
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];
@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
です。しかしながら、これにより、デフォルトのアニメーションも保持されます。これを修正するには、ストーリーボードに移動し、セグエをクリックして、[アニメーション]ボックスをオフにする必要があります。ただし、これにより、アプリがIOS 9.0以降に制限されます(少なくとも、私がXcode 7で実行したとき)。
セグエで行う場合、最後の2行は次のように置き換える必要があります。
self.navigationController?.popViewControllerAnimated(false)
falseに設定しても、無視されます。
であることを覚えておいてくださいスウィフト、 拡張子は間違いなくあなたの友達です!
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)
}
}
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];
-pushViewController:transition:forceImmediate:
することについてしばらく前にここで議論がありましたが、それは悪い考えでしょう。
これがグーグルでのトップの結果なので、私は私が最も健全な方法だと思うことを共有したいと思いました。iOS 7以降の移行APIを使用します。これをiOS 10とSwift 3で実装しました。
のUINavigationController
サブクラスを作成し、UINavigationController
それに準拠するクラスのインスタンスを返す場合、これを2つのビューコントローラー間でアニメーション化する方法と組み合わせるのは非常に簡単です。UIViewControllerAnimatedTransitioning
プロトコルにです。
たとえば、ここに私の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
プロトコルに必要な2つの関数を実装し、好きなようにアニメーション化します。
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ビューコントローラがプッシュされます。ポップ操作の場合、toビューコントローラーが遷移先のコントローラーになり、fromビューコントローラーがポップされるコントローラーになります。
また、to
ビューコントローラーcontainerView
は、遷移コンテキストののサブビューとして追加する必要があります。
アニメーションが完了したら、を呼び出す必要がありますtransitionContext.completeTransition(true)
。インタラクティブなトランジションを実行している場合は、アニメーションの最後でトランジションが完了しているかどうかに応じBool
てcompleteTransition(didComplete: Bool)
、動的にa を返す必要があります。
最後に(オプションの読み取り)、私が取り組んでいた移行をどのように行ったかを確認したい場合があります。このコードはもう少しハッキーで、すぐに書いたので、すばらしいアニメーションコードだとは言えませんが、それでもアニメーション部分の実行方法を示しています。
鉱山は本当に単純な移行でした。UINavigationControllerが通常行うのと同じアニメーションを模倣したかったのですが、「上の次のページ」のアニメーションではなく、古いビューコントローラの1:1アニメーションを新しいビューと同時に実装したかったのです。コントローラが表示されます。これには、2つのView Controllerが互いに固定されているように見えるという効果があります。
プッシュ操作の場合は、最初にtoViewController
のビューの原点を画面外のx軸に設定し、それをのサブビューとして追加し、containerView
それorigin.x
をゼロに設定することで画面にアニメーション化する必要があります。同時に、画面外にfromViewController
設定するorigin.x
ことで、のビューをアニメーション化します。
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)
})
ポップ操作は基本的に逆です。追加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
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/
Swift 4用に更新された回答に基づくjordanperry
プッシュ用 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)
これが私が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)
@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)
}
}
私は最近、似たようなことをしようとしていました。UINavigationControllerのスライドアニメーションは好きではないと判断しましたが、UIViewがカールなどのアニメーションを提供することもしたくありませんでした。プッシュまたはポップするときに、ビュー間でクロスフェードを実行する必要がありました。
ここでの問題は、ビューが文字通りビューを削除している、または現在のビューの上にポップしているという事実に関係しているため、フェードは機能しません。新しいビューを取得して、それをサブビューとしてUIViewControllerのスタックの現在のトップビューに追加するという解決策になりました。アルファ0で追加してから、クロスフェードを行います。アニメーションシーケンスが終了したら、ビューをアニメーション化せずにスタックにプッシュします。次に、古いtopViewに戻り、変更したものをクリーンアップします。
それよりも少し複雑です。navigationItemがあるので、トランジションが正しく見えるように調整する必要があります。また、回転を行う場合は、ビューをサブビューとして追加するときにフレームサイズを調整して、画面に正しく表示されるようにする必要があります。これが私が使用したコードの一部です。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];
}
}
}
それだけです。ローテーションを実装する場合は、追加のコードが必要になります。表示する前に、サブビューとして追加するビューのフレームサイズを設定する必要があります。そうしないと、向きが横向きであるという問題に遭遇しますが、前回のビューを最後に見たときは縦向きでした。したがって、それをサブビューとして追加してフェードインしますが、ポートレートとして表示されます。アニメーションなしでポップすると、同じビューですが、スタック内にあるビューはランドスケープになります。全体が少しファンキーに見えます。ローテーションの実装は誰でも少し異なるので、そのためのコードはここに含めませんでした。
それが一部の人々に役立つことを願っています。私はこのようなものを探し回ってみましたが、何も見つかりませんでした。私はこれが完璧な答えだとは思いませんが、現時点では私にとってはうまくいきます。
これで使用できます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)
}
iJordanの答えをインスピレーションとして使用して、このアニメーションコードをあちこちにコピー/貼り付ける代わりに、アプリ全体で使用するUINavigationControllerにカテゴリを作成するだけではどうですか。
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];
とてもシンプルです
self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
Applidiumで作成したカスタム遷移アニメーション(そのAPIはUINavigationControllerのAPIと一致します)を持つUINavigationControllerの代わりのドロップであるADTransitionControllerを見てください。
Swipe、Fade、Cube、Carrousel、Zoomなどのプッシュアクションとポップアクションに、事前定義されたさまざまなアニメーションを使用できます。
ここでのすべての回答は素晴らしく、ほとんどが非常にうまく機能しますが、同じ効果を達成する少し簡単な方法があります...
プッシュの場合:
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];
}];}
トランジションアニメーションを公に変更する方法を私は知りません。
「戻る」ボタンが不要な場合は、モーダルビューコントローラーを使用して、「下から押す」/「フリップ」/「フェード」/(≥3.2)「ページカール」の切り替えを行う必要があります。
でプライベート側、方法が-pushViewController:animated:
文書化されていないメソッドを呼び出して-pushViewController:transition:forceImmediate:
、あなたがフリップから左から右への移行をしたい場合は、あなたが使用することができます例えばので、
[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];
ただし、この方法で「ポップ」トランジションを変更することはできません。
はるかに少ないコード行で実行する方法については、この質問に対する私の回答を参照してください。このメソッドを使用すると、新しいビューコントローラの疑似「プッシュ」を好きなようにアニメーション化できます。アニメーションが完了すると、標準のプッシュメソッドを使用した場合と同じように、ナビゲーションコントローラが設定されます。この例では、スライドインを左または右からアニメーション化できます。便宜上、ここでコードを繰り返します。
-(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];
}];
}
サンプルアプリから、このバリエーションを確認してください。 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;
}
ただ使う:
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];
これを実現することは古い問題です。viewControllers
提案された回答をいくつかポップするのに問題があったので、私はまだこの回答を投稿したいと思います。私の解決策は、UINavigationController
すべてのpopメソッドとpushメソッドをサブクラス化してオーバーライドすることです。
FlippingNavigationController.h
@interface FlippingNavigationController : UINavigationController
@end
FlippingNavigationController.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
この糸は古いのはわかっているが、2セント入れてみようと思った。カスタムアニメーションを作成する必要はありません。それを行う簡単な(おそらくハックな)方法があります。プッシュを使用する代わりに、新しいナビゲーションコントローラーを作成し、新しいビューコントローラーをそのナビゲーションコントローラーのルートビューコントローラーにして、元のナビゲーションコントローラーのナビゲーションコントローラーを表示します。Presentは多くのスタイルで簡単にカスタマイズでき、カスタムアニメーションを作成する必要はありません。
例えば:
UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)
また、プレゼンテーションスタイルは自由に変更できます。
私の目的に役立つ、これを行うためのやや再帰的な方法を見つけました。通常のポップアニメーションをブロックし、アニメーション化されていない独自のポップメッセージを置き換えるために使用するインスタンス変数BOOLがあります。変数は最初はNOに設定されています。[戻る]ボタンがタップされると、デリゲートメソッドはそれをYESに設定し、アニメーション化されていない新しいポップメッセージをナビゲーションバーに送信します。これにより、同じデリゲートメソッドが再度呼び出されますが、今回は変数がYESに設定されています。変数がYESに設定されている場合、デリゲートメソッドは変数をNOに設定し、YESを返して、アニメーション化されていないポップを発生させます。2番目のデリゲート呼び出しが戻った後、最初の呼び出しに戻り、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;
}
}
私のために働く。