ナビゲーションベースのアプリケーションがあり、プッシュアニメーションとポップアニメーションのアニメーションを変更したい。どうすればいいですか?
編集2018
この質問には多くの回答がありましたが、それはもうかなり前のことです。そうでないと思う人がいる場合は、コメントで教えてください
ナビゲーションベースのアプリでプッシュアニメーションとポップアニメーションを変更する方法...
プリアンブル:
あなたがiOS開発の初心者であるとしましょう。紛らわしいことに、Appleは、簡単に使用できる2つの遷移を提供します。これらは、「クロスフェード」と「フリップ」です。
しかし、もちろん、「クロスフェード」と「フリップ」は無意味です。それらは決して使用されません。 Appleがこれらの2つの役に立たない遷移を提供した理由は誰にもわかりません!
そう:
あなたがやりたいと言ってください普通の、一般的な、移行「スライド」など。その場合、膨大な量の作業を行う必要があります!。
この仕事は、この投稿で説明されています。
繰り返すだけ:
驚いたことに:iOSでは、最も単純で最も一般的な、毎日の移行(通常のスライドなど)が必要な場合、すべての作業を実装する必要があります- 完全なカスタム遷移。
方法は次のとおりです...
UIViewControllerAnimatedTransitioning
が必要ですpopStyle
のような独自のブール値が必要です。 (それはポップオンですか、ポップオフですか?)
transitionDuration
(簡単な)とメイン呼び出しanimateTransition
を含める必要があります
実際、must内部animateTransition
に2つの異なるルーチンを記述します。 1つはプッシュ用、もう1つはポップ用です。おそらくanimatePush
およびanimatePop
という名前を付けてください。 animateTransition
内で、単にpopStyle
で2つのルーチンに分岐します
以下の例は、単純な移動/移動を行います
animatePush
およびanimatePop
ルーチン内。 must「from view」と「to view」を取得します。 (その方法は、コード例に示されています。)
そしてmustaddSubview
は新しい "to"ビューに対して。
そしてあなたはmustアニメの終わりに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でこれを行うだけです。 (「下」にあるもの。)
topでポップしたもので、nothingを実行します。簡単です。
だからあなたのクラス...
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)
}
}
ふう。
最近のiOSアプリでAnimatedTransitioning
を使用する方法の詳細については、@ AlanZeinoと@eliasの回答までスクロールしてください。
私は次のことをしました、それはうまく動作します..そしてシンプルで理解しやすいです..
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)
これが、私が常にこのタスクを完了する方法です。
プッシュの場合:
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];
プッシュ用
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に設定しても、それはそれを無視します。
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)
}
}
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];
これはGoogleでの最高の結果であるため、私が最も健全な方法だと思うことを共有したいと思いました。 iOS 7以降の移行APIを使用します。これをSwift 3でiOS 10に実装しました。
UINavigationController
のサブクラスを作成し、UINavigationController
プロトコルに準拠するクラスのインスタンスを返す場合、これを2つのView Controller間の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 viewコントローラーは移行先のコントローラーになり、from viewコントローラーはポップされるコントローラーになります。
また、to
ビューコントローラーを、containerView
のサブビューとして遷移コンテキストに追加する必要があります。
アニメーションが完了したら、transitionContext.completeTransition(true)
を呼び出す必要があります。インタラクティブなトランジションを行う場合は、アニメーションの最後でトランジションが完了したかどうかに応じて、Bool
をcompleteTransition(didComplete: Bool)
に動的に返す必要があります。
最後に(optional reading)、あなたが私が取り組んでいた移行をどのようにしたかを見たいかもしれません。このコードはもう少しハックで、私はそれを非常に迅速に書いたので、素晴らしいアニメーションコードとは言いませんが、アニメーション部分の実行方法を示しています。
私は本当に簡単な移行でした。 UINavigationControllerが通常行うのと同じアニメーションを模倣したかったのですが、「次のページの上の」アニメーションではなく、新しいビューと同時に古いView Controllerの1:1アニメーションを実装したかったのです。コントローラが表示されます。これにより、2つのView Controllerが互いに固定されているように見えるようになります。
プッシュ操作の場合、最初にx軸オフスクリーンでtoViewController
のビューOriginを設定し、それをcontainerView
のサブビューとして追加し、Origin.x
をゼロに設定してスクリーン上にアニメーション化する必要があります。同時に、画面から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)
})
ポップ操作は基本的に逆です。 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/
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)
これが私が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)
私は最近似たようなことをしようとしていました。 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];
}
}
}
それはほとんどそれです。回転を実装する場合は、追加のコードが必要になります。表示する前にサブビューとして追加するビューのフレームサイズを設定する必要があります。そうしないと、向きが横向きになりますが、前回のビューを最後に見たときに縦向きになります。したがって、サブビューとして追加してフェードインしますが、ポートレートとして表示され、アニメーションなしでポップすると、同じビューがスタックにあるビューが横になります。全体が少しファンキーに見えます。誰もがローテーションを実装する方法は少し異なるため、ここではそのためのコードは含めませんでした。
それが一部の人々に役立つことを願っています。私はこのようなものを探しましたが、何も見つかりませんでした。私はこれが完璧な答えだとは思いませんが、この時点で私にとって本当にうまく機能しています。
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];
これで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)
}
@Swift 4.2の@Luca Davanzoの答え
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)
}
}
ここでのすべての答えは素晴らしく、ほとんどが非常にうまく機能していますが、同じ効果を達成するわずかに単純な方法があります...
プッシュの場合:
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];
}];}
とても簡単です
self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
ADTransitionController をご覧ください。これは、Applidiumで作成したUINavigationControllerをカスタム遷移アニメーション(そのAPIはUINavigationControllerのAPIと一致)に置き換えたものです。
Pushおよびpopアクションに異なる事前定義されたアニメーションを使用できますasSwipe、Fade、Cube、カルーセル、ズームなど。
はるかに少ないコード行でそれを行う方法については、私の答え この質問に対する を参照してください。このメソッドを使用すると、新しいView Controllerの擬似「プッシュ」を好きな方法でアニメーション化できます。アニメーションが完了すると、標準のPushメソッドを使用したかのようにNavigation Controllerが設定されます。私の例では、左または右からスライドインをアニメーション化できます。便宜上、ここでコードを繰り返します。
-(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];
}];
}
トランジションアニメーションをパブリックに変更する方法は知りません。
「戻る」ボタンが必要ない場合は、shoulduse modal view controllers 「下からプッシュ」/「フリップ」/「フェード」/(≥3.2)「ページカール」トランジションを使用します。
private側では、メソッド-pushViewController:animated:
は文書化されていないメソッド-pushViewController:transition:forceImmediate:
を呼び出します。左から右への切り替えが必要な場合は、次を使用できます。
[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];
ただし、この方法で「ポップ」トランジションを変更することはできません。
サンプルアプリから、このバリエーションをご覧ください。 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;
}
これを実現することは古い質問です。提案された回答でいくつかの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セントを入れると思いました。カスタムアニメーションを作成する必要はありません。簡単な(おそらくハッキング)方法があります。プッシュを使用する代わりに、新しいナビゲーションコントローラーを作成し、新しいビューコントローラーをそのnavコントローラーのルートビューコントローラーにし、元のnavコントローラーからnavコントローラーを提示します。 Presentは多くのスタイルで簡単にカスタマイズでき、カスタムアニメーションを作成する必要はありません。
例えば:
UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)
また、必要に応じてプレゼンテーションのスタイルを変更できます。
使用するだけ:
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];