あるビューから別のビューに移行するときにカスタムアニメーションを作成するために、いくつかのチュートリアルに従っています。
here のカスタムセグエを使用した私のテストプロジェクトは正常に動作しますが、カスタムセグエ内でカスタムアニメーションを実行することはもう推奨されないので、UIViewControllerAnimatedTransitioning
を使用する必要があります。
このプロトコルを使用するいくつかのチュートリアルに従いましたが、それらはすべてモーダルプレゼンテーションに関するものです(たとえば このチュートリアル )。
私がやろうとしているのは、Navigation Controllerツリー内のプッシュセグエですが、ショー(プッシュ)セグエで同じことをしようとすると、それはもう機能しません。
Navigation Controllerで、あるビューから別のビューへカスタムアニメーションを移行する正しい方法を教えてください。
とにかく、すべてのトランジションアニメーションに1つの方法を使用できますか?ある日、同じアニメーションを実行したいが、モーダルとコントローラーの移行で作業するためにコードを2回複製しなければならない場合は厄介です。
Navigation Controller(UINavigationController
)を使用してカスタム移行を行うには、次のことを行う必要があります。
UINavigationControllerDelegate
プロトコルに準拠するようにView Controllerを定義します。たとえば、View Controllerの.m
ファイルに、このプロトコルへの準拠を指定するプライベートクラス拡張を含めることができます。
@interface ViewController () <UINavigationControllerDelegate>
@end
実際にView ControllerをNavigation Controllerのデリゲートとして指定していることを確認してください。
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
}
View ControllerにanimationControllerForOperation
を実装します:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
{
if (operation == UINavigationControllerOperationPush)
return [[PushAnimator alloc] init];
if (operation == UINavigationControllerOperationPop)
return [[PopAnimator alloc] init];
return nil;
}
プッシュおよびポップアニメーション用のアニメーターを実装します。例:
@interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
@implementation PopAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
それはフェードトランジションを行いますが、あなたが適切と思うようにアニメーションを自由にカスタマイズする必要があります。
インタラクティブなジェスチャ(たとえば、左から右にスワイプしてポップするなど)を処理する場合は、インタラクションコントローラーを実装する必要があります。
対話コントローラー(UIViewControllerInteractiveTransitioning
に準拠するオブジェクト)のプロパティを定義します。
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
このUIPercentDrivenInteractiveTransition
は、ジェスチャの完成度に基づいてカスタムアニメーションを更新するという面倒な作業を行うNiceオブジェクトです。
ビューにジェスチャレコグナイザーを追加します。ここでは、ポップをシミュレートするために左ジェスチャレコグナイザを実装しています。
UIScreenEdgePanGestureRecognizer *Edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)];
Edge.edges = UIRectEdgeLeft;
[view addGestureRecognizer:Edge];
ジェスチャー認識ハンドラーを実装します。
/** Handle swipe from left Edge
*
* This is the "action" selector that is called when a left screen Edge gesture recognizer starts.
*
* This will instantiate a UIPercentDrivenInteractiveTransition when the gesture starts,
* update it as the gesture is "changed", and will finish and release it when the gesture
* ends.
*
* @param gesture The screen Edge pan gesture recognizer.
*/
- (void)handleSwipeFromLeftEdge:(UIScreenEdgePanGestureRecognizer *)gesture {
CGPoint translate = [gesture translationInView:gesture.view];
CGFloat percent = translate.x / gesture.view.bounds.size.width;
if (gesture.state == UIGestureRecognizerStateBegan) {
self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
[self popViewControllerAnimated:TRUE];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
[self.interactionController updateInteractiveTransition:percent];
} else if (gesture.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [gesture velocityInView:gesture.view];
if (percent > 0.5 || velocity.x > 0) {
[self.interactionController finishInteractiveTransition];
} else {
[self.interactionController cancelInteractiveTransition];
}
self.interactionController = nil;
}
}
Navigation Controllerのデリゲートでは、interactionControllerForAnimationController
デリゲートメソッドも実装する必要があります
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
return self.interactionController;
}
「UINavigationControllerカスタム遷移チュートリアル」をグーグルで検索すると、多くのヒットが得られます。または WWDC 2013 Custom Transitions video をご覧ください。
addSubview
の前に次のコードを追加できます
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
別の質問から custom-transition-for-Push-animation-with-navigationcontroller-on-ios-9
FinalFrameForViewControllerに関するAppleのドキュメントから:
指定されたView Controllerのビューの終了フレーム長方形を返します。
このメソッドによって返される長方形は、トランジションの最後の対応するビューのサイズを表します。プレゼンテーション中にカバーされるビューの場合、このメソッドによって返される値はCGRectZeroである可能性がありますが、有効なフレーム長方形である可能性もあります。
Rob&Q iの完璧な答えを使用して、.Pushと.popに同じフェードアニメーションを使用した簡略化されたSwiftコードを以下に示します。
extension YourViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//INFO: use UINavigationControllerOperation.Push or UINavigationControllerOperation.pop to detect the 'direction' of the navigation
class FadeAnimation: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
if let vc = toViewController {
transitionContext.finalFrame(for: vc)
transitionContext.containerView.addSubview(vc.view)
vc.view.alpha = 0.0
UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
animations: {
vc.view.alpha = 1.0
},
completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
} else {
NSLog("Oops! Something went wrong! 'ToView' controller is nill")
}
}
}
return FadeAnimation()
}
}
YourViewControllerのviewDidLoad()メソッドでデリゲートを設定することを忘れないでください:
override func viewDidLoad() {
//...
self.navigationController?.delegate = self
//...
}
Swift 3と4の両方で機能します
@IBAction func NextView(_ sender: UIButton) {
let newVC = self.storyboard?.instantiateViewControllerWithIdentifier(withIdentifier: "NewVC") as! NewViewController
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCAGravityLeft
//instead "kCAGravityLeft" try with different transition subtypes
self.navigationController?.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(newVC, animated: false)
}