Passage de données entre les contrôleurs de vue

1371

Je suis nouveau sur iOS et Objective-C et tout le paradigme MVC et je suis coincé avec les éléments suivants:

J'ai une vue qui agit comme un formulaire de saisie de données et je veux donner à l'utilisateur la possibilité de sélectionner plusieurs produits. Les produits sont répertoriés sur une autre vue avec unUITableViewController et j'ai activé plusieurs sélections.

Ma question est, comment puis-je transférer les données d'une vue à une autre? Je tiendrai les sélections sur leUITableView dans un tableau, mais comment puis-je les renvoyer à la vue du formulaire de saisie de données précédente afin qu'elles puissent être enregistrées avec les autres données dans Core Data lors de la soumission du formulaire?

J'ai surfé et j'ai vu des gens déclarer un tableau dans le délégué de l'application. J'ai lu quelque chose sur les singletons mais je ne comprends pas ce que c'est et j'ai lu quelque chose sur la création d'un modèle de données.

Quelle serait la bonne façon de procéder et comment procéder?

Prix ​​Matt
la source

Réponses:

1683

Cette question semble être très populaire ici sur stackoverflow, alors j'ai pensé que j'essaierais de donner une meilleure réponse pour aider les gens qui commencent dans le monde d'iOS comme moi.

J'espère que cette réponse est suffisamment claire pour que les gens comprennent et que je n'ai rien manqué.

Transmission des données vers l'avant

Transmission de données à un contrôleur de vue à partir d'un autre contrôleur de vue. Vous utiliseriez cette méthode si vous vouliez passer un objet / une valeur d'un contrôleur de vue à un autre contrôleur de vue que vous pourriez pousser sur une pile de navigation.

Pour cet exemple, nous aurons ViewControllerAetViewControllerB

Pour passer une BOOLvaleur de ViewControllerAà ViewControllerBnous ferions ce qui suit.

  1. pour ViewControllerB.hcréer une propriété pour leBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. en ViewControllerAvous devez en parler ViewControllerBalors utilisez un

    #import "ViewControllerB.h"

    Ensuite, où vous souhaitez charger la vue par exemple. didSelectRowAtIndexou certains dont IBActionvous avez besoin pour définir la propriété ViewControllerBavant de la pousser sur la pile de navigation.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.isSomethingEnabled = YES;
    [self pushViewController:viewControllerB animated:YES];

    Cela mis isSomethingEnabledà ViewControllerBla BOOLvaleur YES.

Transmission de données à l'aide de Segues

Si vous utilisez des storyboards, vous utilisez probablement des séquences et aurez besoin de cette procédure pour transmettre les données. Ceci est similaire à ce qui précède, mais au lieu de transmettre les données avant de pousser le contrôleur de vue, vous utilisez une méthode appelée

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

Donc , pour passer un BOOLde ViewControllerAà ViewControllerBnous faire ce qui suit:

  1. pour ViewControllerB.hcréer une propriété pour leBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. en ViewControllerAvous devez en parler ViewControllerBalors utilisez un

    #import "ViewControllerB.h"
  3. Créez un enchaînement de ViewControllerAà ViewControllerBsur le storyboard et donnez-lui un identifiant, dans cet exemple, nous l'appellerons"showDetailSegue"

  4. Ensuite, nous devons ajouter la méthode à ViewControllerAcelle qui est appelée quand une séquence est effectuée, à cause de cela, nous devons détecter quelle séquence a été appelée et ensuite faire quelque chose. Dans notre exemple, nous vérifierons "showDetailSegue"et si cela est effectué, nous transmettrons notre BOOLvaleur àViewControllerB

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
            controller.isSomethingEnabled = YES;
        }
    }

    Si vos vues sont intégrées dans un contrôleur de navigation, vous devez modifier légèrement la méthode ci-dessus comme suit

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
            controller.isSomethingEnabled = YES;
        }
    }

    Cela mis isSomethingEnabledà ViewControllerBla BOOLvaleur YES.

Transmission de données

Pour retransmettre des données de ViewControllerBà ViewControllerAvous devez utiliser des protocoles et des délégués ou des blocs , ces derniers peuvent être utilisés comme un mécanisme à couplage lâche pour les rappels.

Pour ce faire, nous ferons ViewControllerAun délégué de ViewControllerB. Cela permet ViewControllerBde renvoyer un message pour ViewControllerAnous permettre de renvoyer des données.

Pour ViewControllerAen être délégué, ViewControllerBil faut se conformer au ViewControllerBprotocole que nous devons préciser. Cela indique ViewControllerAquelles méthodes il doit implémenter.

  1. Dans ViewControllerB.h, en dessous de #import, mais au-dessus, @interfacevous spécifiez le protocole.

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
  2. ensuite toujours dans le dont ViewControllerB.hvous avez besoin pour configurer une delegatepropriété et synthétiser dansViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. Dans ViewControllerBnous appelons un message delegatelorsque nous ouvrons le contrôleur de vue.

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
  4. C'est tout ViewControllerB. Maintenant ViewControllerA.h, dites ViewControllerAd'importer ViewControllerBet de vous conformer à son protocole.

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
  5. En ViewControllerA.mimplémentant la méthode suivante de notre protocole

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
  6. Avant de pousser viewControllerBvers la pile de navigation, nous devons dire ViewControllerBque ViewControllerAc'est son délégué, sinon nous obtiendrons une erreur.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.delegate = self
    [[self navigationController] pushViewController:viewControllerB animated:YES];

Références

  1. Utilisation de la délégation pour communiquer avec d'autres contrôleurs de vue dans le Guide de programmation du contrôleur de vue
  2. Modèle de délégué

NSNotification center C'est une autre façon de transmettre des données.

// add observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
    id someObject = notification.object // some custom object that was passed with notification fire.
}

// post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

Transmission de données d'une classe à une autre (une classe peut être n'importe quel contrôleur, gestionnaire de réseau / session, sous-classe UIView ou toute autre classe)

Les blocs sont des fonctions anonymes.

Cet exemple transmet les données du contrôleur B au contrôleur A

définir un bloc

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

ajouter un gestionnaire de blocs (écouteur) là où vous avez besoin d'une valeur (par exemple, vous avez besoin de votre réponse API dans ControllerA ou vous avez besoin de données ContorllerB sur A)

// in ContollerA.m

- (void)viewDidLoad {
    [super viewDidLoad];
    __unsafe_unretained typeof(self) weakSelf = self;
    self.selectedVoucherBlock = ^(NSString *voucher) {
        weakSelf->someLabel.text = voucher;
    };
}

Aller au contrôleur B

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
    [self.navigationController pushViewController:vc animated:NO];

coupe-feu

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: 
(NSIndexPath *)indexPath {
    NSString *voucher = vouchersArray[indexPath.row];
    if (sourceVC.selectVoucherBlock) {
        sourceVC.selectVoucherBlock(voucher);
    }
    [self.navigationController popToViewController:sourceVC animated:YES];
}

Un autre exemple de travail pour les blocs

Prix ​​Matt
la source
24
Devons-nous également mettre un @class ViewControllerB;au-dessus de la définition @protocol? Sans cela, j'obtiens une erreur "Type attendu" sur ViewControllerB dans la ligne: - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; dans la @protocoldéclaration
alan-p
4
Cela fonctionne très bien. Comme le dit alan-p, n'oubliez pas d'écrire @class ViewControllerB; au-dessus du protocole, sinon vous recevrez une erreur "Type attendu".
Andrew Davis
6
vous n'avez pas besoin de délégués pour revenir en arrière, utilisez simplement dérouler.
malhal
4
Quand je mets "viewControllerB.delegate = self;" dans ViewControllerB, je reçois une erreur. Assigner à 'id <ViewControllerBDelegate>' du type incompatible 'ViewControllerB * const __strong', je ne suis pas sûr de ce que je fais mal. Quelqu'un peut-il aider? De plus, j'ai dû changer: initWithNib -> initWithNibName.
uplearnedu.com
4
si vous utilisez, NavigationControllervous devez utiliser à la [self.navigationController pushViewController:viewController animated:YES];place[self pushViewController:viewControllerB animated:YES];
Nazir
192

Rapide

Il y a des tonnes et des tonnes d'explications ici et autour de StackOverflow, mais si vous êtes un débutant qui essaie simplement de faire fonctionner quelque chose de basique, essayez de regarder ce tutoriel YouTube (c'est ce qui m'a aidé à enfin comprendre comment le faire).

Transmission des données au prochain View Controller

Voici un exemple basé sur la vidéo. L'idée est de passer une chaîne du champ de texte du First View Controller à l'étiquette du Second View Controller.

entrez la description de l'image ici

Créez la disposition du storyboard dans le générateur d'interface. Pour effectuer la séquence, il vous suffit de Controlcliquer sur le bouton et de le faire glisser vers le deuxième contrôleur de vue.

Contrôleur First View

Le code du First View Controller est

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

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

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

}

Second View Controller

Et le code du Second View Controller est

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
    }

}

N'oublie pas

  • Branchez les prises pour le UITextFieldet leUILabel .
  • Définissez le premier et le second View Controllers sur les fichiers Swift appropriés dans IB.

Retour des données au précédent View Controller

Pour renvoyer des données du deuxième contrôleur de vue au premier contrôleur de vue, vous utilisez un protocole et un délégué . Cette vidéo est un aperçu très clair de ce processus:

Voici un exemple basé sur la vidéo (avec quelques modifications).

entrez la description de l'image ici

Créez la disposition du storyboard dans le générateur d'interface. Encore une fois, pour faire la séquence, il vous suffit de Controlfaire glisser le bouton vers le deuxième contrôleur de vue. Définissez l'identifiant de séquence surshowSecondViewController . N'oubliez pas non plus de connecter les sorties et les actions en utilisant les noms dans le code suivant.

Contrôleur First View

Le code du First View Controller est

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {

    @IBOutlet weak var label: UILabel!

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showSecondViewController" {
            let secondViewController = segue.destination as! SecondViewController
            secondViewController.delegate = self
        }
    }

    func userDidEnterInformation(info: String) {
        label.text = info
    }
}

Notez l'utilisation de notre DataEnteredDelegateprotocole personnalisé .

Contrôleur et protocole Second View

Le code du deuxième contrôleur de vue est

import UIKit

// protocol used for sending data back
protocol DataEnteredDelegate: AnyObject {
    func userDidEnterInformation(info: String)
}

class SecondViewController: UIViewController {

    // making this a weak variable so that it won't create a strong reference cycle
    weak var delegate: DataEnteredDelegate? = nil

    @IBOutlet weak var textField: UITextField!

    @IBAction func sendTextBackButton(sender: AnyObject) {

        // call this method on whichever class implements our delegate protocol
        delegate?.userDidEnterInformation(info: textField.text!)

        // go back to the previous view controller
        _ = self.navigationController?.popViewController(animated: true)
    }
}

Notez que le protocolest en dehors de la classe View Controller.

C'est ça. En exécutant l'application maintenant, vous devriez pouvoir renvoyer des données du deuxième contrôleur de vue au premier.

Suragch
la source
Compte tenu de certaines des dernières mises à jour de Swift, est-ce toujours un modèle courant à mettre en œuvre?
piofusco
4
La plupart des mises à jour Swift que j'ai vues ont été des changements syntaxiques relativement mineurs, pas des changements dans la façon dont les données sont transmises entre les contrôleurs de vue. Si j'apprends des changements majeurs comme celui-ci, je mettrai à jour ma réponse.
Suragch
2
offtopic - iOS a un moyen si laid de transmettre des paramètres à de nouveaux contrôleurs de vue, incroyable - vous devez définir les paramètres non pas à un endroit lorsque vous passez l'appel, mais dans un autre. Android a une meilleure approche à cet égard - lorsque vous démarrez une activité, vous pouvez transmettre toutes les données (enfin, presque) via son intention de démarrage. Facile. Pas besoin de lancer ou quelque chose. La transmission des valeurs de retour à l'appelant est également une chose essentielle, pas besoin de déléguer. Bien sûr, il est possible d'utiliser des approches laides aussi, pas de problème là-bas))
Mixaz
1
@Himanshu, obtenez d'abord une référence au deuxième contrôleur de vue. Mettez ensuite à jour la variable publique qu'elle contient.
Suragch
8
@Mon chéri. Je pense que le mot «délégué» prête à confusion. Permettez-moi d'utiliser le mot «travailleur». Le "travailleur" (premier contrôleur de vue) fait tout ce que le "patron" (deuxième contrôleur de vue) lui dit de faire. Le "patron" ne sait pas qui sera son "travailleur"; ça pourrait être n'importe qui. Ainsi, dans le premier contrôleur de vue (classe "travailleur"), il est dit que je serai votre "travailleur". Vous me dites quoi écrire sur l'étiquette et je le ferai pour vous. Ainsi, secondViewController.delegate = selfsignifie «J'accepte d'être le travailleur du patron». Voir cette réponse pour un autre exemple et plus d'explications.
Suragch
136

Le M dans MVC est pour "Model" et dans le paradigme MVC le rôle des classes de modèle est de gérer les données d'un programme. Un modèle est l'opposé d'une vue - une vue sait comment afficher des données, mais elle ne sait pas quoi faire des données, tandis qu'un modèle sait tout comment travailler avec des données, mais rien comment les afficher. Les modèles peuvent être compliqués, mais ils ne doivent pas l'être - le modèle de votre application peut être aussi simple qu'un tableau de chaînes ou de dictionnaires.

Le rôle d'un contrôleur est de servir de médiateur entre la vue et le modèle. Par conséquent, ils ont besoin d'une référence à un ou plusieurs objets de vue et à un ou plusieurs objets de modèle. Disons que votre modèle est un tableau de dictionnaires, chaque dictionnaire représentant une ligne de votre tableau. La vue racine de votre application affiche cette table et il peut être responsable du chargement du tableau à partir d'un fichier. Lorsque l'utilisateur décide d'ajouter une nouvelle ligne à la table, il appuie sur un bouton et votre contrôleur crée un nouveau dictionnaire (modifiable) et l'ajoute au tableau. Afin de remplir la ligne, le contrôleur crée un contrôleur de vue de détail et lui donne le nouveau dictionnaire. Le contrôleur de vue détaillée remplit le dictionnaire et revient. Le dictionnaire fait déjà partie du modèle, donc rien d'autre ne doit se produire.

Caleb
la source
95

Il existe différentes manières de recevoir des données vers une autre classe dans iOS. Par exemple -

  1. Initialisation directe après l'allocation d'une autre classe.
  2. Délégation - pour retransmettre les données
  3. Notification - pour diffuser des données à plusieurs classes à la fois
  4. Enregistrer dans NSUserDefaults - pour y accéder plus tard
  5. Cours singleton
  6. Bases de données et autres mécanismes de stockage comme plist, etc.

Mais pour le scénario simple consistant à transmettre une valeur à une classe différente dont l'allocation est effectuée dans la classe actuelle, la méthode la plus courante et préférée serait la définition directe des valeurs après l'allocation. Cela se fait comme suit:-

Nous pouvons le comprendre en utilisant deux contrôleurs - Controller1 et Controller2

Supposons que dans la classe Controller1, vous souhaitez créer l'objet Controller2 et le pousser avec une valeur String transmise. Cela peut être fait comme ceci: -

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj passValue:@"String"];
    [self pushViewController:obj animated:YES];
}

Dans la mise en œuvre de la classe Controller2, il y aura cette fonction

@interface Controller2  : NSObject

@property (nonatomic , strong) NSString* stringPassed;

@end

@implementation Controller2

@synthesize stringPassed = _stringPassed;

- (void) passValue:(NSString *)value {

    _stringPassed = value; //or self.stringPassed = value
}

@end

Vous pouvez également définir directement les propriétés de la classe Controller2 de la même manière que ceci:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj setStringPassed:@"String"];  
    [self pushViewController:obj animated:YES];
}

Pour passer plusieurs valeurs, vous pouvez utiliser les paramètres multiples comme: -

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@“String1 andValues:objArray withDate:date]; 

Ou si vous devez transmettre plus de 3 paramètres liés à une fonction commune, vous pouvez stocker les valeurs dans une classe Model et transmettre ce modelObject à la classe suivante

ModelClass *modelObject = [[ModelClass alloc] init]; 
modelObject.property1 = _property1;
modelObject.property2 = _property2;
modelObject.property3 = _property3;

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passmodel: modelObject];

Bref, si vous voulez -

1) set the private variables of the second class initialise the values by calling a custom function and passing the values.
2) setProperties do it by directlyInitialising it using the setter method.
3) pass more that 3-4 values related to each other in some manner , then create a model class and set values to its object and pass the object using any of the above process.

J'espère que cela t'aides

borncrazy
la source
84

Après plus de recherches, il semble que Protocoles et Délégués soit la manière correcte / préférée d'Apple de le faire.

J'ai fini par utiliser cet exemple

Partage de données entre les contrôleurs de vue et d'autres objets @ SDK de développement iPhone

A très bien fonctionné et m'a permis de passer une chaîne et un tableau en avant et en arrière entre mes vues.

Merci pour votre aide

Prix ​​Matt
la source
3
n'utilisez pas de protocoles et de délégués, utilisez simplement le déroulement.
malhal
1
@malhal Et si vous n'utilisez pas de story-boards ??
Evan R du
Je déteste aussi les protocoles et les délégués inutiles. @malhal
DawnSong
@EvanR Vous pouvez créer et effectuer des séquences dans le code. C'est tout pareil.
DawnSong
1
Essentiellement, l'ensemble du contrôle qualité sur cette page est "de l'ancien temps avant les vues de conteneur". Vous ne vous soucieriez jamais d'un million d'années avec des protocoles ou des délégués maintenant. Chaque petite chose que vous faites sur n'importe quel écran est de toute façon une vue de conteneur, donc la question n'existe plus vraiment - vous avez déjà toutes les références "de haut en bas" de toutes les vues de conteneur.
Fattie
66

Je trouve la version la plus simple et la plus élégante avec des blocs qui passent. Appelons le contrôleur de vue qui attend les données renvoyées comme "A" et le contrôleur de vue qui le renvoie comme "B". Dans cet exemple, nous voulons obtenir 2 valeurs: la première de Type1 et la seconde de Type2.

En supposant que nous utilisons Storyboard, le premier contrôleur définit le bloc de rappel, par exemple lors de la préparation de la séquence:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[BViewController class]])
    {
        BViewController *viewController = segue.destinationViewController;

        viewController.callback = ^(Type1 *value1, Type2 *value2) {
            // optionally, close B
            //[self.navigationController popViewControllerAnimated:YES];

            // let's do some action after with returned values
            action1(value1);
            action2(value2);
        };

    }
}

et le contrôleur de vue "B" doit déclarer la propriété de rappel, BViewController.h:

// it is important to use "copy"
@property (copy) void(^callback)(Type1 *value1, Type2 *value2);

Que dans le fichier d'implémentation BViewController.m après que nous ayons souhaité que les valeurs renvoyées, notre rappel soit appelé:

if (self.callback)
    self.callback(value1, value2);

Une chose à retenir est que l'utilisation du bloc nécessite souvent de gérer des références fortes et __ faibles comme expliqué ici

Leszek Zarna
la source
Pourquoi ne pas faire de la valeur un paramètre du bloc de rappel plutôt que d'être une propriété distincte?
Timuçin
56

La plupart des réponses données contiennent de bonnes informations, mais aucune ne répond pleinement à la question.

La question porte sur la transmission d'informations entre les contrôleurs de vue. L'exemple spécifique donné concerne la transmission d'informations entre les vues, mais compte tenu de la nouveauté auto-déclarée d'iOS, l'affiche originale signifiait probablement entre les viewControllers, pas entre les vues (sans aucune implication des ViewControllers). Il semble que toutes les réponses se concentrent sur deux contrôleurs de vue, mais que faire si l'application évolue pour impliquer plus de deux contrôleurs de vue dans l'échange d'informations?

L'affiche originale a également posé des questions sur les singletons et l'utilisation de l' AppDelegate . Il faut répondre à ces questions.

Pour aider quiconque qui regarde cette question et qui veut une réponse complète, je vais essayer de la fournir.

Scénarios d'application

Plutôt que d'avoir une discussion abstraite hautement hypothétique, il est utile d'avoir à l'esprit des applications concrètes. Pour aider à définir une situation de contrôleur à deux vues et une situation de contrôleur à plus de deux vues, je vais définir deux scénarios d'application concrets.

Scénario 1: deux contrôleurs de vue au maximum doivent partager des informations. Voir schéma un.

diagramme du problème d'origine

Il existe deux contrôleurs de vue dans l'application. Il existe un ViewControllerA (formulaire de saisie de données) et View Controller B (liste de produits). Les éléments sélectionnés dans la liste de produits doivent correspondre aux éléments affichés dans la zone de texte du formulaire de saisie de données. Dans ce scénario, ViewControllerA et ViewControllerB doivent communiquer directement entre eux et aucun autre contrôleur de vue.

Scénario deux : plus de deux contrôleurs de vue doivent partager les mêmes informations. Voir le diagramme deux.

schéma d'application de l'inventaire de la maison

Il y a quatre contrôleurs de vue dans l'application. Il s'agit d'une application basée sur des onglets pour gérer l'inventaire des maisons. Trois contrôleurs de vue présentent des vues filtrées différemment des mêmes données:

  • ViewControllerA - Articles de luxe
  • ViewControllerB - Articles non assurés
  • ViewControllerC - Inventaire de toute la maison
  • ViewControllerD - Ajouter un nouveau formulaire d'élément

Chaque fois qu'un élément individuel est créé ou modifié, il doit également se synchroniser avec les autres contrôleurs de vue. Par exemple, si nous ajoutons un bateau dans ViewControllerD, mais qu'il n'est pas encore assuré, le bateau doit apparaître lorsque l'utilisateur accède à ViewControllerA (articles de luxe), ainsi qu'à ViewControllerC (inventaire de toute la maison), mais pas lorsque l'utilisateur accède à ViewControllerB (Articles non assurés). Nous devons nous préoccuper non seulement d'ajouter de nouveaux éléments, mais également de supprimer des éléments (qui peuvent être autorisés à partir de n'importe lequel des quatre contrôleurs de vue), ou de modifier des éléments existants (qui peuvent être autorisés à partir du "Ajouter un nouveau formulaire d'élément", en réaffectant le même pour l'édition).

Étant donné que tous les contrôleurs de vue doivent partager les mêmes données, les quatre contrôleurs de vue doivent rester synchronisés, et il doit donc y avoir une sorte de communication avec tous les autres contrôleurs de vue, chaque fois qu'un seul contrôleur de vue modifie les données sous-jacentes. Il devrait être assez évident que nous ne voulons pas que chaque contrôleur de vue communique directement entre eux dans ce scénario. Dans le cas où ce n'est pas évident, considérez si nous avions 20 contrôleurs de vue différents (plutôt que seulement 4). Dans quelle mesure serait-il difficile et sujet à erreurs de notifier chacun des 19 autres contrôleurs de vue chaque fois qu'un contrôleur de vue effectuait un changement?

Les solutions: les délégués et le modèle d'observateur, et les singletons

Dans le scénario un, nous avons plusieurs solutions viables, comme d'autres réponses ont donné

  • segues
  • délégués
  • définition directe des propriétés sur les contrôleurs de vue
  • NSUserDefaults (en fait un mauvais choix)

Dans le scénario deux, nous avons d'autres solutions viables:

  • Modèle d'observateur
  • Singletons

Un singleton est une instance d'une classe, cette instance étant la seule instance existant au cours de sa durée de vie. Un singleton tire son nom du fait qu'il s'agit de l'instance unique. Normalement, les développeurs qui utilisent des singletons ont des méthodes de classe spéciales pour y accéder.

+ (HouseholdInventoryManager*) sharedManager; {
    static dispatch_once_t onceQueue;
    static HouseholdInventoryManager* _sharedInstance;

    // dispatch_once is guaranteed to only be executed once in the
    // lifetime of the application
    dispatch_once(&onceQueue, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

Maintenant que nous comprenons ce qu'est un singleton, voyons comment un singleton s'intègre dans le modèle d'observateur. Le motif d'observateur est utilisé pour qu'un objet réponde aux changements d'un autre objet. Dans le deuxième scénario, nous avons quatre contrôleurs de vue différents, qui veulent tous être informés des modifications apportées aux données sous-jacentes. Les "données sous-jacentes" doivent appartenir à une seule instance, un singleton. Le "savoir sur les changements" est accompli en observant les changements apportés au singleton.

L'application d'inventaire d'origine aurait une seule instance d'une classe conçue pour gérer une liste d'articles d'inventaire. Le gestionnaire gérerait une collection d'articles ménagers. Voici une définition de classe pour le gestionnaire de données:

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

@interface HouseholdInventoryManager : NSObject
/*!
 The global singleton for accessing application data
 */
+ (HouseholdInventoryManager*) sharedManager;


- (NSArray *) entireHouseholdInventory;
- (NSArray *) luxuryItems;
- (NSArray *) nonInsuredItems;

- (void) addHouseholdItemToHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) editHouseholdItemInHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) deleteHoueholdItemFromHomeInventory:(JGCHouseholdInventoryItem*)item;
@end

Lorsque la collection d'articles d'inventaire d'origine change, les contrôleurs de vue doivent être informés de cette modification. La définition de classe ci-dessus ne rend pas évident comment cela se produira. Nous devons suivre le modèle de l'observateur. Les contrôleurs de vue doivent observer formellement le sharedManager. Il existe deux façons d'observer un autre objet:

  • Observation clé-valeur (KVO)
  • NSNotificationCenter.

Dans le scénario deux, nous n'avons pas une seule propriété de HouseholdInventoryManager qui pourrait être observée à l'aide de KVO. Parce que nous n'avons pas une seule propriété facilement observable, le modèle d'observateur, dans ce cas, doit être implémenté à l'aide de NSNotificationCenter. Chacun des quatre contrôleurs de vue souscrirait aux notifications et le sharedManager enverrait des notifications au centre de notifications, le cas échéant. Le gestionnaire d'inventaire n'a pas besoin de savoir quoi que ce soit sur les contrôleurs de vue ou les instances d'autres classes qui pourraient être intéressés de savoir quand la collection d'articles d'inventaire change; NSNotificationCenter s'occupe de ces détails d'implémentation. Les contrôleurs de vue s'abonnent simplement aux notifications et le gestionnaire de données publie simplement les notifications.

De nombreux programmeurs débutants profitent du fait qu'il y a toujours exactement un délégué d'application dans la durée de vie de l'application, ce qui est globalement accessible. Les programmeurs débutants utilisent ce fait pour bourrer des objets et des fonctionnalités dans appDelegate pour faciliter l'accès à partir de n'importe où ailleurs dans l'application. Ce n'est pas parce que AppDelegate est un singleton qu'il doit remplacer tous les autres singletons. C'est une mauvaise pratique car elle impose trop de charge à une classe, ce qui brise les bonnes pratiques orientées objet. Chaque classe doit avoir un rôle clair qui s'explique facilement, souvent simplement par le nom de la classe.

Chaque fois que votre délégué d'application commence à se gonfler, commencez à supprimer les fonctionnalités des singletons. Par exemple, la pile de données principale ne doit pas être laissée dans AppDelegate, mais doit plutôt être placée dans sa propre classe, une classe coreDataManager.

Références

Jason Cross
la source
41

L'OP ne mentionnait pas les contrôleurs de vue, mais tant de réponses le font, que je voulais répondre à ce que certaines des nouvelles fonctionnalités de la LLVM permettent de rendre plus facile lorsque vous souhaitez transmettre des données d'un contrôleur de vue à un autre, puis obtenir des résultats.

Les séquences de storyboard, les blocs ARC et LLVM rendent cela plus facile que jamais pour moi. Certaines réponses ci-dessus mentionnaient déjà des scénarios et des séquences, mais reposaient toujours sur la délégation. Définir des délégués fonctionne certainement, mais certaines personnes peuvent trouver plus facile de passer des pointeurs ou des blocs de code.

Avec UINavigators et segues, il existe des moyens faciles de transmettre des informations au contrôleur secondaire et de récupérer les informations. ARC facilite le passage de pointeurs vers des éléments dérivés de NSObjects. Si vous souhaitez que le contrôleur subservient ajoute / change / modifie des données pour vous, passez-lui un pointeur vers une instance mutable. Les blocs facilitent le passage des actions, donc si vous voulez que le contrôleur subordonné invoque une action sur votre contrôleur de niveau supérieur, passez-lui un bloc. Vous définissez le bloc pour accepter n'importe quel nombre d'arguments qui vous conviennent. Vous pouvez également concevoir l'API pour utiliser plusieurs blocs si cela convient mieux.

Voici deux exemples triviaux de la colle segue. Le premier montre simplement un paramètre passé pour l'entrée, le second pour la sortie.

// Prepare the destination view controller by passing it the input we want it to work on
// and the results we will look at when the user has navigated back to this controller's view.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results
     // by virtue of both controllers having a pointer to the same object.
     andResults:self.resultsFromNextController];
}

Ce deuxième exemple montre comment passer un bloc de rappel pour le deuxième argument. J'aime utiliser des blocs car il garde les détails pertinents proches les uns des autres dans la source - la source de niveau supérieur.

// Prepare the destination view controller by passing it the input we want it to work on
// and the callback when it has done its work.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results.
     resultsBlock:^(id results) {
         // This callback could be as involved as you like.
         // It can use Grand Central Dispatch to have work done on another thread for example.
        [self setResultsFromNextController:results];
    }];
}
WeakPointer
la source
41

La transmission de données de ViewController 2 (destination) à viewController 1 (Source) est la chose la plus intéressante. En supposant que vous utilisez storyBoard, voici toutes les façons dont j'ai découvert:

  • Déléguer
  • Notification
  • Valeurs par défaut de l'utilisateur
  • Singleton

Celles-ci ont déjà été discutées ici.

J'ai trouvé qu'il y avait plus de façons:

-Utilisation des rappels de bloc:

l'utiliser dans la prepareForSegueméthode du VC1

NextViewController *destinationVC = (NextViewController *) segue.destinationViewController;
[destinationVC setDidFinishUsingBlockCallback:^(NextViewController *destinationVC)
{
    self.blockLabel.text = destination.blockTextField.text;
}];

-Utilisation de storyboards Unwind (Quitter)

Implémentez une méthode avec un argument UIStoryboardSegue dans VC 1, comme celui-ci:

-(IBAction)UnWindDone:(UIStoryboardSegue *)segue { }

Dans le StoryBoard, accrochez le bouton "retour" au bouton vert de sortie (déroulement) du vc. Maintenant, vous avez une séquence qui "remonte" afin que vous puissiez utiliser la propriété destinationViewController dans prepareForSegue de VC2 et modifier n'importe quelle propriété de VC1 avant qu'elle ne revienne.

  • Une autre option d'utilisation des storyboards Undwind (Exit) - vous pouvez utiliser la méthode que vous avez écrite dans VC1

    -(IBAction)UnWindDone:(UIStoryboardSegue *)segue {
        NextViewController *nextViewController = segue.sourceViewController;
        self.unwindLabel.text = nextViewController.unwindPropertyPass;
    } 

    Et dans le prepareForSegue de VC1, vous pouvez modifier n'importe quelle propriété que vous souhaitez partager.

Dans les deux options de déroulement, vous pouvez définir la propriété de balise du bouton et la vérifier dans prepareForSegue.

J'espère que j'ai ajouté quelque chose à la discussion.

:) À votre santé.

Yevgeni
la source
40

Il existe plusieurs méthodes pour partager des données.

  1. Vous pouvez toujours partager des données à l'aide de NSUserDefaults. Définissez la valeur que vous souhaitez partager par rapport à une clé de votre choix et obtenez la valeur NSUserDefaultassociée à cette clé dans le prochain contrôleur de vue.

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. Vous pouvez simplement créer une propriété dans viewcontrollerA. Créez un objet de viewcontrollerAin viewcontrollerBet affectez la valeur souhaitée à cette propriété.

  3. Vous pouvez également créer des délégués personnalisés pour cela.

Anubrata Santra
la source
30
Le but typique de NSUserDefaults est de stocker les préférences utilisateur qui persistent entre les exécutions d'applications, donc tout ce qui est stocké ici restera ici à moins qu'il ne soit explicitement supprimé. C'est vraiment une mauvaise idée de l'utiliser pour transmettre des informations entre les contrôleurs de vue (ou tout autre objet) dans une application.
José González
30

Si vous souhaitez transmettre des données d'un contrôleur à un autre, essayez ce code

FirstViewController.h

@property (nonatomic, retain) NSString *str;

SecondViewController.h

@property (nonatomic, retain) NSString *str1;

FirstViewController.m

- (void)viewDidLoad
   {
     // message for the second SecondViewController
     self.str = @"text message";

     [super viewDidLoad];
   }

-(IBAction)ButtonClicked
 {
   SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
   secondViewController.str1 = str;
  [self.navigationController pushViewController:secondViewController animated:YES];
 }
user2998756
la source
29

Ceci est une réponse très ancienne et anti-modèle, veuillez utiliser des délégués. N'utilisez pas cette approche !!

1. Créez l'instance du premier View Controller dans le second View Controller et définissez sa propriété @property (nonatomic,assign).

2. Attribuez l' SecondviewControllerinstance de ce contrôleur de vue.

2. Lorsque vous avez terminé l'opération de sélection, copiez la matrice sur le premier contrôleur de vue. Lorsque vous déchargez SecondView, FirstView contiendra les données de la matrice.

J'espère que cela t'aides.

kaar3k
la source
2
Je ne crois pas que ce soit la bonne façon de procéder, car cela crée un lien très faussé entre les contrôleurs de vue. Ne colle pas vraiment à MVC.
Matt Price
1
Si vous souhaitez suivre strictement MVC, utilisez NSNotificationCenter, une méthode peut être appelée de ViewControllerA à ViewControllerB, vérifiez que cela pourrait vous aider
kaar3k
28

Je cherchais cette solution depuis longtemps, Atlast je l'ai trouvée. Tout d'abord, déclarez tous les objets de votre fichier SecondViewController.h comme

@interface SecondViewController: UIviewController 
{
    NSMutableArray *myAray;
    CustomObject *object;
}

Maintenant, dans votre fichier d'implémentation, allouez la mémoire pour ces objets comme celui-ci

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self) 
     {
         // Custom initialization
         myAray=[[NSMutableArray alloc] init];
         object=[[CustomObject alloc] init];
     }
     return self;
}

Vous avez maintenant alloué la mémoire Arrayet l'objet. vous pouvez maintenant remplir cette mémoire avant de pousserViewController

Accédez à votre SecondViewController.h et écrivez deux méthodes

-(void)setMyArray:(NSArray *)_myArray;
-(void)setMyObject:(CustomObject *)_myObject;

dans le fichier d'implémentation, vous pouvez implémenter la fonction

-(void)setMyArray:(NSArray *)_myArray
{
     [myArra addObjectsFromArray:_myArray];
}
-(void)setMyObject:(CustomObject *)_myObject
{
     [object setCustomObject:_myObject];
}

en attendant que vous CustomObjectdeviez avoir une fonction setter avec elle.

maintenant votre travail de base est terminé. allez à l'endroit où vous voulez pousser SecondViewControlleret faites les choses suivantes

SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]] ;
[secondView setMyArray:ArrayToPass];
[secondView setMyObject:objectToPass];
[self.navigationController pushViewController:secondView animated:YES ];

Faites attention aux fautes d'orthographe.

AsifHabib
la source
24

Ce n'est pas la façon de le faire, vous devez utiliser des délégués, je suppose que nous avons deux contrôleurs de vue ViewController1 et ViewController2 et cette vérification est dans la première et lorsque son état change, vous voulez faire quelque chose dans ViewController2, pour Pour y parvenir correctement, vous devez procéder comme suit:

Ajoutez un nouveau fichier à votre projet (protocole Objective-C) Fichier -> Nouveau, nommez-le maintenant ViewController1Delegate ou tout ce que vous voulez et écrivez-les entre les directives @interface et @end

@optional

- (void)checkStateDidChange:(BOOL)checked;

Accédez maintenant à ViewController2.h et ajoutez

#import "ViewController1Delegate.h"

puis changez sa définition en

@interface ViewController2: UIViewController<ViewController1Delegate>

Allez maintenant à ViewController2.m et à l'intérieur de l'implémentation ajoutez:

- (void)checkStateDidChange:(BOOL)checked {
     if (checked) {
           // Do whatever you want here
           NSLog(@"Checked");
     }
     else {
           // Also do whatever you want here
           NSLog(@"Not checked");
     }
}

Accédez maintenant à ViewController1.h et ajoutez la propriété suivante:

@property (weak, nonatomic) id<ViewController1Delegate> delegate; 

Maintenant, si vous créez ViewController1 dans ViewController2 après un événement, vous devez le faire de cette façon en utilisant des fichiers NIB:

ViewController1* controller = [[NSBundle mainBundle] loadNibNamed:@"ViewController1" owner:self options:nil][0];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];

Maintenant, vous êtes prêt, chaque fois que vous détectez que l’événement de vérification a changé dans ViewController1, tout ce que vous avez à faire est le suivant

[delegate checkStateDidChange:checked]; // You pass here YES or NO based on the check state of your control

Veuillez me dire s'il y a quelque chose qui n'est pas clair si je n'ai pas bien compris votre question.

Boda Taljo
la source
23

Si vous souhaitez envoyer des données de l'un à l'autre viewController, voici une façon de procéder:

Disons que nous avons viewControllers: viewControllerA et viewControllerB

Maintenant dans viewControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

- (id)initWithArray:(NSArray)a andString:(NSString)s;

In viewControllerB.m

#import "viewControllerB.h"

@implementation viewControllerB

- (id)initWithArray:(NSArray)a andString:(NSString)s {

   array = [[NSArray alloc] init];
   array = a;

   string = [[NSString alloc] init];
   string = s;

}

In viewControllerA.m

#import "viewControllerA.h"
#import "viewControllerB.h"

@implementation viewControllerA

- (void)someMethod {

  someArray = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];
  someString = [NSString stringWithFormat:@"Hahahahaha"];

  viewControllerB *vc = [[viewControllerB alloc] initWithArray:someArray andString:someString];

  [self.navigationController pushViewController:vc animated:YES];
  [vc release];

}

C'est ainsi que vous pouvez transmettre des données de viewControllerA à viewControllerB sans définir de délégué. ;)

Aniruddh Joshi
la source
1
J'ai essayé d'utiliser le code ur dans mon projet, mais je ne suis pas en mesure d'obtenir les valeurs dans viewcontrollerB. Pouvez-vous me dire quel pourrait être le problème?
The X-Coder
1
@Ajitthala Pouvez-vous coller votre code dans une nouvelle question? Je vais essayer de résoudre votre problème. :)
Aniruddh Joshi
1
est-ce mal de ne pas utiliser les méthodes init et de faire quelque chose comme vcB.string = @ "asdf" du viewcontroller A?
khanh.tran.vinh
1
@ khanh.tran.vinh Selon que vous utilisez ARC ou non.
Aniruddh Joshi
21

Je sais que c'est un sujet battu mais pour ceux qui cherchent à répondre à cette question avec une pente SWIFT et veulent un exemple à nu, voici ma méthode de référence pour transmettre des données si vous utilisez un enchaînement pour vous déplacer.

Il est similaire à ce qui précède mais sans les boutons, les étiquettes et autres. Passer simplement les données d'une vue à l'autre.

Configurer le storyboard

Il y a trois parties.

  1. L'expéditeur
  2. The Segue
  3. Le récepteur

Il s'agit d'une disposition de vue très simple avec une séquence entre eux.


Disposition de vue très simple.  Remarque: pas de contrôleur de navigation


Voici la configuration pour l'expéditeur


L'expéditeur


Voici la configuration du récepteur.


Le récepteur


Enfin, la configuration de la séquence.


L'identifiant Segue


Les contrôleurs de vue

Nous gardons cela simple, donc pas de boutons, pas d'actions, nous déplaçons simplement les données de l'expéditeur vers le récepteur lorsque l'application se charge, puis la sortie de la valeur transmise vers la console.

Cette page prend la valeur initialement chargée et la transmet.

import UIKit


class ViewControllerSender: UIViewController {

    // THE STUFF - put some info into a variable
    let favoriteMovie = "Ghost Busters"

    override func viewDidAppear(animated: Bool) {
        // PASS IDENTIFIER - go to the recieving view controller.
        self.performSegueWithIdentifier("goToReciever", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        //GET REFERENCE - ...to the receiver view.
        var viewControllerReceiver = segue.destinationViewController as? ViewControllerReceiver

        //PASS STUFF - pass the variable along to the target.
        viewControllerReceiver!.yourFavMovie = self.favoriteMovie

    }

}

Cette page envoie simplement la valeur de la variable à la console lors de son chargement. À ce stade, notre film préféré devrait être dans cette variable.

import UIKit

class ViewControllerReceiver: UIViewController {

    //Basic empty variable waiting for you to pass in your fantastic favorite movie.
    var yourFavMovie = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        //And now we can view it in the console.
        println("The Movie is \(self.yourFavMovie)")

    }   
}

Voilà comment vous pouvez y remédier si vous souhaitez utiliser un enchaînement et que vous n'avez pas vos pages sous un contrôleur de navigation.

Une fois exécuté, il doit passer automatiquement à la vue du récepteur et transmettre la valeur de l'expéditeur au récepteur, en affichant la valeur dans la console.

Ghost Busters est un classique.

Christopher Wade Cantley
la source
19

Dans mon cas, j'ai utilisé une classe singleton qui peut fonctionner comme un objet global permettant d'accéder aux données de presque partout dans l'application. La première chose est de construire une classe singleton. S'il vous plaît se référer à la page « Que dois - je Objective-C singleton ressembler? » Et ce que je faisais simplement de faire l'objet était globalement accessible importer dans ce appName_Prefix.pchqui est d'appliquer la déclaration d'importation dans toutes les classes. Pour accéder à cet objet et l'utiliser, j'ai simplement implémenté une méthode de classe pour retourner l'instance partagée, qui contient ses propres variables

petershine
la source
Ceci est la bonne réponse. Utilisez simplement un singleton comme "modèle". Notez que, comme le dit Caleb, "le modèle de votre application peut être aussi simple qu'un tableau de chaînes" . Il est essentiel de noter que faire un singleton dans Swift, est vraiment trivial . (Si simple, cela ne vaut même pas la peine d'être mentionné ici - juste google.) Pour les nouveaux programmeurs, il vaut la peine de comprendre que faire un singleton était une vraie douleur dans le cul . Cependant, les singletons sont absolument essentiels à la programmation iOS - tout ce que fait Apple est un singleton. C'est pourquoi Apple a finalement décidé de créer correctement des singletons (en Swift).
Fattie
1
Notez, cependant, que de nos jours (2016+) "tout est une vue de conteneur dans iOS". Chaque chose que vous faites à l'écran vous fait une petite vue de conteneur. Il est assez trivial d'obtenir les références "haut et bas" des chaînes de vues de conteneurs (bien qu'Apple facilitera cela à l'avenir), et vous le faites de toute façon pour presque toutes les vues de conteneurs. Donc, si vous l'avez fait de toute façon - vous avez la réponse; pas besoin d'un singleton. Intro vue conteneur ... stackoverflow.com/a/23403979/294884
Fattie
19

Swift 5

Eh bien, la réponse de Matt Price convient parfaitement pour transmettre des données, mais je vais les réécrire, dans la dernière version de Swift, car je crois que les nouveaux programmeurs trouvent difficile de quitter en raison de la nouvelle syntaxe et des nouvelles méthodes / cadres, car la publication d'origine est en Objective-C.

Il existe plusieurs options pour transmettre des données entre les contrôleurs de vue.

  1. Utilisation de Navigation Controller Push
  2. Utilisation de Segue
  3. Utilisation de Delegate
  4. Utilisation de Notification Observer
  5. Utilisation de Block

Je vais réécrire sa logique dans Swift avec le dernier framework iOS


Transmission de données via la navigation du contrôleur de navigation : de ViewControllerA à ViewControllerB

Étape 1. Déclarez la variable dans ViewControllerB

var isSomethingEnabled = false

Étape 2. Imprimer la variable dans la méthode ViewDidLoad de ViewControllerB

override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through segue, navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }

Étape 3. Dans ViewControllerA Pass Data tout en poussant à travers le contrôleur de navigation

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
        viewControllerB.isSomethingEnabled = true
        if let navigator = navigationController {
            navigator.pushViewController(viewControllerB, animated: true)
        }
    }

Voici donc le code complet pour:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:Passing Data through Navigation PushViewController
    @IBAction func goToViewControllerB(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.isSomethingEnabled = true
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    //MARK:  - Variable for Passing Data through Navigation push   
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }
}

Transmission de données via Segue : de ViewControllerA à ViewControllerB

Étape 1. Créez Segue de ViewControllerA à ViewControllerB et donnez Identifier = showDetailSegue dans Storyboard comme indiqué ci-dessous

entrez la description de l'image ici

Étape 2. Dans ViewControllerB, déclarez un isSomethingEnabled viable nommé et imprimer sa valeur.

Étape 3. Dans ViewControllerA, la valeur de isSomethingEnabled lors de la transmission de Segue

Voici donc le code complet pour:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:  - - Passing Data through Segue  - - 
    @IBAction func goToViewControllerBUsingSegue(_ sender: Any) {
        performSegue(withIdentifier: "showDetailSegue", sender: nil)
    }

    //Segue Delegate Method
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "showDetailSegue") {
            let controller = segue.destination as? ViewControllerB
            controller?.isSomethingEnabled = true//passing data
        }
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through segue
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }
}

Transmission de données via le délégué : de ViewControllerB à ViewControllerA

Étape 1. Déclarez le protocole ViewControllerBDelegate dans le fichier ViewControllerB mais en dehors de la classe

protocol ViewControllerBDelegate: NSObjectProtocol {

    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

Étape 2. Déclarez l'instance de variable Délégué dans ViewControllerB

var delegate: ViewControllerBDelegate?

Étape 3. Envoyer des données pour le délégué à l'intérieur de la méthode viewDidLoad de ViewControllerB

delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")

Étape 4. Confirmez ViewControllerBDelegate dans ViewControllerA

class ViewControllerA: UIViewController, ViewControllerBDelegate  {
// to do
}

Étape 5. Confirmez que vous implémenterez délégué dans ViewControllerA

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self//confirming delegate
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }

Étape 6. Implémentez la méthode déléguée pour recevoir des données dans ViewControllerA

func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

Voici donc le code complet pour:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController, ViewControllerBDelegate  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //Delegate method
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

    @IBAction func goToViewControllerForDelegate(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

ViewControllerB

import UIKit

//Protocol decleare
protocol ViewControllerBDelegate: NSObjectProtocol {
    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

class ViewControllerB: UIViewController {
    var delegate: ViewControllerBDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        //MARK:  - - - -  Set Data for Passing Data through Delegate  - - - - - -
        delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")
    }
}

Transmission de données via Notification Observer : de ViewControllerB à ViewControllerA

Étape 1. Définir et publier des données dans Notification Observer dans ViewControllerB

let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)

Étape 2. Ajouter un observateur de notifications dans ViewControllerA

NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)

Étape 3. Recevoir la valeur des données de notification dans ViewControllerA

@objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification : ", notification.object ?? "")
    }

Voici donc le code complet pour:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController{

    override func viewDidLoad() {
        super.viewDidLoad()

        // add observer in controller(s) where you want to receive data
        NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)
    }

    //MARK: Method for receiving Data through Post Notification 
    @objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification : ", notification.object ?? "")
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //MARK:Set data for Passing Data through Post Notification
        let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)
    }
}

Passage de données via le bloc : de ViewControllerB à ViewControllerA

Étape 1. Déclarez le bloc dans ViewControllerB

var autorisationCompletionBlock: ((Bool) -> ())? = {_ in}

Étape 2. Définissez les données en bloc dans ViewControllerB

if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }

Étape 3. Recevoir des données de bloc dans ViewControllerA

//Receiver Block
                controller!.authorizationCompletionBlock = { isGranted in
                    print("Data received from Block is :", isGranted)
                }

Voici donc le code complet pour:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:Method for receiving Data through Block
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if (segue.identifier == "showDetailSegue") {
                let controller = segue.destination as? ViewControllerB
                controller?.isSomethingEnabled = true

                //Receiver Block
                controller!.authorizationCompletionBlock = { isGranted in
                    print("Data received from Block is :", isGranted)
                }
            }
        }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    //MARK:Variable for Passing Data through Block
    var authorizationCompletionBlock:((Bool)->())? = {_ in}

    override func viewDidLoad() {
        super.viewDidLoad()

        //MARK:Set data for Passing Data through Block
        if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }
    }
}

Vous pouvez trouver un exemple complet d'application sur mon GitHub Veuillez me faire savoir si vous avez des questions à ce sujet.

swiftBoy
la source
18

Passage de données entre FirstViewController à SecondViewController comme ci-dessous

Par exemple:

FirstViewController Valeur de chaîne en tant que

StrFirstValue = @"first";

afin que nous puissions passer cette valeur en deuxième classe en utilisant l'étape ci-dessous

1> Nous devons créer un objet chaîne dans le fichier SecondViewController.h

NSString *strValue;

2> Besoin de déclarer la propriété comme ci-dessous la déclaration dans le fichier .h

@property (strong, nonatomic)  NSString *strSecondValue;

3> Besoin de synthétiser cette valeur dans le fichier FirstViewController.m sous la déclaration d'en-tête

@synthesize strValue;

et dans FirstViewController.h:

@property (strong, nonatomic)  NSString *strValue;

4> Dans FirstViewController, à partir de quelle méthode nous accédons à la deuxième vue, veuillez écrire le code ci-dessous dans cette méthode.

SecondViewController *secondView= [[SecondViewController alloc]     
initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]];

[secondView setStrSecondValue:StrFirstValue];

[self.navigationController pushViewController:secondView animated:YES ];
Chris Alan
la source
Après avoir été dans le SecondViewController, comment retransmettez-vous les données au FirstViewController?
bruno
18

Je contribue actuellement à une solution open source à ce problème via un projet appelé MCViewFactory, qui peut être trouvé ici:

https://github.com/YetiHQ/manticore-iosviewfactory

L'idée est d'imiter le paradigme d'intention d'Android, en utilisant une usine globale pour gérer la vue que vous regardez et en utilisant des «intentions» pour basculer et transmettre des données entre les vues. Toute la documentation est sur la page github, mais voici quelques points saillants:

Vous configurez toutes vos vues dans des fichiers .XIB et les enregistrez dans le délégué d'application, tout en initialisant la fabrique.

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

// the following two lines are optional. 
[factory registerView:@"YourSectionViewController"]; 

Maintenant, dans votre VC, chaque fois que vous souhaitez passer à un nouveau VC et transmettre des données, vous créez une nouvelle intention et ajoutez des données à son dictionnaire (savedInstanceState). Ensuite, définissez simplement l'intention actuelle de l'usine:

MCIntent* intent = [MCIntent intentWithSectionName:@"YourSectionViewController"];
[intent setAnimationStyle:UIViewAnimationOptionTransitionFlipFromLeft];
[[intent savedInstanceState] setObject:@"someValue" forKey:@"yourKey"];
[[intent savedInstanceState] setObject:@"anotherValue" forKey:@"anotherKey"];
// ...
[[MCViewModel sharedModel] setCurrentSection:intent];

Toutes vos vues qui se conforment à cela doivent être des sous-classes de MCViewController, qui vous permettent de remplacer la nouvelle méthode onResume:, vous permettant d'accéder aux données que vous avez transmises.

-(void)onResume:(MCIntent *)intent {
    NSObject* someValue = [intent.savedInstanceState objectForKey:@"yourKey"];
    NSObject* anotherValue = [intent.savedInstanceState objectForKey:@"anotherKey"];

    // ...

    // ensure the following line is called, especially for MCSectionViewController
    [super onResume:intent];
}

J'espère que certains d'entre vous trouveront cette solution utile / intéressante.


la source
Ensuite, tous les objets du contrôleur pourraient obtenir / définir tous les dictionnaires enregistrés dans toutes les étendues? Downvote this.
Itachi
15

Créez la propriété sur next view controller .het définissez getter et setter.

Ajoutez ceci propertydans NextVC.h sur nextVC

@property (strong, nonatomic) NSString *indexNumber;

Ajouter

@synthesize indexNumber; dans NextVC.m

Enfin

NextVC *vc=[[NextVC alloc]init];

vc.indexNumber=@"123";

[self.navigationController vc animated:YES];
Vivek Yadav
la source
11

Il existe de nombreuses façons de procéder et il est important de choisir la bonne. L'une des plus grandes décisions architecturales repose probablement sur la façon dont le code du modèle sera partagé ou accessible dans l'application.

J'ai écrit un article à ce sujet il y a quelque temps: Partager le code modèle . Voici un bref résumé:

Données partagées

Une approche consiste à partager des pointeurs vers les objets du modèle entre les contrôleurs de vue.

  • Brute forcer l'itération sur les contrôleurs de vue (dans Navigation ou Tab Bar Controller) pour définir les données
  • Définissez les données dans prepareForSegue (si les storyboards) ou init (si programmatique)

Étant donné que la préparation à la séquence est la plus courante, voici un exemple:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var next = segue.destinationViewController as NextViewController
    next.dataSource = dataSource
}

Accès indépendant

Une autre approche consiste à gérer un écran plein de données à la fois et au lieu de coupler les contrôleurs de vue les uns aux autres, coupler chaque contrôleur de vue à une seule source de données à laquelle ils peuvent accéder indépendamment.

La façon la plus courante de voir cela est une instance singleton . Donc, si votre objet singleton était, DataAccessvous pouvez effectuer les opérations suivantes dans la méthode viewDidLoad de UIViewController:

func viewDidLoad() {
    super.viewDidLoad()
    var data = dataAccess.requestData()
}

Il existe des outils supplémentaires qui permettent également de transmettre des données:

  • Observation de valeur-clé
  • NSNotification
  • Données de base
  • NSFetchedResultsController
  • La source de données

Données de base

Ce qui est bien avec Core Data, c'est qu'il a des relations inverses. Donc, si vous voulez simplement donner à NotesViewController l'objet notes, vous pouvez le faire, car il aura une relation inverse avec quelque chose d'autre comme le bloc-notes. Si vous avez besoin de données sur le bloc-notes dans NotesViewController, vous pouvez remonter le graphique d'objet en procédant comme suit:

let notebookName = note.notebook.name

En savoir plus à ce sujet dans mon article de blog: Partage du code modèle

Korey Hinton
la source
10

NewsViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [tbl_View deselectRowAtIndexPath:indexPath animated:YES];
  News *newsObj = [newstitleArr objectAtIndex:indexPath.row];
  NewsDetailViewController *newsDetailView = [[NewsDetailViewController alloc] initWithNibName:@"NewsDetailViewController" bundle:nil];

  newsDetailView.newsHeadlineStr = newsObj.newsHeadline;

  [self.navigationController pushViewController:newsDetailView animated:YES];
}

NewsDetailViewController.h

@interface NewsDetailViewController : UIViewController
@property(nonatomic,retain) NSString *newsHeadlineStr;
@end

NewsDetailViewController.m

@synthesize newsHeadlineStr;
Mohsin Sabasara
la source
10

La délégation est la seule solution pour effectuer de telles opérations lorsque vous utilisez des fichiers .xib, mais toutes les réponses décrites ci-dessus storyboardconcernent les fichiers .xibs dont vous avez besoin pour utiliser la délégation. c'est la seule solution que vous pouvez.

Une autre solution consiste à utiliser le modèle de classe singleton l'initialiser une fois et à l'utiliser dans l'ensemble de votre application.

user2786888
la source
10

si vous souhaitez transmettre des données de ViewControlerOne à ViewControllerTwo, essayez-les.

faites-les dans ViewControlerOne.h

 @property (nonatomic, strong) NSString *str1;

faites-les dans ViewControllerTwo.h

 @property (nonatomic, strong) NSString *str2;

Synthétiser str2 dans ViewControllerTwo.m

@interface ViewControllerTwo ()
@end
@implementation ViewControllerTwo
@synthesize str2;

faites-les dans ViewControlerOne.m

 - (void)viewDidLoad
 {
   [super viewDidLoad];

  // Data or string you wants to pass in ViewControllerTwo..
  self.str1 = @"hello world";

 }

sur les boutons cliquez sur l'événement, procédez comme suit.

-(IBAction)ButtonClicked
{ //Navigation on buttons click event from ViewControlerOne to ViewControlerTwo with transferring data or string..
  ViewControllerTwo *objViewTwo=[self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerTwo"];
  obj.str2=str1;
  [self.navigationController pushViewController: objViewTwo animated:YES];
}

faites-les dans ViewControllerTwo.m

- (void)viewDidLoad
{
 [super viewDidLoad];
  NSLog(@"%@",str2);
}
krushnsinh
la source
10

Vous pouvez enregistrer des données dans Délégué d'application pour y accéder via les contrôleurs de vue de votre application. Tout ce que vous avez à faire est de créer une instance partagée de délégué d'application

AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

Par exemple

si vous déclarez un, NSArray object *arrayXYZvous pouvez y accéder dans n'importe quel contrôleur de vue enappDelegate.arrayXYZ

ak_tyagi
la source
C'est la méthode de choix pour le hackathon
Hai Feng Kao
9

Si vous souhaitez envoyer des données de l'un à l'autre viewController, voici une façon de procéder:

Disons que nous avons viewControllers: ViewController et NewViewController.

dans ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
    IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;
}

@property (nonatomic,retain) IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;

-(IBAction)goToNextScreen:(id)sender;

@end

dans ViewController.m

#import "ViewController.h"

#import "NewViewController.h"

@implementation ViewController
@synthesize mytext1,mytext2,mytext3,mytext4;

-(IBAction)goToNextScreen:(id)sender
{
    NSArray *arr = [NSArray arrayWithObjects:mytext1.text,mytext2.text,mytext3.text,mytext4.text, nil];


    NewViewController *newVc = [[NewViewController alloc] initWithNibName:@"NewViewController" bundle:nil];

    newVc.arrayList = arr;

    [self.navigationController pushViewController:newVc animated:YES];

}

Dans NewViewController.h

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

    NSString *name,*age,*dob,*mobile;

}

@property(nonatomic, retain)NSArray *arrayList;

@end

Dans NewViewController.m

#import "NewViewController.h"

#import "ViewController.h"

@implementation NewViewController
@synthesize arrayList;

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{

    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

    // Return the number of rows in the section.
    return [arrayList count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];      
    }
    // Configure the cell...
    cell.textLabel.text = [arrayList objectAtIndex:indexPath.row];
    return cell;


}

@end

Ainsi, de cette façon, nous pouvons transmettre les données d'un contrôleur de vue à un autre contrôleur de vue ...

Sabs
la source
8

J'aime l'idée des objets Model et des objets Mock basés sur NSProxy pour valider ou supprimer les données si ce que l'utilisateur sélectionne peut être annulé.

Il est facile de transmettre des données car il s'agit d'un seul objet ou de deux objets et si vous avez, disons, le contrôleur UINavigationController, vous pouvez conserver la référence au modèle à l'intérieur et tous les contrôleurs de vue poussés peuvent y accéder directement à partir du contrôleur de navigation.

Ben Sinclair
la source
8

J'ai vu beaucoup de gens compliquer cela en utilisant la didSelectRowAtPathméthode. J'utilise Core Data dans mon exemple.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    //this solution is for using Core Data
    YourCDEntityName * value = (YourCDEntityName *)[[self fetchedResultsController] objectAtIndexPath: indexPath];

    YourSecondViewController * details = [self.storyboard instantiateViewControllerWithIdentifier:@"nameOfYourSecondVC"];//make sure in storyboards you give your second VC an identifier

    //Make sure you declare your value in the second view controller
    details.selectedValue = value;

    //Now that you have said to pass value all you need to do is change views
    [self.navigationController pushViewController: details animated:YES];

}

4 lignes de code à l'intérieur de la méthode et vous avez terminé.

App Dev Guy
la source
6

Il existe de nombreuses réponses à ces questions offrant de nombreuses façons différentes d'effectuer une communication avec le contrôleur de vue qui fonctionnerait effectivement, mais je ne vois nulle part mentionné laquelle est réellement la meilleure à utiliser et laquelle éviter.

En pratique, à mon avis, seules quelques solutions sont recommandées:

  • Pour transmettre des données:
    • remplacer la prepare(for:sender:)méthode UIViewControllerlors de l'utilisation d'un storyboard et de séquences
    • transmettre des données via un initialiseur ou via des propriétés lors de l'exécution des transitions du contrôleur de vue avec le code
  • Pour passer des données en arrière
    • mettre à jour l'état partagé de l'application (que vous pouvez transmettre entre les contrôleurs de vue avec l'une des méthodes ci-dessus)
    • utiliser la délégation
    • utiliser une séquence de déroulement

Solutions que je recommande de NE PAS utiliser:

  • Référencer directement le contrôleur précédent au lieu d'utiliser la délégation
  • Partage de données via un singleton
  • Passer des données via le délégué de l'application
  • Partage de données via les valeurs par défaut de l'utilisateur
  • Transmission de données via des notifications

Ces solutions, bien que fonctionnant à court terme, introduisent trop de dépendances qui brouilleront l'architecture de l'application et créeront plus de problèmes plus tard.

Pour ceux qui sont intéressés, j'ai écrit quelques articles qui abordent ces points de manière plus approfondie et mettent en évidence les différents inconvénients:

Matteo Manferdini
la source