1437 votes

Passage de données entre contrôleurs de vue

Je suis novice en matière d'iOS, d'Objective-C et de tout ce qui touche à l'informatique. MVC paradigme et je suis coincé avec ce qui suit :

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 listés dans une autre vue avec un UITableViewController et j'ai activé les sélections multiples.

Comment transférer les données d'une vue à une autre ? Je maintiendrai les sélections sur le UITableView dans un tableau, mais comment puis-je ensuite renvoyer ces données à la vue précédente du formulaire de saisie de données afin qu'elles soient enregistrées avec les autres données dans Core Data lors de la soumission du formulaire ?

J'ai surfé et vu que certaines personnes déclarent un tableau dans le délégué de l'application. J'ai lu quelque chose sur 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 est la bonne façon de procéder et comment dois-je m'y prendre ?

1716voto

Matt Price Points 10374

Cette question semble être très populaire ici sur Stack Overflow, alors j'ai pensé que je pourrais essayer de donner une meilleure réponse pour aider les personnes qui débutent dans le monde d'iOS comme moi.

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

Transférer les données

Transmettre des données à un contrôleur de vue depuis un autre contrôleur de vue. Vous utiliserez cette méthode si vous souhaitez transmettre un objet/une valeur d'un contrôleur de vue à un autre contrôleur de vue que vous pouvez pousser sur une pile de navigation.

Pour cet exemple, nous aurons ViewControllerA et ViewControllerB

Pour passer un BOOL valeur de ViewControllerA à ViewControllerB nous ferions ce qui suit.

  1. sur ViewControllerB.h créer une propriété pour le BOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. sur ViewControllerA vous devez lui parler de ViewControllerB utilisez donc un

     #import "ViewControllerB.h"

Puis l'endroit où vous voulez charger la vue, par exemple, didSelectRowAtIndex ou un IBAction vous devez définir la propriété dans ViewControllerB avant de le pousser sur la pile de navigation.

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

Cela permettra de régler isSomethingEnabled sur ViewControllerB à BOOL valeur YES .

Transmission des données à l'aide de segments

Si vous utilisez des Storyboards, vous utilisez très probablement des séquences et vous aurez besoin de cette procédure pour transmettre les données. C'est similaire à la procédure ci-dessus mais au lieu de passer 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 BOOL de ViewControllerA à ViewControllerB nous ferions ce qui suit :

  1. sur ViewControllerB.h créer une propriété pour le BOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. sur ViewControllerA vous devez lui parler de ViewControllerB donc utiliser un

     #import "ViewControllerB.h"
  3. Créez la transition entre ViewControllerA à ViewControllerB sur le storyboard et lui donner un identifiant. Dans cet exemple, nous l'appellerons "showDetailSegue"

  4. Ensuite, nous devons ajouter la méthode à ViewControllerA qui est appelé lorsqu'un enchaînement est effectué. Pour cette raison, nous devons détecter quelle segue a été appelée et ensuite faire quelque chose. Dans notre exemple, nous allons vérifier que "showDetailSegue" et si cela est effectué, nous passerons notre BOOL à la valeur 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 pour la remplacer par la suivante

    -(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 permettra de régler isSomethingEnabled sur ViewControllerB à BOOL valeur YES .

Renvoi des données

Pour renvoyer les données de ViewControllerB à ViewControllerA vous devez utiliser Protocoles et délégués ou Blocs le second peut être utilisé comme un mécanisme de couplage souple pour les rappels.

Pour ce faire, nous allons faire ViewControllerA un délégué de ViewControllerB . Cela permet ViewControllerB pour renvoyer un message à ViewControllerA nous permettant de renvoyer des données.

Pour ViewControllerA pour être un délégué de ViewControllerB il doit être conforme à ViewControllerB Nous devons spécifier le protocole de l'entreprise. Ceci indique ViewControllerA les méthodes qu'il doit mettre en œuvre.

  1. Sur ViewControllerB.h en dessous de la #import mais au-dessus @interface vous spécifiez le protocole.

     @class ViewControllerB;
    
     @protocol ViewControllerBDelegate <NSObject>
     - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
     @end
  2. Suivant toujours dans le ViewControllerB.h vous devez mettre en place un delegate et synthétiser en ViewControllerB.m

     @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. Sur ViewControllerB nous appelons un message sur le delegate quand 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 pour ViewControllerB . Maintenant en ViewControllerA.h dire ViewControllerA d'importer ViewControllerB et se conformer à son protocole.

     #import "ViewControllerB.h"
    
     @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
  5. Sur ViewControllerA.m mettre en œuvre 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 viewControllerB à la pile de navigation, nous devons dire à ViewControllerB que ViewControllerA 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é

Centre de notification NSN

C'est un autre moyen de transmettre des données.

// Add an 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];

Transférer des données d'une classe à l'autre (Une classe peut être un contrôleur, un gestionnaire de réseau/session, une sous-classe UIView ou toute autre classe).

Les blocs sont des fonctions anonymes.

Cet exemple transmet les données de Contrôleur B à Contrôleur A

Définir un bloc

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

Ajouter un gestionnaire de bloc (listener)

Où vous avez besoin d'une valeur (par exemple, vous avez besoin de votre réponse API dans ControllerA ou vous avez besoin des données de 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];

Bloc 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

25 votes

Devons-nous également mettre un @class ViewControllerB; au-dessus de la définition du @protocole ? Sans elle, j'obtiens une erreur "Expected type" sur ViewControllerB dans la ligne : - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; au sein de la @protocol déclaration

4 votes

Cela fonctionne très bien. Comme le dit alan-p, n'oubliez pas d'écrire @class ViewControllerB ; au-dessus du protocole, sinon vous recevrez l'erreur "Expected a type".

0 votes

J'ai eu un problème avec la spécification des protocoles. J'avais aussi l'erreur [at]class, mais elle n'acceptait pas non plus la ligne @interface ViewControllerA : UIViewController <ViewControllerBDelegate>. Si vous avez aussi ce problème (pour dire à ViewControllerA de se conformer au protocole), alors cliquez sur le lien ici. J'ai utilisé la réponse de l'utilisateur Anomie. lien

139voto

Caleb Points 72897

Le M de MVC signifie "Model" et dans le paradigme MVC, le rôle des classes de modèles 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 ne sait pas quoi faire avec les données, alors qu'un modèle sait tout sur la façon de travailler avec les données, mais rien sur la façon de les afficher. Les modèles peuvent être compliqués, mais ils ne doivent pas nécessairement l'être. Le modèle de votre application peut être aussi simple qu'un tableau de chaînes de caractères 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, il a 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 ce tableau et peut être responsable du chargement du tableau depuis un fichier. Lorsque l'utilisateur décide d'ajouter une nouvelle ligne au tableau, il appuie sur un bouton et votre contrôleur crée un nouveau dictionnaire (mutable) et l'ajoute au tableau. Afin de remplir la ligne, le contrôleur crée un contrôleur de vue détaillée 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, il n'y a donc rien d'autre à faire.

99voto

borncrazy Points 361

Il existe plusieurs façons de recevoir des données par une classe différente dans iOS. Par exemple .

  1. Initialisation directe après l'attribution d'une autre classe.
  2. Délégation - pour le renvoi des données
  3. Notification - pour diffuser des données à plusieurs classes en même temps
  4. Économiser en NSUserDefaults - pour y accéder ultérieurement
  5. Classes singleton
  6. Les bases de données et autres mécanismes de stockage, comme Fichiers p-list etc.

Mais pour le scénario simple consistant à transmettre une valeur à une classe différente dont l'allocation est effectuée dans la classe courante, la méthode la plus courante et la plus préférable 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 - Contrôleur 1 et contrôleur 2

Supposons que dans la classe Controller1, vous voulez créer l'objet Controller2 et le pousser avec une valeur String passée. Cela peut être fait comme suit :

- (void)pushToController2 {

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

Dans l'implémentation de la classe Controller2 il y aura cette fonction comme :

@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 :

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

Si vous devez transmettre plus de trois paramètres liés à une caractéristique commune, vous pouvez stocker les valeurs dans une classe de modèle et transmettre cet objet de modèle à 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];

Donc en résumé, si vous voulez

  1. définir les variables privées de la deuxième classe initialiser les valeurs en appelant une fonction personnalisée et en passant les valeurs.
  2. setProperties le font en l'initialisant directement à l'aide de la méthode setter.
  3. passer plus de 3-4 valeurs liées les unes aux autres d'une manière ou d'une autre, puis créer une classe modèle et définir des valeurs pour son objet et passer l'objet en utilisant l'un des processus ci-dessus.

86voto

Matt Price Points 10374

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

J'ai fini par utiliser cet exemple (dans le SDK de développement de l'iPhone) :

Partage des données entre les contrôleurs de vue et d'autres objets

Cela a bien fonctionné et m'a permis de faire passer une chaîne et un tableau dans les deux sens entre mes vues.

4 votes

N'utilisez pas les protocoles et les délégués, utilisez simplement le déroulement.

1 votes

@malhal Et si tu n'utilises pas de storyboards ? ?

0 votes

Je déteste les protocoles et les délégués inutiles, moi aussi. @malhal

70voto

Leszek Żarna Points 638

Je trouve la version la plus simple et la plus élégante avec les blocs de passage. Appelons le contrôleur de vue qui attend les données retournées "A" et le contrôleur de vue qui retourne les données "B". Dans cet exemple, nous voulons obtenir 2 valeurs : la première de type 1 et la seconde de type 2.

En supposant que nous utilisons Storyboard, le premier contrôleur définit le bloc de rappel, par exemple pendant 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é callback, BViewController.h :

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

Puis, dans le fichier d'implémentation BViewController.m, une fois que nous avons les valeurs souhaitées à retourner, notre callback doit être appelé :

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

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

0 votes

Pourquoi la valeur ne serait-elle pas un paramètre du bloc de rappel plutôt qu'une propriété distincte ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X