Jak zaprezentować kontroler widoku od prawej do lewej w iOS przy użyciu Swift

83

Używam presentViewController do zaprezentowania nowego ekranu

let dashboardWorkout = DashboardWorkoutViewController()
presentViewController(dashboardWorkout, animated: true, completion: nil)

Przedstawia nowy ekran od dołu do góry, ale chcę, aby był prezentowany od prawej do lewej bez używania UINavigationController.

Używam Xib zamiast Storyboard, więc jak mogę to zrobić?

Umair Afzal
źródło
w skrócie, pełna odpowiedź: stackoverflow.com/a/48081504/294884
Fattie

Odpowiedzi:

160

Nie ma znaczenia, czy jest xiblub storyboard, że używasz. Zwykle przejście z prawej do lewej strony jest używane, gdy wepchniesz kontroler widoku do kontrolera prezentera UINavigiationController.

AKTUALIZACJA

Dodano funkcję pomiaru czasu kCAMediaTimingFunctionEaseInEaseOut

Przykładowy projekt z implementacją Swift 4 dodaną do GitHub


Swift 3 i 4.2

let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
present(dashboardWorkout, animated: false, completion: nil)


ObjC

CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.5;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
[transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[self.view.window.layer addAnimation:transition forKey:kCATransition];
[self presentViewController:dashboardWorkout animated:false completion:nil];


Swift 2.x

let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
transition.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
view.window!.layer.addAnimation(transition, forKey: kCATransition)
presentViewController(dashboardWorkout, animated: false, completion: nil)

Wygląda na to, że animatedparametr w presentViewControllermetodzie nie ma większego znaczenia w przypadku niestandardowego przejścia. To może mieć dowolną wartość, albo truealbo false.

mrvincenzo
źródło
2
Próbowałem użyć kodu Swift3 w metodzie perform () niestandardowego UIStoryboardSegue, problem polega na tym, że przejście odbywa się na tym samym widoku, a następnie pojawia się drugi widok. Jakieś pomysły na ten temat?
Devarshi
2
uObecnie, gdy używam tej metody, zanikanie źródła VC Jak mogę usunąć efekt zanikania i sprawić, by wyglądał jak animacja wypychania?
Umair Afzal
1
@UmairAfzal To kolor okna, musisz zmienić kolor okna, aby tego uniknąć.
Abdul Rehman
Jak skutecznie wykorzystać to przejście z UIModalPresentationStyle.overCurrentContext w Swift 3
Mamta
3
Jeśli ustawisz „animowany” jako „prawdziwy”, będzie to również miało niewielką animację w górę. Zalecam ustawienie go na „false”
Rebeloper
68

Pełny kod prezentowania / zwolnienia, Swift 3

extension UIViewController {

    func presentDetail(_ viewControllerToPresent: UIViewController) {
        let transition = CATransition()
        transition.duration = 0.25
        transition.type = kCATransitionPush
        transition.subtype = kCATransitionFromRight
        self.view.window!.layer.add(transition, forKey: kCATransition)

        present(viewControllerToPresent, animated: false)
    }

    func dismissDetail() {
        let transition = CATransition()
        transition.duration = 0.25
        transition.type = kCATransitionPush
        transition.subtype = kCATransitionFromLeft
        self.view.window!.layer.add(transition, forKey: kCATransition)

        dismiss(animated: false)
    }
}
Дмитрий Боровиков
źródło
2
Proszę być bardziej konkretnym. Wyjaśnij więcej !
Emm
3
Głosowanie za twoją odpowiedzią, ponieważ ma ona również funkcję zwolnienia
Rebeloper
1
Jeden z najlepszych !! 👌
Shyam
44

Przeczytaj wszystkie odpowiedzi i nie widzisz prawidłowego rozwiązania. Właściwym sposobem jest utworzenie niestandardowego UIViewControllerAnimatedTransitioning dla prezentowanego delegata VC.

Zakłada więc wykonanie większej liczby kroków, ale wynik jest bardziej konfigurowalny i nie ma żadnych skutków ubocznych, takich jak przejście z widoku razem z prezentowanym widokiem.

Załóżmy więc, że masz ViewController i istnieje metoda prezentacji

var presentTransition: UIViewControllerAnimatedTransitioning?
var dismissTransition: UIViewControllerAnimatedTransitioning?    

func showSettings(animated: Bool) {
    let vc = ... create new vc to present

    presentTransition = RightToLeftTransition()
    dismissTransition = LeftToRightTransition()

    vc.modalPresentationStyle = .custom
    vc.transitioningDelegate = self

    present(vc, animated: true, completion: { [weak self] in
        self?.presentTransition = nil
    })
}

presentTransitioni dismissTransitionsłuży do animowania kontrolerów widoku. Dlatego dostosowujesz ViewController do UIViewControllerTransitioningDelegate:

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return presentTransition
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return dismissTransition
    }
}

Więc ostatnim krokiem jest utworzenie niestandardowego przejścia:

class RightToLeftTransition: NSObject, UIViewControllerAnimatedTransitioning {
    let duration: TimeInterval = 0.25

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)!

        container.addSubview(toView)
        toView.frame.origin = CGPoint(x: toView.frame.width, y: 0)

        UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: {
            toView.frame.origin = CGPoint(x: 0, y: 0)
        }, completion: { _ in
            transitionContext.completeTransition(true)
        })
    }
}

class LeftToRightTransition: NSObject, UIViewControllerAnimatedTransitioning {
    let duration: TimeInterval = 0.25

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        let fromView = transitionContext.view(forKey: .from)!

        container.addSubview(fromView)
        fromView.frame.origin = .zero

        UIView.animate(withDuration: duration, delay: 0, options: .curveEaseIn, animations: {
            fromView.frame.origin = CGPoint(x: fromView.frame.width, y: 0)
        }, completion: { _ in
            fromView.removeFromSuperview()
            transitionContext.completeTransition(true)
        })
    }
}

W tym widoku kodu kontroler jest prezentowany w bieżącym kontekście, możesz od tego momentu dokonać dostosowań. Możesz również zauważyć, że niestandardowe UIPresentationControllerjest również przydatne (przekazuj użycie UIViewControllerTransitioningDelegate)

HotJard
źródło
Dobra robota! Przez chwilę próbowałem wszystkich odpowiedzi i było coś nie tak ... Nawet zagłosowałem za tym, który nie powinien być ...
Reimond Hill
kiedy odrzucasz prezentowane VC, czy po prostu nazywasz odrzucenie (animowane: prawda, zakończenie: zero) ???
Reimond Hill
@ReimondHill tak, używam tylko zwykłego zwolnieniaViewController
HotJard
To zdecydowanie najlepsze rozwiązanie. Tak, jest trochę bardziej skomplikowany, ale jest wytrzymały i nie pęka w różnych warunkach. Zaakceptowaną odpowiedzią jest włamanie.
mmh02
Działa doskonale na wszystkich urządzeniach z wyjątkiem iPhone 7+, 8+, X, XMax. Każdy pomysł, dlaczego? vc nie przyjmuje pełnej klatki.
Umair Afzal
14

Możesz również użyć niestandardowego przejścia.

Szybki 5

class SegueFromRight: UIStoryboardSegue {

    override func perform() {
        let src = self.source
        let dst = self.destination

        src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
        dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)

        UIView.animate(withDuration: 0.25,
               delay: 0.0,
               options: UIView.AnimationOptions.curveEaseInOut,
               animations: {
                    dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
            },
                   completion: { finished in
                    src.present(dst, animated: false, completion: nil)
        })
    }
}
Punkt fotonowy
źródło
Lepsza odpowiedź niż inna, jeśli jest używana w prezentacji w bieżącym kontekście
Varun Naharia
Po odrzuceniu tego niestandardowego przejścia nadal domyślnie przyjmuje oryginalny typ przejścia. Czy można to obejść?
Alex Bailey
1
podczas gdy niestandardowe fragmenty są świetne - są tylko rozwiązaniem scenorysowym
Fattie
7

Spróbuj tego,

    let animation = CATransition()
    animation.duration = 0.5
    animation.type = kCATransitionPush
    animation.subtype = kCATransitionFromRight
     animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    vc.view.layer.addAnimation(animation, forKey: "SwitchToView")

    self.presentViewController(vc, animated: false, completion: nil)

Oto vckontroler widoku, dashboardWorkoutw twoim przypadku.

Ketan Parmar
źródło
3
Dzięki, ale następny VC pojawia się nagle, zamiast od prawej do lewej. Zmieniłem czas trwania animacji, ale nadal to samo
Umair Afzal
4

Jeśli chcesz użyć metody „szybkiej naprawy” CATransition ....

class AA: UIViewController

 func goToBB {

    let bb = .. instantiateViewcontroller, storyboard etc .. as! AlreadyOnboardLogin

    let tr = CATransition()
    tr.duration = 0.25
    tr.type = kCATransitionMoveIn // use "MoveIn" here
    tr.subtype = kCATransitionFromRight
    view.window!.layer.add(tr, forKey: kCATransition)

    present(bb, animated: false)
    bb.delegate, etc = set any other needed values
}

i wtedy ...

func dismissingBB() {

    let tr = CATransition()
    tr.duration = 0.25
    tr.type = kCATransitionReveal // use "Reveal" here
    tr.subtype = kCATransitionFromLeft
    view.window!.layer.add(tr, forKey: kCATransition)

    dismiss(self) .. or dismiss(bb), or whatever
}

Wszystko to niestety nie jest poprawne :(

CATransition tak naprawdę nie jest stworzony do wykonywania tej pracy.

Zauważ, że irytujący krzyż wyblaknie do czerni, co niestety psuje efekt.


Wielu programistów (takich jak ja) naprawdę nie lubi używać NavigationController. Często bardziej elastyczne jest prezentowanie w sposób doraźny na bieżąco, szczególnie w przypadku nietypowych i złożonych aplikacji. Jednak „dodanie” kontrolera nawigacji nie jest trudne.

  1. po prostu na storyboardzie, przejdź do wpisu VC i kliknij "embed -> in nav controller". Naprawdę to wszystko. Lub,

  2. w didFinishLaunchingWithOptionsłatwo zbudować kontroler nav jeśli wolisz

  3. naprawdę nie musisz nawet nigdzie przechowywać tej zmiennej, ponieważ .navigationControllerjest ona zawsze dostępna jako właściwość - to łatwe.

Naprawdę, gdy masz już NavigationController, przejście między ekranami jest trywialne,

    let nextScreen = instantiateViewController etc as! NextScreen
    navigationController?
        .pushViewController(nextScreen, animated: true)

i możesz pop.

To jednak daje tylko standardowy efekt „podwójnego naciśnięcia” jabłka ...

(Stary zsuwa się z mniejszą prędkością, podczas gdy nowy ślizga się).

Ogólnie rzecz biorąc, i co zaskakujące, zwykle musisz podjąć wysiłek, aby wykonać w pełni niestandardowe przejście.

Nawet jeśli chcesz po prostu najprostszego, najczęstszego przejścia na ruch / opuszczenie, musisz wykonać w pełni niestandardowe przejście.

Na szczęście, aby to zrobić, w tej kontroli jakości jest trochę kodu standardowego wycinania i wklejania ... https://stackoverflow.com/a/48081504/294884 . Szczęśliwego Nowego Roku 2018!

Fattie
źródło
3

zaimportuj UIKit i utwórz jedno rozszerzenie dla UIViewController:

extension UIViewController {
func transitionVc(vc: UIViewController, duration: CFTimeInterval, type: CATransitionSubtype) {
    let customVcTransition = vc
    let transition = CATransition()
    transition.duration = duration
    transition.type = CATransitionType.push
    transition.subtype = type
    transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
    view.window!.layer.add(transition, forKey: kCATransition)
    present(customVcTransition, animated: false, completion: nil)
}}

po prostym połączeniu:

let vC = YourViewController()
transitionVc(vc: vC, duration: 0.5, type: .fromRight)

od lewej do prawej:

let vC = YourViewController()
transitionVc(vc: vC, duration: 0.5, type: .fromleft)

możesz zmienić czas trwania z preferowanym czasem trwania ...

Fabio
źródło
1

Spróbuj tego.

let transition: CATransition = CATransition()
transition.duration = 0.3

transition.type = kCATransitionReveal
transition.subtype = kCATransitionFromLeft
self.view.window!.layer.addAnimation(transition, forKey: nil)
self.dismissViewControllerAnimated(false, completion: nil)
Ashwin Felix
źródło
1
let transition = CATransition()
    transition.duration = 0.25
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromLeft
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    tabBarController?.view.layer.add(transition, forKey: kCATransition)
    self.navigationController?.popToRootViewController(animated: true)
Chilli
źródło
Dodałem to w moim zdarzeniu kliknięcia przycisku dla animacji. U mnie działa w Xcode 10.2 swift 4.
Chilli
0

Obecny kontroler widoku od prawej do lewej w iOS przy użyciu Swift

func FrkTransition() 

{
    let transition = CATransition()

    transition.duration = 2

    transition.type = kCATransitionPush

    transitioningLayer.add(transition,forKey: "transition")

    // Transition to "blue" state

    transitioningLayer.backgroundColor = UIColor.blue.cgColor
    transitioningLayer.string = "Blue"
}

Refrence By: [ https://developer.apple.com/documentation/quartzcore/catransition][1]

FURKAN VIJAPURA
źródło
-1

Mam lepsze rozwiązanie, które zadziałało, jeśli chcesz zachować klasyczną animację Apple, bez oglądania tego „czarnego” przejścia, które widzisz za pomocą CATransition (). Po prostu użyj metody popToViewController.

Możesz poszukać elementu viewController, którego potrzebujesz

self.navigationController.viewControllers // Array of VC that contains all VC of current stack

Możesz poszukać elementu viewController, którego potrzebujesz, wyszukując jego restorationIdentifier.

BĄDŹ OSTROŻNY: na przykład, jeśli nawigujesz przez 3 kontrolery widoku, a kiedy dotrzesz do ostatniego, chcesz otworzyć pierwszy. W takim przypadku utracisz odwołanie do efektywnego kontrolera widoku DRUGIEGO, ponieważ popToViewController go nadpisał. Przy okazji, istnieje rozwiązanie: możesz łatwo zapisać przed popToViewcontroller, VC, którego będziesz potrzebować później. U mnie zadziałało bardzo dobrze.

Edoardo Vicoli
źródło
-4

To zadziałało dla mnie:

self.navigationController?.pushViewController(controller, animated: true)
Mathis Delaunay
źródło
Nie chciałem umieszczać go w stosie nawigacyjnym.
Umair Afzal