Jak zmienić animacje Push i Pop w aplikacji nawigacyjnej

221

Mam aplikację do nawigacji i chcę zmienić animację animacji push i pop. Jak mam to zrobić?

Edytuj 2018

Odpowiedzi na to pytanie było wiele i minęło trochę czasu. Ponownie wybrałem odpowiedź na to, co uważam za najbardziej aktualne. Jeśli jest ktoś, kto myśli inaczej, daj mi znać w komentarzach

Ukłucie
źródło
25
Począwszy od iOS 7, istnieje do tego oficjalne API; patrz obsługa niestandardowej animacji przejścia UINavigationControllerDelegate . Jest też wideo na ten temat z WWDC 2013 .
Jesse Rusak,
Dodałem odpowiedź (poniżej) za zrobienie tego w Swift - natknąłem się na to pytanie, pytając o implementacje Swift, więc pomyślałem, że włączy się w moją kolejną implementację.
djbp
1
Aby uzyskać bardzo dobry samouczek z oficjalnym interfejsem API (iOS 7+), zobacz: bradbambara.wordpress.com/2014/04/11/…
nikolovski
1
@JesseRusak zaktualizował link do filmu WWDC 2013: developer.apple.com/videos/play/wwdc2013-218
Wojciech Rutkowski
1
Zmieniono moją zaakceptowaną odpowiedź chłopaki i dziewczęta. Mam nadzieję że to pomoże! GLHF
Jab

Odpowiedzi:

35

Jak zmienić animacje Push i Pop w aplikacji nawigacyjnej ...

Na rok 2019 „ostateczna odpowiedź!”

Preambuła:

Powiedz, że jesteś nowy w tworzeniu iOS. Mylące jest to, że Apple zapewnia dwa przejścia, z których można łatwo korzystać. Są to: „crossfade” i „flip”.

Ale oczywiście „crossfade” i „flip” są bezużyteczne. Nigdy nie są używane. Nikt nie wie, dlaczego Apple zapewnił te dwa bezużyteczne przejścia!

Więc:

Powiedz, że chcesz wykonać zwykłe, wspólne przejście, takie jak „slajd”. W takim przypadku musisz wykonać OGROMNĄ pracę! .

Ta praca została wyjaśniona w tym poście.

Wystarczy powtórzyć:

Zaskakujące: jeśli masz iOS, jeśli chcesz najprostszych, najczęstszych codziennych przejść (takich jak zwykły slajd), musisz wykonać całą pracę związaną z implementacją pełnego niestandardowego przejścia .

Oto jak to zrobić ...

1. Potrzebujesz niestandardowego UIViewControllerAnimatedTransitioning

  1. Potrzebujesz własnego bool popStyle. (Czy wyskakuje, czy wyskakuje?)

  2. Musisz dołączyć transitionDuration(trywialne) i główne połączenie,animateTransition

  3. W rzeczywistości musisz napisać dwie różne procedury dla wnętrza animateTransition. Jeden dla popychacza i jeden dla popu. Prawdopodobnie nazwij je animatePushi animatePop. W środku animateTransitionpo prostu przejdź popStyledo dwóch procedur

  4. Poniższy przykład przedstawia proste przeniesienie / przeniesienie

  5. W twoim animatePushi animatePoprutynie. Państwo musi uzyskać „z widoku” i „do widzenia”. (Jak to zrobić, pokazano w przykładzie kodu.)

  6. i musi addSubview za nowy „do” widzenia.

  7. i musisz zadzwonić completeTransitionpod koniec swojego anime

Więc ..

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

I wtedy ...

2. Użyj go w kontrolerze widoku.

Uwaga: co dziwne, musisz to zrobić tylko w „pierwszym” kontrolerze widoku. (Ten, który jest „pod spodem”).

Z tym, który wyskakujesz na wierzch , nic nie rób . Łatwo.

Twoja klasa ...

class SomeScreen: UIViewController {
}

staje się...

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

Otóż ​​to.

Naciśnij i pop dokładnie tak, jak zwykle, bez zmian. Popychać ...

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
          .instantiateViewController(withIdentifier: "nextScreenStoryboardID")
          as! NextScreen
navigationController?.pushViewController(n, animated: true)

i żeby to pop, możesz, jeśli chcesz, po prostu zrób to na następnym ekranie:

class NextScreen: TotallyOrdinaryUIViewController {
    
    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
        
        navigationController?.popViewController(animated: true)
    }
}

Uff


3. Skorzystaj również z innych odpowiedzi na tej stronie, które wyjaśniają, jak zastąpić AnimatedTransitioning

Przewiń do @AlanZeino i @elias, aby uzyskać więcej dyskusji na temat tego, jak AnimatedTransitioningw aplikacjach na iOS w dzisiejszych czasach!

Fattie
źródło
Doskonały! Jeśli chcę, aby gest przesuwania palcem w tył obsługiwał również to samo AnimatedTransitioning. Dowolny pomysł?
sam chi wen
dzięki @samchiwen - rzeczywiście to jest dokładnie to animatePushi animatePopto są dwa różne kierunki!
Fattie
268

Zrobiłem następujące i działa dobrze .. i jest prosty i łatwy do zrozumienia ..

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

I to samo dotyczy push…


Wersja 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)
Magnus
źródło
34
+1, to naprawdę najbardziej rozsądne rozwiązanie. Tylko drobna uwaga dla przyszłych gości: ta Animated:NOczęść jest niezbędna. Jeśli YESzostanie przekazany, animacje mieszają się i powodują śmieszne efekty.
DarkDust,
12
Najlepsze rozwiązanie do tej pory .. A dla początkujących nie zapomnij dołączyć QuartCore (#import <QuartzCore / QuartzCore.h>)
nomann
4
Jedyny problem, jaki mam z tym rozwiązaniem, to wywołanie viewDidAppear pchniętego kontrolera widoku wywoływane natychmiast po tym, jak wypchnę go bez animacji. Czy jest na to jakiś sposób?
Pedro Mancheno
9
Mój problem z tym kodem polega na tym, że każdy widok wydaje się migać na szary lub biały podczas przesuwania się lub wysuwania.
Chris
1
sprawdzone na iOS 7.1.2 i iOS 8.3 - ten kod działa dobrze, działa również dobrze dla metodysetViewControllers:
proff
256

W ten sposób zawsze udało mi się wykonać to zadanie.

Dla Push:

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

Dla popu:

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


Nadal otrzymuję wiele opinii, więc zamierzam go zaktualizować, aby używać bloków animacji, co jest zalecanym przez Apple sposobem robienia animacji.

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

Dla popu:

[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
                         }];
[self.navigationController popViewControllerAnimated:NO];
jordanperry
źródło
3
Dzięki za to. Ale pop jest wykonywany automatycznie przez UINavigationController. Jak zastąpić to zachowanie, aby móc wywołać niestandardową logikę pop?
Joshua Frank
1
@stuckj faktycznie to działa !!! po prostu trzeba zastąpić superprzezself.navigationController
holierthanthou84
Czy jest jakiś sposób, aby uzyskać slajd z lewej zamiast domyślnej z prawej?
shim
Pierwszy nie pokazuje wcale nowego widoku. Drugi nie pokazuje animacji. Bardzo zła odpowiedź! iOS 7.
Dmitry
2
dlaczego podklasujesz UIViewControllernazwę bez części „ViewController”. Ta nazwa jest bardziej odpowiednia dla UIView.
user2159978
29

do pchnięcia

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

dla popu

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];
Przetrząsać
źródło
19

@Magnus odpowiedź, tylko wtedy dla 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)

Niektóre sidenotes:

Możesz to zrobić również za pomocą Segue, po prostu zaimplementuj to w prepareForSeguelub shouldPerformSegueWithIdentifier. Zachowa to jednak również domyślną animację. Aby to naprawić, musisz przejść do scenorysu, kliknąć Segue i odznaczyć pole „Animacje”. Ale to ograniczy twoją aplikację dla IOS 9.0 i nowszych (przynajmniej kiedy zrobiłem to w Xcode 7).

Robiąc w segue, ostatnie dwa wiersze należy zastąpić:

self.navigationController?.popViewControllerAnimated(false)

Mimo że ustawiłem fałsz, to w pewnym sensie to ignoruje.

CularBytes
źródło
Jak usunąć czarny kolor z tła na końcu animacji.
Madhu
Nie działa animacja kontrolera widoku push działa dla kontrolera widoku Pop
Mukul More
16

Pamiętaj, że w Swift , rozszerzenie są zdecydowanie znajomych!

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

}
Luca Davanzo
źródło
11

Korzystanie z połączeń prywatnych jest złym pomysłem, ponieważ Apple nie zatwierdza już aplikacji, które to robią. Może mógłbyś spróbować:

//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
źródło
Działa tylko „połowa” - nie rozwiąże trudniejszego problemu z animacjami pop.
Adam
Bardziej podoba mi się to rozwiązanie i tak, działa. Korzystanie z metod prywatnych na pewno Cię odrzuci.
Benjamin Intal
@nicktmro, które jest prywatnym połączeniem API. Nie zauważyłem żadnego.
Franklin
@Franklin jakiś czas temu była tutaj dyskusja na temat używania, -pushViewController:transition:forceImmediate:co byłoby złym pomysłem.
nicktmro
9

Ponieważ jest to najlepszy wynik w Google, pomyślałem, że podzielę się tym, co uważam za najbardziej rozsądny sposób; który ma używać przejściowego interfejsu API systemu iOS 7+. Zaimplementowałem to dla iOS 10 z Swift 3.

Całkiem łatwo jest połączyć to z UINavigationControlleranimacją między dwoma kontrolerami widoku, jeśli utworzysz podklasę UINavigationControlleri zwrócisz instancję klasy zgodnej z UIViewControllerAnimatedTransitioningprotokołem.

Na przykład tutaj jest moja UINavigationControllerpodklasa:

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

}

Widać, że ustawiłem wartość UINavigationControllerDelegatesamą w sobie, a w rozszerzeniu mojej podklasy zaimplementowałem metodę, UINavigationControllerDelegatektóra umożliwia zwrócenie niestandardowego kontrolera animacji (tj NavigationControllerAnimation.). Ten niestandardowy kontroler animacji zastąpi dla ciebie standardową animację.

Prawdopodobnie zastanawiasz się, dlaczego przekazuję operację do NavigationControllerAnimationinstancji za pomocą jej inicjatora. Robię to, aby przy NavigationControllerAnimationimplementacji UIViewControllerAnimatedTransitioningprotokołu wiedziałem, co to jest operacja (tj. „Push” lub „pop”). Pomaga to wiedzieć, jaki rodzaj animacji powinienem zrobić. W większości przypadków chcesz wykonać inną animację w zależności od operacji.

Reszta jest dość standardowa. Zaimplementuj dwie wymagane funkcje w UIViewControllerAnimatedTransitioningprotokole i animuj, jak chcesz:

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

Ważne jest, aby pamiętać, że dla każdego rodzaju operacji (np. „Push” lub „pop”) kontrolery widoku i od będą różne. Podczas operacji wypychania kontroler to view zostanie wypchnięty. Podczas operacji pop kontroler do widoku będzie tym, który jest przenoszony, a kontroler z widoku będzie tym, który jest otwierany.

Ponadto tokontroler widoku należy dodać jako widok podrzędny containerVieww kontekście przejścia.

Po zakończeniu animacji musisz zadzwonić transitionContext.completeTransition(true). Jeśli robisz interaktywną przejścia, trzeba będzie dynamicznie zwróci Boolsię completeTransition(didComplete: Bool), w zależności czy przejście jest zakończona pod koniec animacji.

Na koniec ( czytanie opcjonalne ) możesz zobaczyć, jak wykonałem przejście, nad którym pracowałem. Ten kod jest nieco bardziej zhackowany i napisałem go dość szybko, więc nie powiedziałbym, że to świetny kod animacji, ale wciąż pokazuje, jak wykonać część animacji.

Moje było naprawdę prostym przejściem; Chciałem naśladować tę samą animację, którą zwykle robi UINavigationController, ale zamiast animacji „następna strona powyżej”, chciałem zaimplementować animację 1: 1 starego kontrolera widoku w tym samym czasie, co nowy widok pojawia się kontroler. Powoduje to, że dwa kontrolery widoku wyglądają tak, jakby były ze sobą połączone.

W przypadku operacji wypychania, która wymaga najpierw ustawienia początku toViewControllerwidoku na ekranie poza osią x, dodania go jako widoku podrzędnego containerViewi animowania go na ekranie poprzez ustawienie go origin.xna zero. Jednocześnie animuję fromViewControllerwidok, odsuwając go origin.xod ekranu:

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

Operacja pop jest w zasadzie odwrotna. Dodaj toViewControllerwidok podrzędny containerViewi animuj od fromViewControllerprawej, animując toViewControllerod lewej:

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

Oto sedno z całym szybkim plikiem:

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

Alan Zeino
źródło
Wspaniały!. Chciałem po prostu animować w przeciwnym kierunku. Sprawdziłem kilka innych rozwiązań, ale wszystkie migają na lewym i prawym ekranie. Wygląda na to, że nie można z nimi usunąć domyślnej animacji zmiany alfa. Tylko to rozwiązanie rozwiązało problem.
beshio
tak, to jedyne poprawne nowoczesne rozwiązanie. (Nie mam nic przeciwko, ale jest dokładnie takie samo, jak rozwiązanie, które
wpisałem
@AlanZeino Co zrobić, jeśli w tym samym ViewController potrzebujesz różnych animacji dla różnych kliknięć przycisków? Tak więc, dla button1 potrzebujesz animacji rozpuszczania, dla button2 potrzebujesz domyślnego przejścia.
jzeferino
7

Tam są UINavigationControllerDelegate i UIViewControllerAnimatedTransitioning tam możesz zmienić animację na cokolwiek chcesz.

Na przykład jest to pionowa animacja pop dla 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())
    })
}

}

I wtedy

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    if operation == .Pop {
        return PopAnimator()
    }
    return nil;
}

Przydatny samouczek https://www.objc.io/issues/5-ios7/view-controller-transitions/

eilas
źródło
6

Na podstawie odpowiedzi zaktualizowanej do szybkiego 4jordanperry

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

Dla popu

UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)
vp2698
źródło
5

Oto jak zrobiłem to samo w Swift:

Dla Push:

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

Dla popu:

Zrobiłem to trochę inaczej niż niektóre z powyższych odpowiedzi - ale ponieważ jestem nowy w rozwoju Swift, może to nie być właściwe. Zastąpiłem viewWillDisappear:animated:i dodałem tam kod pop:

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

    super.viewWillDisappear(animated)
djbp
źródło
5

Odpowiedź Lucy Davanzo w 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)
    }

}
Serj Rubens
źródło
4

Niedawno próbowałem zrobić coś podobnego. Uznałem, że nie podoba mi się przesuwana animacja UINavigationController, ale nie chciałem też wykonywać animacji, które UIView daje ci curl lub coś w tym rodzaju. Chciałem zrobić przenikanie między widokami, kiedy naciskam lub pop.

Problem polega na tym, że widok dosłownie usuwa widok lub przesuwa go nad bieżącym, więc zanikanie nie działa. Rozwiązanie, na które wpadłem, polegało na wzięciu mojego nowego widoku i dodaniu go jako widoku podrzędnego do bieżącego widoku z góry na stosie UIViewController. Dodaję go z cyfrą 0, a następnie przechodzę w płynne przejście. Kiedy sekwencja animacji zakończy się, pcham widok na stos bez animowania go. Następnie wracam do starego topView i sprzątam rzeczy, które zmieniłem.

Jest to trochę bardziej skomplikowane, ponieważ masz elementy nawigacyjne, które musisz dostosować, aby przejście wyglądało poprawnie. Ponadto, jeśli wykonujesz obrót, musisz dostosować rozmiary ramek, dodając widoki jako widoki podrzędne, aby były poprawnie wyświetlane na ekranie. Oto część kodu, którego użyłem. Podklasowałem UINavigationController i zastąpiłem metody push i 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];

 }

Jak wspomniałem w kodzie, zawsze mam element przycisku lewego paska, więc nie sprawdzam, czy nie ma go przed umieszczeniem go w tablicy, którą przekazuję jako kontekst dla delegata animacji. Jeśli to zrobisz, możesz to sprawdzić.

Problem, który znalazłem, polegał na tym, że awaria metody delegowania nie spowoduje awarii programu. To po prostu powstrzymuje delegata przed ukończeniem, ale nie pojawia się żadne ostrzeżenie.
Ponieważ więc przeprowadzałem czyszczenie w ramach tej procedury delegowania, powodowało to dziwne zachowanie wizualne, ponieważ nie kończyło czyszczenia.

Przycisk Wstecz, który tworzę, wywołuje metodę „goBack”, a ta metoda po prostu wywołuje procedurę pop.

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

Oto moja popowa rutyna.

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


 }
}

To prawie wszystko. Będziesz potrzebować dodatkowego kodu, jeśli chcesz zastosować rotacje. Musisz ustawić rozmiar ramki swoich widoków, które dodajesz jako widoki podrzędne, zanim je pokażesz, w przeciwnym razie napotkasz problemy z orientacją poziomą, ale ostatnim razem, gdy widziałeś poprzedni widok, był portret. Więc dodajesz go jako widok pomocniczy i przenikasz, ale pokazuje się on jako portret, a potem, gdy pop bez animacji, ten sam widok, ale ten, który jest na stosie, teraz jest poziomy. Całość wygląda trochę funky. Implementacja rotacji dla każdego jest trochę inna, więc nie dodałem tutaj mojego kodu.

Mam nadzieję, że to pomaga niektórym ludziom. Szukałem czegoś takiego i nie mogłem nic znaleźć. Nie sądzę, że to idealna odpowiedź, ale w tym momencie działa dla mnie naprawdę dobrze.

georryan
źródło
Choć jest to godne podziwu, nie jest to teraz rozwiązanie 7 lat później!
Fattie
Masz rację. Ta odpowiedź pochodzi z 2011 roku. Wtedy to zadziałało, ale od tego czasu wiele się zmieniło. =)
georryan
4

Możesz teraz użyć UIView.transition. Zauważ, że animated:false. Działa to z dowolną opcją przejścia, pop, push lub zamianą stosu.

if let nav = self.navigationController
{
    UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
        _ = nav.popViewController(animated:false)
    }, completion:nil)
}
Peter DeWeese
źródło
1
@Fattie, ta konkretna metoda działa tylko z dowolnymi standardowymi animacjami, takimi jak klapki i loki wymienione w developer.apple.com/documentation/uikit/uiviewanimationoptions
Peter DeWeese
3

Korzystając z odpowiedzi iJordan jako inspiracji, dlaczego po prostu nie utworzyć kategorii na UINavigationController do użycia w całej aplikacji zamiast kopiować / wklejać ten kod animacji w dowolnym miejscu?

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

Następnie wystarczy zaimportować plik UINavigationController + Animation.h i wywołać go normalnie:

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

[self.navigationController popViewControllerWithFlip];
DiscDev
źródło
Sprytny. Ale dlaczego nie dodać metod push / pop, które wykorzystują argument UIViewAnimationTransition zamiast twardego kodu do flipFromRight?
Jef
@Jeżeli są to metody wygody - w ten sposób implementator nie musi pamiętać, którą wartość UIViewAnimationTransition przekazać dla każdego określonego typu animacji, po prostu wywołuje metodę z „angielską” nazwą tego, co chce osiągnąć.
DiscDev,
@Jef również twoja sugestia jest zdecydowanie poprawna - gdybym nadal używał celu-c i potrzebowałbym obsługiwać wiele stylów przejścia (zdecydowanie nie jest to zalecane, ponieważ wiele różnych stylów przejścia pomyli użytkowników), miałbym 1 metodę, która przyjmuje typ UIViewAnimationTransition, a następnie kilka wygodnych metod ułatwiających programowanie.
DiscDev,
3

To jest bardzo proste

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
użytkownik2501116
źródło
Witamy w StackOverflow: jeśli publikujesz kod, XML lub próbki danych, zaznacz te wiersze w edytorze tekstu i kliknij przycisk „próbki kodu” ({}) na pasku narzędzi edytora lub użyj Ctrl + K na klawiaturze, aby ładnie sformatować i składnia to podkreślają!
WhatsThePoint,
2

Spójrz na ADTransitionController , spadek w zastępstwie UINavigationController z niestandardowymi animacjami przejścia (jego interfejs API pasuje do interfejsu API UINavigationController), który stworzyliśmy w Applidium.

Możesz używać różnych predefiniowanych animacji dla akcji push i pop, takich jak Swipe , Fade , Cube , Carrousel , Zoom i tak dalej.

Felginep
źródło
2

Chociaż wszystkie odpowiedzi tutaj są świetne, a większość działa bardzo dobrze, istnieje nieco prostsza metoda, która osiąga ten sam efekt ...

Dla Push:

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

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

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

Dla popu:

  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];
  }];}
wpisz kody
źródło
Wystąpi awaria podczas otwierania do kontrolera widoku root.
Abdullah Umer
1

Nie wiem, w jaki sposób możesz publicznie zmienić animację przejścia.

Jeśli przycisk „wstecz” nie jest konieczny, należy użyć modalnych kontrolerów widoku, aby uzyskać przejścia „push from bottom” / „flip” / „fade” / (≥3,2) „zawinięcie strony”.


Po stronie prywatnej metoda -pushViewController:animated:wywołuje metodę nieudokumentowaną -pushViewController:transition:forceImmediate:, więc np. Jeśli chcesz przejść z przejścia z lewej na prawą, możesz użyć

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

Nie można jednak zmienić przejścia „pop” w ten sposób.

kennytm
źródło
1

Zobacz moją odpowiedź na to pytanie, aby dowiedzieć się, jak to zrobić w znacznie mniejszej liczbie wierszy kodu. Ta metoda pozwala animować pseudo „Push” nowego kontrolera widoku w dowolny sposób, a gdy animacja jest zakończona, konfiguruje kontroler nawigacji tak, jakbyś użył standardowej metody Push. Mój przykład pozwala animować wsuwanie z lewej lub z prawej strony. Kod powtórzony tutaj dla wygody:

-(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];
    }];
}
RobP
źródło
0

W przykładowej aplikacji sprawdź tę odmianę. 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;
}
johndpope
źródło
0

Po prostu użyj:

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];
Sergio Andreotti
źródło
0

Uświadomienie sobie tego jest starym pytaniem. Nadal chciałbym opublikować tę odpowiedź, ponieważ miałem pewne problemy viewControllersz kilkoma proponowanymi odpowiedziami. Moim rozwiązaniem jest podklasowanie UINavigationControlleri zastępowanie wszystkich metod pop i 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
Christian Otkjær
źródło
0

Wiem, że ten wątek jest stary, ale pomyślałem, że wpłacę dwa centy. Nie musisz tworzyć niestandardowej animacji, istnieje prosty (może hacky) sposób na zrobienie tego. Zamiast używać wypychania, utwórz nowy kontroler nawigacyjny, ustaw nowy kontroler widoku jako główny kontroler widoku tego kontrolera nawigacyjnego, a następnie zaprezentuj kontroler nawigacyjny z oryginalnego kontrolera nawigacyjnego. Prezent można łatwo dostosować za pomocą wielu stylów i nie trzeba tworzyć niestandardowej animacji.

Na przykład:

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

I możesz zmienić styl prezentacji, jak chcesz.

Ethan Zhao
źródło
-1

Znalazłem lekko rekurencyjny sposób na zrobienie tego, co działa dla moich celów. Mam zmienną instancji BOOL, której używam do blokowania normalnej animacji popping i zastępowania własnej nie-animowanej wiadomości pop. Zmienna jest początkowo ustawiona na NIE. Po naciśnięciu przycisku Wstecz metoda delegowania ustawia go na TAK i wysyła nową nie animowaną wiadomość pop na pasek nawigacyjny, w ten sposób ponownie wywołując tę ​​samą metodę delegowania, tym razem ze zmienną ustawioną na TAK. Gdy zmienna jest ustawiona na TAK, metoda delegowania ustawia ją na NIE i zwraca TAK, aby umożliwić wystąpienie nie animowanego popu. Po powrocie drugiego połączenia delegata wracamy do pierwszego, gdzie zwracane jest NIE, blokując oryginalny animowany pop! W rzeczywistości nie jest tak niechlujny jak się wydaje. Moja metoda powinna powinna wyglądać następująco:

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

Pracuje dla mnie.

CharlieMezak
źródło