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

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

23 ответов


Я сделал следующее, И это работает прекрасно.. и это просто и легко понять..

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)

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

Для Нажмите:

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.

Для Толчка:

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

Для Pop:

[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
                         }];
[self.navigationController popViewControllerAnimated:NO];

для 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];

поп

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. , это также сохранит анимацию по умолчанию. Чтобы исправить это, вы должны перейти к раскадровке, нажмите Segue и снимите флажок "анимация". Но это ограничит ваше приложение для iOS 9.0 и выше (по крайней мере, когда я сделал это в Xcode 7).

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

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

}

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

для 2018, " окончательный ответ!"

преамбула скажем, вы новичок в разработке iOS, возможно, с Android. Очень запутанно, Apple предоставляет два (2) перехода, которые можно легко использовать. Это: "crossfade"и " flip". Теперь два самых бесполезных перехода во всех вычислениях - "crossfade" и "flip", которые никто не использует. Если вы хотите делать самые обычные переходы, например "соскальзывать", вам предстоит проделать огромный объем работы. Эта работа, объясняется в этом посте!

  • во-первых, если вы хотите использовать один из двух анимов, которые предоставляет Apple (crossfade, flips), это просто - используйте решение @PeterDeWeese выше.

  • во-вторых, есть старый быстрый-и-грязный CATransition quickfix. это объясняется подробно здесь. Это действительно не работает и не является реалистичным решением.

в противном случае, удивительно, вы должны приложить усилия, чтобы сделать полный пользовательский переход.

повторю:

даже если вы просто хотите самый простой, наиболее распространенный, переход перемещения/перемещения, к лучшему или худшему, вам нужно реализовать полный пользовательский переход.

вот как это сделать он. ..

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

  1. вам нужен собственный bool, как popStyle . (Он появляется или исчезает?)

  2. необходимо указать transitionDuration (тривиальный) и основной вызов,animateTransition

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

  4. пример ниже делает простой шаг-за переездом-офф

  5. в своем animatePush и animatePop процедуры. Вы должны получить "from view "и"to view". (Как показано в Примере.)

  6. и самое главное, что вы должны сделать, это, действительно, addSubview для нового представления "кому".

  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. Используйте его в контроллере view.

обратите внимание, что вы только сделать это в контроллере" первого " вида.

ты поп на вершине, ничего не делать. Простой.

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

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

это "так просто": O


использование частных вызовов-плохая идея, так как 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+ transitioning API. Я реализовал это для iOS 10 С Swift 3.

это довольно просто объединить это с how 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 экземпляр через его initializer. Я делаю это так, что в 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 view будут быть иными. Когда вы находитесь в операции толчка, для просмотра контроллер будет один толкают. Когда вы находитесь в операции pop, контроллер для просмотра будет тем, который переходит, а контроллер из вида будет тем, который выскакивает.

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

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

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

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

для операции push, которая требует первой установки toViewController начало взгляда С на оси с экрана, добавляя это как subview 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)
})

операция pop в основном обратная. Добавьте toViewController в качестве подвида containerView, и анимировать от fromViewController вправо, как вы анимировать в toViewController слева:

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
                toViewController.view.frame = containerView.bounds
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

вот суть всего swift файл:

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


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


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

проблема заключается в том, что представление буквально удаляет представление или выскакивает поверх текущего, поэтому исчезание не работает. Решение, к которому я пришел участвовал в принятии моего нового представления и добавлении его в качестве подвида к текущему верхнему виду в стеке UIViewController. Я добавляю его с Альфой 0, затем делаю crossfade. Когда последовательность анимации заканчивается, я нажимаю вид на стек, не анимируя его. Затем я возвращаюсь к старому topView и убираю вещи, которые я изменил.

его немного сложнее, чем это, потому что у вас есть navigationItems вы должны настроить, чтобы сделать переход выглядеть правильно. Кроме того, если вы это сделаете любое вращение, вы должны настроить размеры кадра, как вы добавляете представления в качестве подвидов, чтобы они правильно отображались на экране. Вот некоторые из кодов, которые я использовал. Я подкласс 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];
}

кроме того, вот моя поп-рутина.

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


 }
}

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

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


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

Для Pop:

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

на основе jordanperry ответ обновлено для swift 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)

используя ответ iJordan в качестве вдохновения, почему бы просто не создать Категорию на UINavigationController для использования во всем вашем приложении вместо копирования/вставки этого кода анимации повсюду?

UINavigationController+Анимация.h

@interface UINavigationController (Animation)

- (void) pushViewControllerWithFlip:(UIViewController*) controller;

- (void) popViewControllerWithFlip;

@end

UINavigationController+Анимация.м

@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 + анимацию.H файл и назовите его нормально:

[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];

[self.navigationController popViewControllerWithFlip];

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

вы можете использовать различные предопределенные анимации для пуш и поп действия, такие как салфетки, Fade, куб, Карусель, масштабирование и так далее.


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

Для Нажмите:

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

Теперь вы можете использовать UIView.transition. Обратите внимание, что animated:false. Это работает с любым вариантом перехода, pop, 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)
}

очень просто

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

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

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

Я не знаю, каким образом вы можете изменить анимацию перехода публично.

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


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

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

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


из примера приложения, проверить этот вариант. 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.м:

#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

Я нашел слегка рекурсивный способ сделать это, который работает для моих целей. У меня есть переменная экземпляра BOOL, которую я использую, чтобы заблокировать обычную анимацию popping и заменить мое собственное не анимированное поп-сообщение. Переменная изначально имеет значение NO. Когда кнопка "Назад" нажата, метод делегата устанавливает его в да и отправляет новое не анимированное pop-сообщение на панель навигации, тем самым вызывая тот же метод делегата снова, на этот раз с переменной, установленной в да. С переменной установлено значение YES, метод делегата устанавливает его в НЕТ и возвращает да, чтобы разрешить не анимированный pop. После возвращения второго вызова делегата мы возвращаемся в первый, где NO возвращается, блокируя оригинальный анимированный pop! На самом деле все не так грязно, как кажется. Мой метод 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;
    }
}

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