web-dev-qa-db-ger.com

Abschlussblock für popViewController

Wenn Sie einen Modal-View-Controller mit dismissViewController schließen, gibt es die Option, einen Beendigungsblock bereitzustellen. Gibt es ein ähnliches Äquivalent für popViewController?

Das Abschlussargument ist sehr praktisch. Ich kann es beispielsweise verwenden, um das Entfernen einer Zeile aus einer Tabellenansicht zu stoppen, bis der Modal vom Bildschirm abweicht, sodass der Benutzer die Zeilenanimation sehen kann. Bei der Rückkehr von einem Push-View-Controller hätte ich die gleiche Gelegenheit.

Ich habe versucht, popViewController in einem UIView-Animationsblock zu platzieren, wo ich Zugriff auf einen Beendigungsblock habe. Dies führt jedoch zu unerwünschten Nebeneffekten in der Ansicht, in die Sie springen.

Wenn keine solche Methode verfügbar ist, gibt es einige Problemumgehungen?

89
Ben Packard

Ich weiß, dass eine Antwort vor mehr als zwei Jahren akzeptiert wurde, diese Antwort ist jedoch unvollständig.

Es gibt keine Möglichkeit, das zu tun, was Sie sofort wünschen

Dies ist technisch korrekt, da die UINavigationController-API hierfür keine Optionen bietet. Wenn Sie jedoch das CoreAnimation-Framework verwenden, können Sie der darunter liegenden Animation einen Abschlussblock hinzufügen:

[CATransaction begin];
[CATransaction setCompletionBlock:^{
    // handle completion here
}];

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

Der Beendigungsblock wird aufgerufen, sobald die von popViewControllerAnimated: verwendete Animation endet. Diese Funktionalität ist seit iOS 4 verfügbar.

169
Joris Kluivers

Für iOS9 Swift-Version - funktioniert wie ein Zauber (hatte nicht für frühere Versionen getestet). Basierend auf dieser Antwort

extension UINavigationController {    
    func pushViewController(viewController: UIViewController, animated: Bool, completion: () -> ()) {
        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }

    func popViewController(animated: Bool, completion: () -> ()) {
        popViewControllerAnimated(animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}
41
HotJard

Ich habe eine Swift-Version mit Erweiterungen mit @JorisKluivers answer erstellt.

Nach Abschluss der Animation wird sowohl für Push als auch für pop ein Abschluss abgeschlossen.

extension UINavigationController {
    func popViewControllerWithHandler(completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewControllerAnimated(true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}
27
Arbitur

Ich hatte das gleiche Problem. Und weil ich sie mehrfach verwenden musste, und innerhalb von Ketten von Abschlussblöcken, erstellte ich diese generische Lösung in einer UINavigationController-Unterklasse:

- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
    if (_completion) {
        _completion();
        _completion = nil;
    }
}

- (UIViewController *) popViewControllerAnimated:(BOOL) animated completion:(void (^)()) completion {
    _completion = completion;
    return [super popViewControllerAnimated:animated];
}

Angenommen,

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

und

@implementation NavigationController {
    void (^_completion)();
}

und

- (id) initWithRootViewController:(UIViewController *) rootViewController {
    self = [super initWithRootViewController:rootViewController];
    if (self) {
        self.delegate = self;
    }
    return self;
}
16
Jos Jong

Es gibt keine Möglichkeit, das zu tun, was Sie sofort wünschen. d.h. es gibt kein Verfahren mit einem Beendigungsblock, um eine Ansichtskontrolle aus einem Navigationsstapel zu entfernen.

Ich würde die Logik in viewDidAppear setzen. Dies wird aufgerufen, wenn die Ansicht auf dem Bildschirm angezeigt wird. Es wird für alle verschiedenen Szenarien aufgerufen, in denen der View Controller erscheint, aber das sollte in Ordnung sein.

Oder Sie können die UINavigationControllerDelegate-Methode navigationController:didShowViewController:animated: verwenden, um eine ähnliche Aufgabe zu erfüllen. Dies wird aufgerufen, wenn der Navigationscontroller einen View-Controller gedrückt oder gedrückt hat.

15
mattjgalloway

Mit oder ohne Animation richtig arbeiten und enthält auch popToRootViewController

 // updated for Swift 3.0
extension UINavigationController {

  private func doAfterAnimatingTransition(animated: Bool, completion: @escaping (() -> Void)) {
    if let coordinator = transitionCoordinator, animated {
      coordinator.animate(alongsideTransition: nil, completion: { _ in
        completion()
      })
    } else {
      DispatchQueue.main.async {
        completion()
      }
    }
  }

  func pushViewController(viewController: UIViewController, animated: Bool, completion: @escaping (() ->     Void)) {
    pushViewController(viewController, animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popToRootViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popToRootViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }
}
9
rshev

Basierend auf der Antwort von @ HotJard, wenn Sie nur ein paar Zeilen Code benötigen. Schnell und einfach.

Swift 4 :

_ = self.navigationController?.popViewController(animated: true)
self.navigationController?.transitionCoordinator.animate(alongsideTransition: nil) { _ in
    doWantIWantAfterContollerHasPopped()
}
6
Vitalii

Swift 3 Antwort dank dieser Antwort: https://stackoverflow.com/a/28232570/3412567

    //MARK:UINavigationController Extension
extension UINavigationController {
    //Same function as "popViewController", but allow us to know when this function ends
    func popViewControllerWithHandler(completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewController(animated: true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}
5
Benobab

Der Beendigungsblock wird aufgerufen, nachdem die viewDidDisappear-Methode auf dem präsentierten View-Controller aufgerufen wurde. Das Einfügen von Code in die viewDidDisappear-Methode des geöffneten View-Controllers sollte also wie ein Beendigungsblock funktionieren.

4
rdelmar

Für 2018 ...

wenn du das hast ...

    navigationController?.popViewController(animated: false)
    NeXTSTEP()

und du möchtest einen Abschluss hinzufügen ...

    CATransaction.begin()
    navigationController?.popViewController(animated: true)
    CATransaction.setCompletionBlock({ [weak self] in
       self?.NeXTSTEP() })
    CATransaction.commit()

so einfach ist das.

3
Fattie

Swift 4.1

extension UINavigationController {
func pushToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.pushViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popViewController(animated: true)
    CATransaction.commit()
}

func popToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popToRootViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToRootViewController(animated: animated)
    CATransaction.commit()
}
}
3
iOS_Developer

Es gibt einen Pod namens UINavigationControllerWithCompletionBlock , der Unterstützung für einen Beendigungsblock hinzufügt, wenn auf einem UINavigationController sowohl Push als auch Popping ausgeführt werden.

2
duncanc4

Genau das habe ich mit einem Block erreicht. Ich wollte, dass der abgerufene Ergebniscontroller die von der modalen Ansicht hinzugefügte Zeile nur dann anzeigt, wenn er den Bildschirm vollständig verlassen hat, damit der Benutzer die Änderung sehen kann. Bei der Vorbereitung des Segues, das für die Anzeige des Modal View Controllers zuständig ist, stelle ich den Block ein, den ich ausführen möchte, wenn der Modal nicht mehr angezeigt wird. Und im Modal-View-Controller überschreibe ich viewDidDissapear und rufe den Block auf. Ich fange einfach mit Aktualisierungen an, wenn der Modal erscheint, und beende die Aktualisierungen, wenn er verschwindet. Das liegt jedoch daran, dass ich einen NSFetchedResultsController verwende. Allerdings können Sie im Block alles tun, was Sie möchten.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"addPassword"]){

        UINavigationController* nav = (UINavigationController*)segue.destinationViewController;
        AddPasswordViewController* v = (AddPasswordViewController*)nav.topViewController;

...

        // makes row appear after modal is away.
        [self.tableView beginUpdates];
        [v setViewDidDissapear:^(BOOL animated) {
            [self.tableView endUpdates];
        }];
    }
}

@interface AddPasswordViewController : UITableViewController<UITextFieldDelegate>

...

@property (nonatomic, copy, nullable) void (^viewDidDissapear)(BOOL animated);

@end

@implementation AddPasswordViewController{

...

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    if(self.viewDidDissapear){
        self.viewDidDissapear(animated);
    }
}

@end
1
malhal

Verwenden Sie die nächste Erweiterung für Ihren Code: (Swift 4)

import UIKit

extension UINavigationController {

    func popViewController(animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        popViewController(animated: animated)
        CATransaction.commit()
    }

    func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}

Der Vollständigkeit halber habe ich eine Objective-C-Kategorie erstellt:

// UINavigationController+CompletionBlock.h

#import <UIKit/UIKit.h>

@interface UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion;

@end
// UINavigationController+CompletionBlock.m

#import "UINavigationController+CompletionBlock.h"

@implementation UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion {
    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        completion();
    }];

    UIViewController *vc = [self popViewControllerAnimated:animated];

    [CATransaction commit];

    return vc;
}

@end
1
Diego Freniche

Swift 4-Version mit optionalem viewController-Parameter zum Aufrufen eines bestimmten Parameters.

extension UINavigationController {
    func pushViewController(viewController: UIViewController, animated: 
        Bool, completion: @escaping () -> ()) {

        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
}

func popViewController(viewController: UIViewController? = nil, 
    animated: Bool, completion: @escaping () -> ()) {
        if let viewController = viewController {
            popToViewController(viewController, animated: animated)
        } else {
            popViewController(animated: animated)
        }

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}
1
TejAces

Aufgeräumte Swift 4 Version basierend auf dieser Antwort .

extension UINavigationController {
    func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
        self.pushViewController(viewController, animated: animated)
        self.callCompletion(animated: animated, completion: completion)
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController? {
        let viewController = self.popViewController(animated: animated)
        self.callCompletion(animated: animated, completion: completion)
        return viewController
    }

    private func callCompletion(animated: Bool, completion: @escaping () -> Void) {
        if animated, let coordinator = self.transitionCoordinator {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}
0
d4Rk