Как изменить анимацию Push и Pop в приложении на основе навигации

У меня есть приложение на основе навигации, и я хочу изменить анимацию анимации push и pop. Как бы я это сделал?

Изменить 2018

Было много ответов на этот вопрос, и прошло довольно много времени, я переизбрал ответ на то, что я считаю наиболее актуальным сейчас. Если есть кто-то, кто думает иначе, пожалуйста, дайте мне знать в комментариях

Ответ 1

Как изменить анимацию Push и Pop в приложении для навигации...

Для 2019 года "окончательный ответ!"

Преамбула:

Скажем, вы новичок в разработке для iOS. Смущает, что Apple предоставляет два перехода, которые можно легко использовать. Это: "кроссфейд" и "флип".

Но, конечно, "кроссфейд" и "флип" бесполезны. Они никогда не используются. Никто не знает, почему Apple предоставила эти два бесполезных перехода!

Так:

Скажем, вы хотите сделать обычный, обычный переход, такой как "слайд". В этом случае вам придется выполнить ОГРОМНОЕ количество работы! ,

Эта работа, объясняется в этом посте.

Просто повторить:

Удивительно: с iOS, если вам нужны самые простые, самые обычные, ежедневные переходы (например, обычный слайд), вам нужно выполнить всю работу по реализации полного пользовательского перехода.

Вот как это сделать...

1. Вам нужен пользовательский UIViewControllerAnimatedTransitioning

  1. Вам нужен собственный bool, такой как popStyle. (Это срабатывает, или выключается?)

  2. Вы должны включить transitionDuration (тривиальное) и основной вызов animateTransition

  3. На самом деле вы должны написать две разные подпрограммы для внутри animateTransition. Один для толчка, а другой для попса. Вероятно, назовите их animatePush и animatePop. Внутри animateTransition просто popStyle в popStyle к двум подпрограммам

  4. В приведенном ниже примере выполняется простое перемещение/удаление

  5. В ваших animatePush и animatePop. Вы должны получить "от просмотра" и "для просмотра". (Как это сделать, показано в примере кода.)

  6. и вы должны addSubview для нового представления "to".

  7. и вы должны вызвать 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. Используйте его в вашем контроллере представления.

Примечание: как ни странно, вам нужно сделать это только в "первом" контроллере вида. (Тот, который "под".)

С тем, что вы попали на вершине, ничего не делать. Легко.

Итак, ваш класс...

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 для получения дополнительной информации о том, как AnimatedTransitioning переход в приложениях iOS в эти дни!

Ответ 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];

И то же самое для 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)

Ответ 3

Вот как мне всегда удавалось выполнить эту задачу.

Для Push:

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];

Для Pop:

[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 для анимации.


Для Push:

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];

Ответ 4

для push

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];

для pop

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

@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).

При выполнении в segue последние две строки следует заменить на:

self.navigationController?.popViewControllerAnimated(false)

Даже если я устанавливаю значение false, оно игнорирует его.

Ответ 6

Помните, что в Swift, расширение, безусловно, ваши друзья!

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 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)
    }

}

Ответ 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];

Ответ 8

Поскольку это лучший результат в Google, я думал, что поделюсь тем, что, по моему мнению, является самым разумным способом; который должен использовать API перехода iOS 7+. Я реализовал это для iOS 10 с Swift 3.

Совсем просто комбинировать это с тем, как UINavigationController анимирует между двумя диспетчерами представлений, если вы создаете подкласс UINavigationController и возвращаете экземпляр класса, который соответствует протоколу 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 я знал, что такое операция (т.е. "Push" или "pop" ). Это помогает понять, какую анимацию я должен делать. В большинстве случаев вы хотите выполнить другую анимацию в зависимости от операции.

Остальное довольно стандартно. Внесите две требуемые функции в протокол 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
        }
    }
}

Важно помнить, что для каждого типа операций (т.е. "push" или "pop" ) контроллеры to и from будут разными. Когда вы находитесь в режиме push, просмотр контроллера будет одним нажатием. Когда вы находитесь в поп-операции, для просмотра контроллера будет тот, к которому идет переход, и из диспетчера представлений будет тот, который выскочил.

Кроме того, диспетчер представлений to должен быть добавлен как подчиненный containerView в контексте перехода.

Когда ваша анимация завершена, вы должны позвонить transitionContext.completeTransition(true). Если вы выполняете интерактивный переход, вам придется динамически возвращать Bool в completeTransition(didComplete: Bool), в зависимости от того, завершен ли переход в конце анимации.

Наконец (необязательное чтение), вы можете увидеть, как я сделал переход, над которым я работал. Этот код немного взломан, и я написал его довольно быстро, поэтому я бы не сказал, что это отличный анимационный код, но он все еще показывает, как сделать часть анимации.

Моя была действительно простым переходом; Я хотел подражать той же анимации, которую обычно выполняет UINavigationController, но вместо анимации "next page over the top" я хотел реализовать анимацию 1:1 старого контроллера представлений одновременно с новым представлением появляется контроллер. Это приводит к тому, что два диспетчера представлений выглядят так, как будто они привязаны друг к другу.

Для операции push, для которой сначала нужно установить начало координат 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)
})

Вот сущность со всем быстрым файлом:

https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be

Ответ 9

Есть 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/

Ответ 10

Вот как я сделал то же самое в Swift:

Для Push:

    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)
    })

Для Pop:

Я на самом деле сделал это несколько иначе, чем некоторые из ответов выше, но поскольку я новичок в разработке 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)

Ответ 11

На основе ответа jordanperry обновлено для быстрого 4

Для push 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)

Ответ 12

Недавно я пытался сделать что-то подобное. Я решил, что мне не нравится скользящая анимация UINavigationController, но я также не хотел делать анимации, которые UIView дает вам, как завиток или что-то в этом роде. Я хотел сделать перекрестный переход между взглядами, когда я нажимаю или поп.

Проблема заключается в том, что представление буквально удаляет вид или выскакивает один поверх текущей, поэтому затухание не работает. Решение, к которому я пришел, привлекло мое новое представление и добавило его в качестве подзаголовка в текущий верхний вид стека UIViewController. Я добавляю его с альфой из 0, затем делаю кроссфейд. Когда последовательность анимации заканчивается, я нажимаю представление на стек, не анимируя его. Затем я возвращаюсь к старым 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];

 }

Как я упоминал в коде, у меня всегда есть элемент кнопки с левым баром, поэтому я не проверяю, не ноль, перед тем как поместить его в массив, который я передаю в качестве контекста для делегата анимации. Если вы сделаете это, вы можете сделать эту проверку.

Проблема, которую я обнаружил, заключается в том, что если вы вообще вообще не выполняетесь в методе делегата, это не приведет к сбою программы. Он просто останавливает делегата от завершения, но вы не получаете никакого предупреждения.
Так как я делал очистку в этой процедуре делегата, это вызывало какое-то странное визуальное поведение, так как оно не заканчивало очистку.

Кнопка "Назад", которую я создаю, вызывает метод "goBack", и этот метод просто вызывает процедуру pop.

-(void)goBack
{ 
     [self popViewControllerAnimated:YES];
}

Кроме того, вот моя pop-процедура.

-(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];
    }


 }
}

Это в значительной степени. Вам понадобится дополнительный код, если вы хотите выполнить вращения. Вам нужно будет установить размер фреймов ваших представлений, которые вы добавляете в качестве подзонов, прежде чем показывать их, иначе вы столкнетесь с проблемами ориентации, а в последний раз, когда вы видели предыдущий вид, это был портрет. Таким образом, вы добавляете его в виде суб-представления и затушевываете его, но он отображается как портрет, а затем, когда мы выходим без анимации, тот же вид, но тот, который находится в стеке, теперь является ландшафтом. Все это выглядит немного напуганным. Каждая реализация вращения немного отличается, поэтому я не включил в нее код для этого.

Надеюсь, это поможет некоторым людям. Я искал все что-то вроде этого и ничего не мог найти. Я не думаю, что это идеальный ответ, но в настоящий момент он работает для меня хорошо.

Ответ 13

Используя 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];

Ответ 14

Теперь вы можете использовать UIView.transition. Заметим, что animated:false. Это работает с любой опцией перехода, поп, push или stack replace.

if let nav = self.navigationController
{
    UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
        _ = nav.popViewController(animated:false)
    }, completion:nil)
}

Ответ 15

@Лука Даванзо ответ в 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 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)
    }

}

Ответ 16

В то время как все ответы здесь велики и большинство работают очень хорошо, есть несколько более простой метод, который достигает такого же эффекта...

Для Push:

  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];
  }];

Для Pop:

  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];
  }];}

Ответ 17

Посмотрите ADTransitionController, сокращение замещения для UINavigationController с помощью настраиваемых анимаций перехода (его API соответствует API UINavigationController), который мы создали в Applidium.

Вы можете использовать разные предопределенные анимации для push и pop действий, таких как Swipe, Fade, Cube, Carrousel, Zoom и т.д.

Ответ 18

Это очень просто

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

Ответ 19

См. мой ответ на этот вопрос для способа сделать это в гораздо меньшем количестве строк кода. Этот метод позволяет вам анимировать псевдо- "Push" нового контроллера представлений любым способом, который вам нравится, и когда анимация завершена, он настраивает навигационный контроллер так же, как если бы вы использовали стандартный метод Push. Мой пример позволяет вам анимировать либо слайд-шоу слева, либо справа. Код повторен для удобства:

-(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];
    }];
}

Ответ 20

Я не знаю, каким образом можно публично изменить трансляцию.

Если кнопка "назад" не нужна, вы должны использовать контроллеры modal view, чтобы иметь "push from bottom" / "flip" / "fade" /(≥3.2) переходы "page curl".


На стороне private метод -pushViewController:animated: вызывает недокументированный метод -pushViewController:transition:forceImmediate:, так, например, если вы хотите переключение между левыми и правыми, вы можете использовать

[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];

Вы не можете изменить переход "поп" таким образом, однако.

Ответ 21

См. мой более подробный ответ, используя только общедоступные методы:

Предотвратить анимацию при нажатии "Назад" на панели навигации?

... он несовершенен (это означает повторную реализацию некоторого UINavigationController), но он не использует никаких частных методов, и он работает.

Ответ 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.

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

Ответ 25

Я знаю, что эта ветка старая, но я решил поставить свои два цента. Вам не нужно создавать собственную анимацию, есть простой (возможно, хакерский) способ сделать это. Вместо использования push создайте новый контроллер навигации, сделайте новый контроллер представления корневым контроллером представления этого контроллера nav, а затем представьте контроллер nav из исходного контроллера nav. Подарок легко настраивается со многими стилями, и нет необходимости создавать собственную анимацию.

Например:

UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)

И вы можете изменить стиль презентации, как вы хотите.

Ответ 26

Я нашел мягко рекурсивный способ сделать это, который работает для моих целей. У меня есть переменная экземпляра BOOL, которую я использую, чтобы заблокировать нормальную всплывающую анимацию и заменить мое собственное неанимированное поп-сообщение. Первоначально переменная установлена ​​в НЕТ. Когда обратная кнопка нажата, метод delegate устанавливает ее в YES и отправляет новое неанимированное поп-сообщение на панель навигации, тем самым снова вызывая тот же метод делегата, на этот раз с переменной, установленной в YES. Если для переменной установлено значение YES, метод делегата устанавливает значение НЕТ и возвращает YES, чтобы разрешить неанимированную попку. После того, как второй вызов делегата возвращается, мы вернемся к первому, где возвращается NO, блокируя исходный анимированный поп! На самом деле это не так грязно, как кажется. Мой метод toPopItem выглядит следующим образом:

- (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;
    }
}

Работает для меня.