web-dev-qa-db-fra.com

Bloc d'achèvement pour popViewController

Lorsque vous quittez un contrôleur de vue modale à l'aide de dismissViewController, vous avez la possibilité de fournir un bloc d'achèvement. Existe-t-il un équivalent similaire pour popViewController?

L'argument d'achèvement est assez pratique. Par exemple, je peux l'utiliser pour retarder le retrait d'une ligne d'une table jusqu'à ce que le modal soit désactivé, permettant ainsi à l'utilisateur de voir l'animation de la ligne. Au retour d'un contrôleur de vue poussée, j'aimerais avoir la même possibilité.

J'ai essayé de placer popViewController dans un bloc d'animation UIView, où j'ai accès à un bloc d'achèvement. Cependant, cela produit des effets secondaires indésirables sur la vue affichée.

Si aucune méthode de ce type n'est disponible, quelles sont les solutions de contournement?

89
Ben Packard

Je sais qu'une réponse a été acceptée il y a plus de deux ans, mais cette réponse est incomplète.

Il n'y a aucun moyen de faire ce que vous voulez, prêt à l'emploi.

Ceci est techniquement correct car l’API UINavigationController n’offre aucune option pour cela. Cependant, en utilisant le framework CoreAnimation, il est possible d’ajouter un bloc d’achèvement à l’animation sous-jacente:

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

Le bloc d'achèvement sera appelé dès que l'animation utilisée par popViewControllerAnimated: se terminera. Cette fonctionnalité est disponible depuis iOS 4.

169
Joris Kluivers

Pour iOS9 version Swift - fonctionne comme un charme (n'avait pas testé les versions précédentes) Basé sur cette réponse

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

J'ai créé une version Swift avec des extensions avec @JorisKluivers answer.

Ceci appellera une clôture d'achèvement une fois l'animation terminée pour Push et pop.

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

J'ai eu le même problème. Et comme je devais l'utiliser plusieurs fois et au sein de chaînes de blocs d'achèvement, j'ai créé cette solution générique dans une sous-classe UINavigationController:

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

En supposant

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

et

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

et

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

Il n'y a aucun moyen de faire ce que vous voulez au tout début. c'est-à-dire qu'il n'existe aucune méthode avec un bloc d'achèvement pour extraire un contrôleur de vue à partir d'une pile de navigation.

Ce que je ferais, c’est de mettre la logique dans viewDidAppear. Cela sera appelé lorsque la vue aura fini de s'afficher à l'écran. Cela sera appelé pour tous les différents scénarios d’apparition du contrôleur de vue, mais cela devrait aller.

Ou vous pouvez utiliser la méthode UINavigationControllerDelegatenavigationController:didShowViewController:animated: pour faire la même chose. Ceci est appelé lorsque le contrôleur de navigation a fini de pousser ou d'afficher un contrôleur de vue.

15
mattjgalloway

Travailler avec ou sans animation correctement, et inclut également 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

Basé sur la réponse de @ HotJard, quand vous ne voulez que quelques lignes de code Rapide et facile.

Swift 4 :

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

Swift 3 répond, grâce à cette réponse: 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

Le bloc d'achèvement est appelé après l'appel de la méthode viewDidDisappear sur le contrôleur de vue présenté. Par conséquent, l'insertion de code dans la méthode viewDidDisappear du contrôleur de vue éclaté doit fonctionner de la même manière qu'un bloc d'achèvement.

4
rdelmar

Pour 2018 ...

si vous avez ça ...

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

et vous voulez ajouter une finition ...

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

c'est si simple.

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

Il existe un module appelé UINavigationControllerWithCompletionBlock qui ajoute la prise en charge d'un bloc d'achèvement lors du chargement et du déclenchement d'un UINavigationController.

2
duncanc4

J'ai réalisé exactement cela avec précision en utilisant un bloc. Je voulais que mon contrôleur de résultats récupérés affiche la ligne ajoutée par la vue modale, uniquement une fois qu'il a complètement quitté l'écran, afin que l'utilisateur puisse voir le changement se produire. En préparation de la séquence responsable de l'affichage du contrôleur de vue modale, je règle le bloc que je souhaite exécuter lorsque la modale disparaît. Et dans le contrôleur de vue modale, je substitue viewDidDissapear puis appelle le bloc. Je commence simplement les mises à jour quand le modal va apparaître et je les termine quand il disparaît, mais c'est parce que j'utilise un NSFetchedResultsController, mais vous pouvez faire ce que vous voulez dans le bloc.

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

Utilisez l'extension suivante de votre 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()
    }
}

Pour compléter, j'ai créé une catégorie Objective-C prête à l'emploi:

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

Version Swift 4 avec le paramètre optionnel viewController pour en ajouter un spécifique.

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

Version nettoyée Swift 4 basée sur cette réponse .

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