it-swarm.com.ru

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

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

Правка 2018

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

204
Jab

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

Для 2018, ошибка 2019, "окончательный ответ!"

ПреамбулаСкажем, вы новичок в разработке для iOS, возможно, из Android. Смущает, что Apple предоставляет два (и только два) перехода, которые можно легко использовать. Это: «кроссфейд» и «флип». Теперь два самых бесполезных перехода - это «кроссфейд» и «флип»! Никто не знает, почему Apple предоставила эти два бесполезных перехода в качестве встроенных переходов! Итак: удивительно, если вы хотите выполнить самые обычные переходы, например, «сдвинуться с места», вам нужно выполнить ОГРОМНОЕ количество работы. Эта работа, объясняется в этом посте.

  • Примечание 1: чтобы использовать один из двух вариантов, предоставляемых Apple (кроссфейд, флипс), посмотрите ответ @PeterDeWeese на этой странице.

  • Примечание 2: несколько лет назад произошел быстрый и грязный квазипереход CATransition. Это не работает.

Итак, чтобы повторить:

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

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

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

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

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

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

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

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

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

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

С тем, что вы вставляете на top, делайте ничто. Легко.

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

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

"Это так просто."

18
Fattie

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

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)
258
Magnus

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

Для толчка:

MainView *nextView=[[MainView alloc] init];
[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];

Для поп:

[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];


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

Для 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];
256
jordanperry

для толчка

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

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

Некоторые sidenotes:

Вы можете сделать это также с Segue, просто внедрите это в prepareForSegue или shouldPerformSegueWithIdentifier. Однако , это также сохранит анимацию по умолчанию. Чтобы это исправить, вам нужно перейти на раскадровку, щелкнуть «Segue» и снять флажок «Animates». Но это ограничит ваше приложение для IOS 9.0 и выше (по крайней мере, когда я сделал это в Xcode 7).

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

self.navigationController?.popViewControllerAnimated(false)

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

18
CularBytes

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

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewControllerAnimated(false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to Push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func Push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = type
        self.view.layer.addAnimation(transition, forKey: nil)
    }

}
15
Luca Davanzo

Использование личных звонков - плохая идея, так как 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];
10
nicktmro

Так как это лучший результат в 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») контроллеры представления «в» и «из» будут разными. Когда вы находитесь в операции Push, контроллером для просмотра будет тот, который будет выдвинут. Когда вы работаете в режиме pop, контроллер для просмотра будет тем, на который выполняется переход, и контроллером из представления будет тот, который выскочил.

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

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

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

Мой был действительно простой переход; Я хотел имитировать ту же анимацию, что обычно делает UINavigationController, но вместо анимации «следующая страница поверх», я хотел реализовать анимацию 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)
})

Вот Gist со всем файлом Swift:

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

8
Alan Zeino

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

6
eilas

Основано на 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)
5
vp2698

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

Для толчка:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        self.navigationController!.pushViewController(nextView, animated: false)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
    })

Для поп:

На самом деле я сделал это немного иначе, чем некоторые из приведенных выше ответов, но, поскольку я новичок в разработке Swift, это может быть неправильно. Я переопределил viewWillDisappear:animated: и добавил туда поп-код:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
    })

    super.viewWillDisappear(animated)
5
djbp

Я недавно пытался сделать что-то подобное. Я решил, что мне не нравится скользящая анимация 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];
}

Кроме того, вот моя популярная рутина.

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


 }
}

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

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

4
georryan

Используя ответ 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];
3
DiscDev

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

Для толчка:

  NextViewController *nextViewController = [[NextViewController alloc] init];

  // Shift the view to take the status bar into account 
  CGRect frame = nextViewController.view.frame;
  frame.Origin.y -= 20;
  frame.size.height += 20;
  nextViewController.view.frame = frame;

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
    [self.navigationController pushViewController:nextViewController animated:NO];
  }];

Для поп:

  int numViewControllers = self.navigationController.viewControllers.count;
  UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
    [self.navigationController popViewControllerAnimated:NO];
  }];}
2
head in the codes

Теперь вы можете использовать UIView.transition. Обратите внимание, что animated:false. Это работает с любой опцией перехода, pop, Push или заменой стека.

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

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

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
2
user2501116

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

Вы можете использовать различные предварительно определенные анимации для действий Push и pop, таких как Размах, Исчезать, Куб, Карусель, Zoom и так далее.

2
felginep

Смотрите мой ответ на этот вопрос чтобы узнать, как сделать это в гораздо меньшем количестве строк кода. Этот метод позволяет вам анимировать псевдо-«Push» нового контроллера представления любым удобным вам способом, и когда анимация завершена, он устанавливает Navigation Controller так же, как если бы вы использовали стандартный метод 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];
    }];
}
1
RobP

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

Если кнопка «назад» не нужна, вы должны использовать контроллеры модального вида чтобы получить «толчок снизу»/«перевернуть»/«исчезнуть»/(≥3.2) переходы "page curl".


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

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

Однако, вы не можете изменить «поп» переход.

0
kennytm

@ Лука Давандзо ответ в Swift 4.2

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewController(animated: false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to Push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func Push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = type
        self.view.layer.add(transition, forKey: nil)
    }

}
0
Serj Rubens

Из примера приложения проверьте этот вариант . 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;
}
0
johndpope

Понимая, что это старый вопрос. Я все еще хотел бы опубликовать этот ответ, так как у меня возникли проблемы с совмещением нескольких 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
0
Christian Otkjær

Просто используйте:

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];
0
Sergio Andreotti