web-dev-qa-db-fra.com

Détecter quand un contrôleur de vue présenté est congédié

Disons que j'ai une instance d'une classe de contrôleur de vue appelée VC2. Dans VC2, il existe un bouton "annuler" qui disparaîtra de lui-même. Mais je ne peux ni détecter ni recevoir aucun rappel lorsque le bouton "annuler" a un déclencheur. VC2 est une boîte noire.

Un contrôleur de vue (appelé VC1) présentera VC2 en utilisant la méthode presentViewController:animated:completion:.

Quelles options VC1 a-t-il pour détecter le moment où VC2 a été congédié?

Edit: Du commentaire de @rory mckinnel et de la réponse de @NicolasMiari, j’ai essayé:

Dans VC2:

-(void)cancelButton:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:^{

    }];
//    [super dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

Dans VC1:

//-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
- (void)dismissViewControllerAnimated:(BOOL)flag
                           completion:(void (^ _Nullable)(void))completion
{
    NSLog(@"%s ", __PRETTY_FUNCTION__);
    [super dismissViewControllerAnimated:flag completion:completion];
//    [self dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

Mais la dismissViewControllerAnimated dans le VC1 n'était pas appelée.

33
user523234

Selon les docs, le responsable de la présentation est responsable du renvoi. Lorsque le contrôleur présenté se renverse, il demandera au présentateur de le faire pour lui. Donc, si vous écrasez licenciéViewControllerAnimated dans votre contrôleur VC1, je crois qu’il sera appelé lorsque vous appuierez sur Cancel sur VC2. Détectez le licenciement puis appelez la version des super classes qui le fera.

Comme il ressort de la discussion, cela ne semble pas fonctionner. Plutôt que de compter sur le mécanisme sous-jacent, au lieu d'appeler dismissViewControllerAnimated:completion sur VC2, appelez dismissViewControllerAnimated:completion sur self.presentingViewController dans VC2. Cela appellera alors directement votre remplacement.

Une meilleure approche consisterait à faire en sorte que VC2 fournisse un bloc appelé lorsque le contrôleur modal sera terminé.

Donc, dans VC2, fournissez une propriété de bloc avec le nom onDoneBlock.

Dans VC1, vous présentez comme suit:

  • Dans VC1, créez VC2

  • Définissez le gestionnaire done pour VC2 comme suit: VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • Présentez le contrôleur VC2 comme d'habitude en utilisant [self presentViewController: VC2 animé: YES complétion: nil];

  • Dans VC2, dans l'appel d'action d'annulation de cible self.onDoneBlock();

Le résultat est VC2 indique à celui qui le relève que cela est fait. Vous pouvez étendre la onDoneBlock pour avoir des arguments indiquant si le modal est comleté, annulé, réussi, etc.

31
Rory McKinnel

Utiliser une propriété de bloc

Déclarer dans VC2

var onDoneBlock : ((Bool) -> Void)?

Configuration dans VC1

VC2.onDoneBlock = { result in
                // Do something
            }

Appelez VC2 lorsque vous êtes sur le point de licencier

onDoneBlock!(true)
17
brycejl

Le contrôleur de vue présenté et peut appeler dismissViewController:animated: afin de fermer le contrôleur de vue présenté. 

La première option est (sans doute) la "bonne", du point de vue de la conception: le même contrôleur de vue "parent" est responsable de la présentation et de la suppression du contrôleur de vue modal ("enfant"). 

Toutefois, cette dernière solution est plus pratique: en règle générale, le bouton "Refuser" est associé à la vue du contrôleur de vue présenté, et ce dernier est défini comme cible d'action.

Si vous adoptez l'ancienne approche, vous connaissez déjà la ligne de code dans votre contrôleur de vue présentation où se produit le renvoi: exécutez votre code juste après le dismissViewControllerAnimated:completion: ou dans le bloc d'achèvement.

Si vous adoptez cette dernière approche (le contrôleur de vue présenté se ferme tout seul), gardez à l'esprit que l'appel de dismissViewControllerAnimated:completion: à partir du contrôleur de vue présenté entraîne UIKit à appeler à son tour cette méthode sur le contrôleur de vue présenté:

Discussion

Le responsable de la vue présentation est responsable de rejetant le contrôleur de vue présenté. Si vous appelez cette méthode sur le contrôleur de vue présenté lui-même, UIKit demande à la présentation contrôleur de vue pour gérer le licenciement.

( source: Référence de la classe UIViewController )

Ainsi, pour intercepter un tel événement, vous pouvez remplacer cette méthode dans le contrôleur de vue présentant:

override func dismissViewControllerAnimated(_ flag: Bool,
                        completion completion: (() -> Void)?)
{
     super.dismissViewControllerAnimated(flag, completion:completion)

    // Your custom code here...
}
8
Nicolas Miari

Vous pouvez utiliser undind segue pour effectuer cette tâche, il n'est pas nécessaire d'utiliser le rejetModalViewController. Définissez une méthode de déroulement de déroulement dans votre VC1.

Voir ce lien sur la façon de créer la séquence de déroulement, https://stackoverflow.com/a/15839298/5647055 .

En supposant que votre séquence de déroulement soit configurée, dans la méthode d'action définie pour votre bouton "Annuler", vous pouvez effectuer la séquence de la manière suivante:

[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];

Maintenant, chaque fois que vous appuierez sur le bouton "Annuler" du VC2, celui-ci sera ignoré et VC1 apparaîtra. Il appellera également la méthode de déroulement, définie dans VC1. Vous savez maintenant quand le contrôleur de vue présenté est congédié.

2
valarMorghulis

J'utilise ce qui suit pour signaler à un coordinateur que le contrôleur de vue est "terminé". Ceci est utilisé dans une sous-classe AVPlayerViewController dans une application tvOS et sera appelé une fois la transition de licenciement de playerVC terminée:

class PlayerViewController: AVPlayerViewController {
  var onDismissal: (() -> Void)?

  override func beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) {
    super.beginAppearanceTransition(isAppearing, animated: animated)
    transitionCoordinator?.animate(alongsideTransition: nil,
      completion: { [weak self] _ in
         if !isAppearing {
            self?.onDismissal?()
        }
    })
  }
}
1
fruitcoder

@ user523234 - "Mais le rejetViewControllerAnimated dans le VC1 n'a pas été appelé."

Vous ne pouvez pas supposer que VC1 fait la présentation - cela pourrait être le contrôleur de vue racine, VC0, par exemple. Il y a 3 contrôleurs de vue impliqués:

  • sourceViewController
  • présenterViewController
  • presentsViewController

Dans votre exemple, VC1 = sourceViewController, VC2 = presentedViewController, ?? = presentingViewController - peut-être VC1, peut-être pas.

Cependant, vous pouvez toujours compter sur l'appel de VC1.animationControllerForDismissedController (si vous avez implémenté les méthodes de délégation) lorsque vous quittez VC2 et que vous pouvez faire ce que vous voulez avec VC1.

1
Andrew Coad

Comme il a été mentionné, la solution consiste à utiliser override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)

Pour ceux qui se demandent pourquoi override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) ne semble pas toujours fonctionner, vous pouvez constater que l'appel est intercepté par une UINavigationController s'il est géré. J'ai écrit une sous-classe qui devrait aider: 

class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }

0
Steve

J'ai vu ce post tellement de fois quand j'ai traité de cette question, j'ai pensé que je pourrais enfin faire la lumière sur une réponse possible.

Si vous avez besoin de savoir si des actions initiées par l'utilisateur (comme des gestes à l'écran) entraînent le renvoi d'un UIActionController , et si vous ne souhaitez pas investir du temps dans la création de sous-classes ou d'extensions ou quoi que ce soit dans votre code, il existe un alternative.

Il s’avère que la propriété popoverPresentationController de la propriété/ UIActionController (ou, au contraire, de tout UIViewController à cet effet), a un délégué vous pouvez définir à tout moment dans votre code, qui UIPopoverPresentationControllerDelegate , et a les méthodes suivantes:

Affectez le délégué à partir de votre contrôleur d'action, implémentez la ou les méthodes de votre choix dans la classe de délégué (vue, contrôleur de vue ou autre), et le tour est joué!

J'espère que cela t'aides.

0
Izhido

overrideing viewDidAppear a fait le tour pour moi. J'ai utilisé un Singleton dans mon modal et suis maintenant en mesure de définir et d'obtenir à partir de celui-ci dans le VC appelant, le modal et partout ailleurs.

0
A T

Remplacer la fonction viewWillDisappear dans le contrôleur de vue présenté.

override func viewWillDisappear(_ animated: Bool) {
    //Your code here
}
0
Prince Jayakumar

Utiliser willMove(toParent: UIViewController?) de la manière suivante a semblé fonctionner pour moi. (Testé sur iOS12).

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent);

    if parent == nil
    {
        // View controller is being removed.
        // Perform onDismiss action
    }
}
0
Shavi
0
Savas Adar
  1. Créez un fichier de classe (.h/.m) et nommez-le: DismissSegue 
  2. Sélectionnez la sous-classe de: UIStoryboardSegue

  3. Allez dans le fichier DismissSegue.m et notez le code suivant:

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. Ouvrez le scénario, puis appuyez sur Ctrl + glisser du bouton d’annulation vers VC1 et sélectionnez Action Segue comme rejet et vous avez terminé.

0
Tapansinh Solanki