web-dev-qa-db-fra.com

Au lieu de Push segue, comment remplacer le contrôleur de vue (ou le retirer de la pile de navigation)?

J'ai ne petite application iPhone , qui utilise un contrôleur de navigation pour afficher 3 vues (ici plein écran ):

Xcode screenshot

Il affiche d'abord une liste de réseaux sociaux (Facebook, Google+, etc.):

list screenshot

Ensuite, une boîte de dialogue OAuth) demandant des informations d'identification:

login screenshot

Et (après cela, dans le même UIWebView) pour les autorisations:

permissions screenshot

Enfin, il affiche le dernier contrôleur de vue avec les détails de l'utilisateur (dans l'application réelle, ce sera le menu dans lequel le jeu multijoueur peut être lancé):

details screenshot

Tout cela fonctionne bien, mais j'ai un problème lorsque l'utilisateur veut revenir en arrière et sélectionner un autre réseau social:

L'utilisateur appuie sur le bouton de retour et au lieu d'afficher la première vue, la seconde est affichée, demandant à nouveau OAuth informations d'identification/autorisations à nouveau.

Que puis-je faire ici? Le Xcode 5.0.2 montre un choix très limité pour les segments - Push , modal (que je ne peux pas utiliser, car elle masque la barre de navigation nécessaire à mon jeu) et personnalisé .

Je suis un débutant en programmation iOS, mais j'avais précédemment développé un application mobile Adobe AIR et il était possible de 1) remplacer la vue au lieu de pousser et 2) de supprimer une vue inutile de la pile de navigation.

Comment faire la même chose dans une application native s'il vous plaît?

64
Alexander Farber

Vous pouvez utiliser un segment personnalisé: pour ce faire, vous devez créer une sous-classe UIStoryboardSegue (par exemple, MyCustomSegue), puis vous pouvez remplacer le paramètre "perform" par quelque chose comme ceci.

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    // Pop to root view controller (not animated) before pushing
    [navigationController popToRootViewControllerAnimated:NO];
    [navigationController pushViewController:destinationController animated:YES];    
}

A ce stade, allez dans Interface Builder, sélectionnez "custom", et indiquez le nom de votre classe (par exemple, MyCustomSegue).

37
Daniele

Pour développer les différentes étapes ci-dessus, voici ma solution. Il présente les avantages suivants:

  • Peut fonctionner n'importe où dans la pile de vues, pas seulement la vue de dessus (je ne suis pas sûr si c'est réellement nécessaire ou même techniquement possible de le déclencher, mais bon, c'est là dedans).
  • Cela ne provoque pas de transition pop OR vers le contrôleur de vue précédent avant d'afficher le remplacement, mais simplement d'afficher le nouveau contrôleur avec une transition naturelle, la navigation arrière étant identique à la navigation arrière). le contrôleur de source.

Code de la division:

- (void)perform {
    // Grab Variables for readability
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;

    // Get a changeable copy of the stack
    NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
    // Replace the source controller with the destination controller, wherever the source may be
    [controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];

    // Assign the updated stack with animation
    [navigationController setViewControllers:controllerStack animated:YES];
}
58
ima747

Le segment personnalisé ne fonctionnait pas pour moi car j'avais un contrôleur de vue Splash et je voulais le remplacer. Comme il n'y avait qu'un seul contrôleur de vue dans la liste, le popToRootViewController laissait toujours le Splash sur la pile. J'ai utilisé le code suivant pour remplacer le contrôleur unique

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    [navigationController setViewControllers:@[destinationController] animated:YES];
}

et maintenant dans Swift 4:

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        source.navigationController?.setViewControllers([destination], animated: true)
    }
}

et maintenant dans Swift 2.0

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
    }
}
23
christophercotton

la Swift 2 version de ima747 answer:

override func perform() {
    let navigationController: UINavigationController = sourceViewController.navigationController!;

    var controllerStack = navigationController.viewControllers;
    let index = controllerStack.indexOf(sourceViewController);
    controllerStack[index!] = destinationViewController

    navigationController.setViewControllers(controllerStack, animated: true);
}

Comme il l'a mentionné, il présente les avantages suivants:

  • Peut fonctionner n'importe où dans la pile de vues, pas seulement la vue de dessus (je ne suis pas sûr si c'est réellement nécessaire ou même techniquement possible de le déclencher, mais bon, c'est là dedans).
  • Cela ne provoque pas de transition pop OR vers le contrôleur de vue précédent avant d'afficher le remplacement, mais simplement d'afficher le nouveau contrôleur avec une transition naturelle, la navigation arrière étant identique à la navigation arrière). le contrôleur de source.
17
Dr.Agos

Pour ce problème, je pense que la réponse est aussi simple que

  1. Obtenez le tableau des contrôleurs de vue à partir de NavigationController
  2. Suppression du dernier ViewController (contrôleur de vue actuel)
  3. Insérer un nouveau enfin
  4. Puis redéfinissez le tableau de ViewControllers sur navigationController comme suit:

     if let navController = self.navigationController {
        let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
    
        var stack = navController.viewControllers
        stack.remove(at: stack.count - 1)       // remove current VC
        stack.insert(newVC, at: stack.count) // add the new one
        navController.setViewControllers(stack, animated: true) // boom!
     }
    

fonctionne parfaitement avec Swift 3.

J'espère que ça aide pour les nouveaux gars.

À votre santé.

15
Xuan-Gieng Nguyen

L'utilisation de la fonction de déroulement constitue la solution la plus appropriée à ce problème. Je suis d'accord avec Lauro.

Voici une brève explication pour configurer une transition entre detailsViewController [ou viewController3] et myAuthViewController [ou viewController1].

C’est essentiellement ainsi que vous procéderiez pour décompresser du code.

  • Implémentez une méthode IBAction dans le viewController que vous souhaitez décompresser (dans ce cas, viewController1). Le nom de la méthode peut être si long qu'il faut un argument de type UIStoryboardSegue.

    @IBAction func unwindToMyAuth(segue: UIStoryboardSegue) {
    println("segue with ID: %@", segue.Identifier)
    }
    
  • Liez cette méthode dans le viewController (3) à partir duquel vous souhaitez vous dérouler. Pour créer un lien, cliquez avec le bouton droit de la souris sur l'icône de sortie en haut du viewController. À ce stade, la méthode 'unwindToMyAuth' apparaît dans la boîte de dialogue contextuelle. Cliquez avec cette méthode sur la première icône, l'icône viewController (également présente en haut du viewController, dans la même ligne que l'icône de sortie). Sélectionnez l'option "manuelle" qui apparaît.

  • Dans la structure du document, pour la même vue (viewController3), sélectionnez la séquence de déroulement que vous venez de créer. Accédez à l'inspecteur attribué et attribuez un identificateur unique à cette séquence de déroulement. Nous avons maintenant une séquence de déroulement générique prête à être utilisée.

  • Maintenant, la séquence de déroulement peut être effectuée comme n'importe quelle autre séquence du code.

    performSegueWithIdentifier("unwind.to.myauth", sender: nil)
    

Cette approche vous mènera de viewController3 à viewController1 sans qu'il soit nécessaire de supprimer viewController2 de la hiérarchie de navigation.

Contrairement aux autres séquences, les séquences de déroulement n'instancient pas un contrôleur de vue, elles ne font que se rendre vers un contrôleur de vue existant dans la hiérarchie de navigation.

9
Lester

Comme mentionné dans les réponses précédentes, pop non animé, puis Push animé ne seront pas très beaux, car l’utilisateur verra le processus réel. Je vous recommande d'abord Push animé, puis supprimez le vc précédent. Ainsi:

extension UINavigationController {
    func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) {
        pushViewController(viewController, animated: animated)
        let indexToRemove = viewControllers.count - 2
        if indexToRemove >= 0 {
            viewControllers.remove(at: indexToRemove)
        }
    }
}
4
Alex Shubin

Utilisez le code ci-dessous dernier contrôleur de vue Vous pouvez utiliser un autre bouton ou le mettre vous-même au lieu du bouton d'annulation que j'ai utilisé

- (void)viewDidLoad
{
 [super viewDidLoad];

 [self.navigationController setNavigationBarHidden:YES];

 UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismiss:)];

self.navigationItemSetting.leftBarButtonItem = cancelButton;

}

- (IBAction)dismissSettings:(id)sender
{

// your logout code for social media selected
[self.navigationController popToRootViewControllerAnimated:YES];

}
2
bhavya kothari

Ce que vous devriez vraiment faire est de présenter modalement un UINavigationController contenant le réseau social UIViewControllers de votre menu UIViewController (qui peut être intégré à un UINavigationController si vous vouloir). Ensuite, une fois l’utilisateur authentifié, vous fermez le réseau social UINavigationController et affichez à nouveau votre Menu UIViewController.

2
Mark

Dans Swift3 créer une séquence - ajouter un identifiant - ajouter et définir une classe de storyboard personnalisée avec un fichier cocoatouch - Dans une substitution de classe personnalisée perform ()

override func perform() {
    let sourceViewController = self.source
    let destinationController = self.destination
    let navigationController = sourceViewController.navigationController
    // Pop to root view controller (not animated) before pushing
    if self.identifier == "your identifier"{
    navigationController?.popViewController(animated: false)
    navigationController?.pushViewController(destinationController, animated: true)
    }
    }

-Vous devez également remplacer une méthode dans votre contrôleur de vue source

  override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
    return false
}
2
Mahesh Giri

Voici une solution rapide Swift 4/5 en créant une séquence personnalisée qui remplace le contrôleur de vue (pile la plus haute) par le nouveau (sans animation):

class SegueNavigationReplaceTop: UIStoryboardSegue {

    override func perform () {
        guard let navigationController = source.navigationController else { return }
        navigationController.popViewController(animated: false)
        navigationController.pushViewController(destination, animated: false)
    }
}
1
HixField

Cela a fonctionné pour moi dans Swift 3:

class ReplaceSegue: UIStoryboardSegue {
    override func perform() {
        if let navVC = source.navigationController {
            navVC.pushViewController(destination, animated: true)
        } else {
            super.perform()
        }
    }
}
1
jeffbailey

Que diriez-vous de ça :) J'ai maintenant la vieille question, mais cela fonctionnera comme un charme:

UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:@[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;
1
Denis Kozhukhov

Eh bien, ce que vous pouvez également faire est d’utiliser les commandes du contrôleur de vue de déroulement.

En fait, je pense que c'est exactement ce dont vous avez besoin.

Vérifiez cette entrée: À quoi servent les segments Unwind et comment les utilisez-vous?