Comment passer prepareForSegue: un objet

358

J'ai de nombreuses annotations dans une carte (avec des rightCalloutAccessoryboutons). Le bouton effectuera un enchaînement de ceci mapviewà a tableview. Je veux passer tableviewun objet différent (qui contient des données) en fonction du bouton de légende sur lequel vous avez cliqué.

Par exemple: (totalement composé)

  • annotation1 (Austin) -> passer les données obj 1 (pertinentes pour Austin)
  • annotation2 (Dallas) -> passer les données obj 2 (pertinentes pour Dallas)
  • annotation3 (Houston) -> passer les données obj 3 et ainsi de suite ... (vous avez l'idée)

Je peux détecter le bouton de légende sur lequel vous avez cliqué.

J'utilise prepareForSegue: pour passer les données obj à la destination ViewController. Étant donné que je ne peux pas faire de cet appel un argument supplémentaire pour l'obj de données dont j'ai besoin, quelles sont les façons élégantes d'obtenir le même effet (obj de données dynamiques)?

Tout conseil serait apprécié.

grésiller
la source

Réponses:

674

Saisissez simplement une référence au contrôleur de vue cible dans la prepareForSegue:méthode et passez-y tous les objets dont vous avez besoin. Voici un exemple ...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Make sure your segue name in storyboard is the same as this line
    if ([[segue identifier] isEqualToString:@"YOUR_SEGUE_NAME_HERE"])
    {
        // Get reference to the destination view controller
        YourViewController *vc = [segue destinationViewController];

        // Pass any objects to the view controller here, like...
        [vc setMyObjectHere:object];
    }
}

RÉVISION: Vous pouvez également utiliser la performSegueWithIdentifier:sender:méthode pour activer la transition vers une nouvelle vue en fonction d'une sélection ou d'une pression sur un bouton.

Par exemple, considérez que j'avais deux contrôleurs de vue. Le premier contient trois boutons et le second doit savoir lequel de ces boutons a été enfoncé avant la transition. Vous pouvez câbler les boutons jusqu'à un IBActiondans votre code qui utilise une performSegueWithIdentifier:méthode, comme celle-ci ...

// When any of my buttons are pressed, push the next view
- (IBAction)buttonPressed:(id)sender
{
    [self performSegueWithIdentifier:@"MySegue" sender:sender];
}

// This will get called too before the view appears
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"MySegue"]) {

        // Get destination view
        SecondView *vc = [segue destinationViewController];

        // Get button tag number (or do whatever you need to do here, based on your object
        NSInteger tagIndex = [(UIButton *)sender tag];

        // Pass the information to your destination view
        [vc setSelectedButton:tagIndex];
    }
}

EDIT: L'application de démonstration que j'ai jointe à l'origine a maintenant six ans, donc je l'ai supprimée pour éviter toute confusion.

Simon
la source
Merci mais je veux régler [vc setMyObjectHere:object];cela dynamiquement. ie obj1 pour button1, obj2 pour button2 Le problème est que je ne peux pas passer d'argument. Y a-t-il un moyen de contourner cela?
grésiller
2
J'ai mis à jour mon article avec un exemple téléchargeable de ce dont je parle.
Simon
ça a marché! Merci beaucoup. En guise de note complémentaire prepareForSegue: a un argument UIControl qui est une classe parent de UIButton (donc capable d'obtenir la balise): D
chizzle
PrepareForSegue est-il appelé même lorsque vous appuyez simplement sur un contrôleur de navigation sans enchaînement de storyboard?
zakdances
C'est l'une des réponses les plus approfondies que j'aie jamais vues. De bons exemples de code, résout le problème, propose un échantillon téléchargeable ... wow. Je suis impressionné!
lewiguez
82

Il est parfois utile d'éviter de créer une dépendance au moment de la compilation entre deux contrôleurs de vue. Voici comment vous pouvez le faire sans vous soucier du type de contrôleur de vue de destination:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController respondsToSelector:@selector(setMyData:)]) {
        [segue.destinationViewController performSelector:@selector(setMyData:) 
                                              withObject:myData];
    } 
}

Donc, tant que votre contrôleur de vue de destination déclare une propriété publique, par exemple:

@property (nonatomic, strong) MyData *myData;

vous pouvez définir cette propriété dans le contrôleur de vue précédent comme je l'ai décrit ci-dessus.

Macondo2Seattle
la source
12
C'est vraiment une question d'opinion. Je n'ai pas (encore) eu de situation où je ne voulais pas contrôler les contrôleurs de vue `` étroitement '', bien que j'apprécie ce que vous dites et cela peut être important dans les bonnes circonstances.
Simon
2
@Simon: oui, vous choisissez simplement l'approche qui vous convient le mieux. Dans l'application sur laquelle je travaille maintenant, par exemple, mon approche a beaucoup de sens puisque je continue d'ajouter des contrôleurs de vue qui ont besoin du même objet de données. Être en mesure de les connecter avec juste une séquence et savoir qu'ils obtiendront les bonnes données est super pratique.
Macondo2Seattle
10
Ce n'est pas une question d'opinion, c'est juste faux :) L'expression "La réponse acceptée n'est pas la meilleure façon de faire cela" est fausse. Il devrait se lire "Dans certains cas, vous devez le faire ..."
Fattie
2
Je préfère la méthode de Simon. Comme cela me dira les erreurs au moment de la compilation. Par exemple, si j'ai raté la déclaration de myData dans le contrôleur de vue de destination, je le saurai immédiatement. Mais pour votre scénario, votre approche semble bonne!
Abdurrahman Mubeen Ali du
14
Cette approche crée absolument une dépendance, car vous avez besoin que le contrôleur de vue de destination en ait un setMyData:. C'est une dépendance. Le fait que vous utilisiez un sélecteur pour éviter l'erreur de compilation doit être traité comme une faiblesse de votre approche et non comme un avantage. Je suis choqué par le nombre de développeurs qui ont perdu le concept selon lequel les erreurs de compilation devraient être préférées aux erreurs d'exécution.
Nate
21

Dans Swift 4.2, je ferais quelque chose comme ça:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let yourVC = segue.destination as? YourViewController {
        yourVC.yourData = self.someData
    }
}
Remy Cilia
la source
Avez-vous besoin d'appeler: `super.prepare (for: segue, sender: sender)`?
Andrew K
16

J'ai une classe d'expéditeur , comme celle-ci

@class MyEntry;

@interface MySenderEntry : NSObject
@property (strong, nonatomic) MyEntry *entry;
@end

@implementation MySenderEntry
@end

J'utilise cette classe d'expéditeur pour passer des objets àprepareForSeque:sender:

-(void)didSelectItemAtIndexPath:(NSIndexPath*)indexPath
{
    MySenderEntry *sender = [MySenderEntry new];
    sender.entry = [_entries objectAtIndex:indexPath.row];
    [self performSegueWithIdentifier:SEGUE_IDENTIFIER_SHOW_ENTRY sender:sender];
}

-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_SHOW_ENTRY]) {
        NSAssert([sender isKindOfClass:[MySenderEntry class]], @"MySenderEntry");
        MySenderEntry *senderEntry = (MySenderEntry*)sender;
        MyEntry *entry = senderEntry.entry;
        NSParameterAssert(entry);

        [segue destinationViewController].delegate = self;
        [segue destinationViewController].entry = entry;
        return;
    }

    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_HISTORY]) {
        // ...
        return;
    }

    if ([[segue identifier] isEqualToString:SEGUE_IDENTIFIER_FAVORITE]) {
        // ...
        return;
    }
}
neoneye
la source
sommes - nous utilisons prepareForSegue ou nous appliquons et l'hypothèse est prepareForSegue est appelée elle - même à dire que nous ne devons faire quelque chose comme[self prepareForSegue]
Honey
15

Je suis tombé sur cette question lorsque j'essayais d'apprendre à transmettre des données d'un View Controller à un autre. J'ai besoin de quelque chose de visuel pour m'aider à apprendre, donc cette réponse est un complément aux autres déjà présentes. Elle est un peu plus générale que la question d'origine mais elle peut être adaptée au travail.

Cet exemple de base fonctionne comme ceci:

entrez la description de l'image ici

L'idée est de passer une chaîne du champ de texte du First View Controller à l'étiquette du Second View Controller.

Contrôleur First View

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        // get a reference to the second view controller
        let secondViewController = segue.destinationViewController as! SecondViewController

        // set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

Second View Controller

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

Se souvenir de

  • Effectuez la séquence en controlcliquant sur le bouton et en le déplaçant vers le deuxième contrôleur de vue.
  • Branchez les prises pour le UITextFieldet le UILabel.
  • Définissez le premier et le second View Controllers sur les fichiers Swift appropriés dans IB.

La source

Comment envoyer des données via Segue (Swift) (tutoriel YouTube)

Voir également

Contrôleurs de vue: transmission de données vers l'avant et transmission de données (réponse plus complète)

Suragch
la source
5

Pour Swift, utilisez ceci,

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var segueID = segue.identifier

    if(segueID! == "yourSegueName"){

        var yourVC:YourViewController = segue.destinationViewController as YourViewController

        yourVC.objectOnYourVC = setObjectValueHere!

    }
}
Mohammad Zaid Pathan
la source
4

J'ai implémenté une bibliothèque avec une catégorie sur UIViewController qui simplifie cette opération. Fondamentalement, vous définissez les paramètres que vous souhaitez passer dans un NSDictionary associé à l'élément d'interface utilisateur qui effectue la transition. Cela fonctionne également avec les séquences manuelles.

Par exemple, vous pouvez faire

[self performSegueWithIdentifier:@"yourIdentifier" parameters:@{@"customParam1":customValue1, @"customValue2":customValue2}];

pour une séquence manuelle ou créez un bouton avec une séquence et utilisez

[button setSegueParameters:@{@"customParam1":customValue1, @"customValue2":customValue2}];

Si le contrôleur de vue de destination n'est pas conforme au codage de valeur-clé pour une clé, rien ne se produit. Il fonctionne également avec les valeurs-clés (utile pour les séquences de déroulement). Découvrez-le ici https://github.com/stefanomondino/SMQuickSegue

Stefano Mondino
la source
2

Ma solution est similaire.

// In destination class: 
var AddressString:String = String()

// In segue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if (segue.identifier == "seguetobiddetailpagefromleadbidder")
    {
        let secondViewController = segue.destinationViewController as! BidDetailPage
        secondViewController.AddressString = pr.address as String
    }
}
AG
la source
1

Utilisez simplement cette fonction.

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let index = CategorytableView.indexPathForSelectedRow
    let indexNumber = index?.row
    let VC = segue.destination as! DestinationViewController
   VC.value = self.data

}
Parth Barot
la source
0

J'ai utilisé cette solution afin de pouvoir conserver l'invocation de la séquence et la communication de données dans la même fonction:

private var segueCompletion : ((UIStoryboardSegue, Any?) -> Void)?

func performSegue(withIdentifier identifier: String, sender: Any?, completion: @escaping (UIStoryboardSegue, Any?) -> Void) {
    self.segueCompletion = completion;
    self.performSegue(withIdentifier: identifier, sender: sender);
    self.segueCompletion = nil
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    self.segueCompletion?(segue, sender)
}

Un cas d'utilisation serait quelque chose comme:

func showData(id : Int){
    someService.loadSomeData(id: id) {
        data in
        self.performSegue(withIdentifier: "showData", sender: self) {
            storyboard, sender in
            let dataView = storyboard.destination as! DataView
            dataView.data = data
        }
    }
}

Cela semble fonctionner pour moi, cependant, je ne suis pas sûr à 100% que les fonctions performer et préparer sont toujours exécutées sur le même thread.

dannrob
la source