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


221

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

Изменить 2018

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


25
Начиная с iOS 7, для этого есть официальный API; см. пользовательскую поддержку анимации перехода UINavigationControllerDelegate . Об этом также есть видео WWDC 2013 .
Джесси Русак

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

1
Очень хороший учебник с официальным API (iOS 7+) см. По адресу
nikolovski

1
@JesseRusak обновил ссылку на WWDC 2013 Видео: developer.apple.com/videos/play/wwdc2013-218
Войцех Рутковски

1
Изменился мой принятый ответ, ребята и девчонки. Надеюсь это поможет! GLHF
Jab

Ответы:


35

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

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

Преамбула:

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

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

Так:

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

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

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

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

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

1. Вам нужен кастом UIViewControllerAnimatedTransitioning

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

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

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

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

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

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

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

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

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

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 в эти дни!


Превосходно! Если я хочу, чтобы навигационный жест назад поддерживал ту же AnimatedTransitioning тоже. Любая идея?
Сам Чи Вэнь

спасибо @samchiwen - действительно, это именно то, что animatePushи animatePopесть .. два разных направления!
Толстяк

268

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

CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];

И то же самое для толчка ..


Версия Swift 3.0:

let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)

34
+1, это действительно самое вменяемое решение. Небольшая заметка для будущих посетителей: Animated:NOэто жизненно важно. Если YESоно пропущено, анимация смешивается и вызывает забавные эффекты.
DarkDust

12
Лучшее решение на данный момент .. И для начинающих, не забудьте включить QuartCore (#import <QuartzCore / QuartzCore.h>)
nomann

4
Единственная проблема, с которой я столкнулся в этом решении, заключается в том, что viewDidAppear объекта push-viewcontroller вызывается сразу после его нажатия без анимации. Есть ли способ обойти это?
Педро Манчено

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

1
проверено на iOS 7.1.2 и iOS 8.3 - этот код работает нормально и работает нормально для методаsetViewControllers:
proff

256

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

Для толчка:

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

Для поп:

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

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


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

Для толчка:

MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [self.navigationController pushViewController:nextView animated:NO];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
                         }];

Для поп:

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

3
Спасибо за это. Но популярность выполняется автоматически с помощью UINavigationController. Как вы переопределяете это поведение, чтобы вызывать свою собственную логику поп-музыки?
Джошуа Франк

1
@ Stuckj на самом деле это работает! Вы просто должны заменить superнаself.navigationController
holierthanthou84

Есть ли способ получить слайд слева вместо слайда по умолчанию справа?
Шим

Первый вообще не показывает новый вид. Второй не показывает анимацию. Очень плохой ответ! iOS 7.
Дмитрий

2
почему вы дали UIViewControllerподклассу имя без части "ViewController". Это имя больше подходит для UIView.
user2159978

29

для толчка

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

19

@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, это как бы игнорирует это.


Как убрать черный цвет фона в конце анимации.
Мадху,

Не работает для контроллера push view Анимация работает для контроллера Pop view
Mukul More

16

Помните, что в 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'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)
    }

}

11

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

Это только «половина» работает - это не решит более сложную проблему поп-анимации.
Адам

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

@nicktmro, который является частным вызовом API. Я не заметил никого.
Франклин

@Franklin здесь недавно была дискуссия об использовании, -pushViewController:transition:forceImmediate:которая была бы плохой идеей.
nicktmro

9

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

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

Когда ваша анимация завершится, вы должны позвонить transitionContext.completeTransition(true). Если вы делаете интерактивный переход, вам придется динамически возвращать a 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)
})

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

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


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

да, это единственное правильное современное решение. (Я не против, но это точно то же самое, что и решение, которое я напечатал ниже! :))
Толстяк

@AlanZeino Что если внутри одного и того же ViewController вам понадобятся разные анимации для разных нажатий кнопок? Итак, для button1 вам нужна анимация растворения, для button2 вам нужен переход по умолчанию.
Jzeferino

7

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

На основании ответа, обновленного для swift 4jordanperry

Для толчка 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

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

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

}

4

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


 }
}

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

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


Хотя это достойно восхищения, честно говоря, это не решение сейчас, 7 лет спустя!
Толстяк

Ты прав. Этот ответ был с 2011 года. Он работал тогда, но с тех пор многое изменилось. =)
георриан

4

Теперь вы можете использовать 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)
}

1
@ Толстяк, этот конкретный метод работает только со всеми стандартными анимациями, такими как сальто и кудри, перечисленные в developer.apple.com/documentation/uikit/uiviewanimationoptions
Питер ДеВиз

3

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

Умная. Но почему бы не добавить методы push / pop, которые принимают аргумент UIViewAnimationTransition, а не hardcode для flipFromRight?
Джеф

@Jef это удобные методы - таким образом, разработчику не нужно запоминать, какое значение UIViewAnimationTransition передать для каждого конкретного типа анимации, они просто вызывают метод с «английским» именем того, что они хотят выполнить.
DiscDev

@Jef, ваше предложение определенно верно - если бы я все еще использовал target-c и мне нужно было поддерживать много стилей перехода (определенно не рекомендуется, так как много разных стилей перехода будут сбивать пользователей с толку), у меня был бы 1 метод, который принимает тип UIViewAnimationTransition, тогда несколько удобных способов сделать разработку проще.
DiscDev

3

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

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

Добро пожаловать в StackOverflow: если вы публикуете код, XML или образцы данных, выделите эти строки в текстовом редакторе и нажмите кнопку «примеры кода» ({}) на панели инструментов редактора или используйте Ctrl + K на клавиатуре для удобного форматирования и синтаксис выделить его!
WhatsThePoint

2

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

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


2

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

Для толчка:

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

Это произойдет сбой при переходе к корневому представлению контроллера.
Абдулла Умер

1

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

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


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

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

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


1

Посмотрите мой ответ на этот вопрос, чтобы узнать, как сделать это в гораздо меньшем количестве строк кода. Этот метод позволяет вам анимировать псевдо-«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];
    }];
}

0

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

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

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

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

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

Например:

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

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


-1

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.