4 votes

Les propriétés IBOutlet ne se mettent pas à jour lors de l'utilisation de la méthode prepareForSegue.

Je rencontre un problème pour passer une valeur à une propriété IBOutlet du destinationViewController, mais cela fonctionne bien avec une propriété ordinaire, voir le code ci-dessous

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"NewsCellToDetail"]) {        
    testViewController *viewController = segue.destinationViewController;
    viewController.titleLabel.text = @"test"; // définir le texte de l'étiquette IBOutlet sur quelque chose
    NSLog(@"%@",viewController.titleLabel.text); // cela affichera nil
    viewController.textTest = @"testing2"; // définir la propriété sur quelque chose
    NSLog(@"%@", viewController.textTest) // cela affichera la chaîne testing2
}

Voici le code du fichier d'en-tête testviewcontroller.h

#import 
@interface NewsDetailViewController : UIViewController
@property (strong, nonatomic) IBOutlet UILabel *titleLabel;
@property (strong, nonatomic) NSString *textTest;
@end

J'ai déjà synthétisé les deux propriétés.

Merci pour l'aide.

15voto

Matt S. Points 732

Je suis un peu en retard pour répondre à cette question, mais j'ai récemment rencontré ce problème, et la raison en aurait été évidente si nous étions toujours en train d'instancier les choses manuellement au lieu de laisser le storyboard le gérer. Il se trouve que c'est la même raison pour laquelle vous ne manipulez jamais la vue lors de l'instanciation manuelle des contrôleurs de vue: la destinationViewController de la segue n'a pas encore appelé loadView: , ce qui dans un storyboard passe par la désérialisation de tous les objets de vue du nib associé.

Une façon extrêmement simple de le voir en action :

  1. Créez deux scènes de ViewController (ViewController1 et ViewController2)
  2. Ajoutez un bouton à ViewController1 et une segue d'action du bouton à ViewController2
  3. Ajoutez une subview à ViewControler2, et un IBOutlet à cette subview
  4. Dans le prepareForSegue: de ViewController1, essayez de référencer cet IBOutlet de subview de ViewController2 - vous constaterez qu'il est nul et son frame/bounds sont null.

C'est parce que la vue de ViewController2 n'a pas encore été ajoutée à la pile de vues, mais le contrôleur a été initialisé. Ainsi, vous ne devriez jamais essayer de manipuler la vue de ViewController2 dans prepareForSegue: ou sinon tout ce que vous ferez sera perdu. Référez-vous au Guide de programmation des contrôleurs de vue d'Apple ici pour le cycle de vie : https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html

La réponse acceptée ici force loadView à s'exécuter dans prepareForSegue: en accédant à la propriété .view de la destination, c'est pourquoi les choses apparaissent dans le désordre, et auront des résultats inconnus / difficiles à reproduire si vous essayez de faire toute manipulation de vue dans viewDidLoad pour prendre en compte les chargements de données, car sans avoir la vue chargée dans la pile de vues, toute référence à la vue parente pour les références de trame sera nulle.

TL;DR; - Si vous avez des données à passer comme dans le cas de l'OP, définissez-les en utilisant des propriétés publiques sur la destination, et ensuite dans le viewDidLoad du contrôleur de destination, chargez ces données dans les sous-vues de la vue.

Edit:

Question similaire ici - IBOutlet is nil inside custom UIView (Using STORYBOARD)

Vous voudrez peut-être également utiliser viewDidLayoutSubviews: pour toute manipulation de sous-vue.

10voto

Steve Wang Points 156

Je viens de rencontrer le même problème récemment. Mais lorsque je le débogue étape par étape, je trouve une raison possible. (Je suis désolé, je suis aussi nouveau en Objective C, donc mon explication suivante peut ne pas être aussi précise et professionnelle ... Mon expérience précédente est principalement dans le développement web.)

Si vous placez un point d'arrêt juste après la ligne où vous appelez

testViewController *viewController = segue.destinationViewController;

lorsque vous compilez et lancez le projet, vous constaterez que la propriété UITextField dans destinationViewController n'est pas allouée et initialisée (la mémoire est à 0x0) au point d'arrêt. Pendant ce temps, la propriété NSString est déjà allouée et initialisée (vous pouvez donc définir sa valeur).

Je pense que probablement UITextfield est une vue enfant, donc il est seulement initialisé lorsque sa vue parente (la vue destination) est initialisée. Mais NSString est une propriété qui n'est pas associée à une vue spécifique, donc elle est allouée et initialisée avec le contrôleur de vue qui la déclare.

Lorsque j'ai fait un test plus approfondi, j'ai trouvé quelque chose de très intéressant : la deuxième vue est chargée pendant l'exécution de - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender. J'ai fait un code de test comme ci-dessous :

Dans le fichier .m du premier contrôleur de vue :

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    NSLog("1. %@, %@",[segue identifier],segue.destinationViewController);

    Scene2Controller *scene2ViewController = [segue destinationViewController];
    [txtScene2 resignFirstResponder];

    NSLog("2. scene2ViewController : %", scene2ViewController);
    NSLog("3. txtScene1 : %; passValue : %", [scene2ViewController txtScene1], scene2ViewController.passValue);
    NSLog("4. View2 : %; passValue : %", [scene2ViewController view], scene2ViewController.passValue);
    NSLog("5. txtScene1 : %; passValue : %", [scene2ViewController txtScene1], scene2ViewController.passValue);

    //txtScene1 est la propriété UITextfield que j'ai déclarée dans le deuxième contrôleur de vue
    //txtScene2 est la propriété UITextfield que j'ai déclarée dans le premier contrôleur de vue
    //passValue est la propriété NSString que j'ai déclarée dans le deuxième contrôleur de vue 

}

Dans le fichier .m du deuxième contrôleur de vue :

- (void)viewDidLoad
{
    NSLog("6. txtScene1 : %; passValue : %", txtScene1,passValue);

    [super viewDidLoad];

}

Remarquez que j'ai ajouté des numéros séquentiels avant les messages NSLog. J'ai constaté que la séquence finale de logs était 1,2,3,6,4,5 plutôt que 1,2,3,4,5,6. Et dans le log 3, le résultat de txtScene1 était nul (non initialisé), mais après le log 4 (la deuxième vue était chargée), dans le log 5, txtScene1 n'était PAS nul et avait été initialisé. Cela suggère que la deuxième vue était chargée pendant l'exécution du segue. Donc je suppose que pendant la transition de segue, la séquence d'initialisation des objets du contrôleur de vue de la deuxième vue serait : contrôleur de vue de la deuxième vue -> propriété NSString (et autres propriétés similaires, comme NSInteger, etc.) -> deuxième vue -> propriété UITextfield (et autres propriétés de sous-vue).

J'ai donc modifié mes codes dans le fichier .m du premier contrôleur de vue comme suit :

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

    Scene2Controller *scene2ViewController = [segue destinationViewController];
    [txtScene2 resignFirstResponder];

    if ([scene2ViewController view]) {
        if ([txtScene2.text isEqualToString:@""]) {
            scene2ViewController.txtScene1.text = "No Value";
        } else {
            scene2ViewController.txtScene1.text = txtScene2.text;
        }
    }

}

Ce code fonctionne ensuite correctement et la valeur est passée directement à la propriété UITextfield dans la deuxième vue.

J'espère que l'explication ci-dessus est claire et utile pour vous.

2voto

GrandSteph Points 223

La méthode viewDidLoad du contrôleur de destination fait exactement ce qu'elle dit. Elle charge tous les composants de la vue. Donc toute tentative de les modifier avant cela sera perdue.

Le meilleur moyen de transmettre des données est de les stocker dans des propriétés qui ne sont pas chargées lors du viewDidLoad mais lors de l'initialisation de l'objet.

Typiquement dans le fichier FirstViewController.m :

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
 if ([[segue identifier] isEqualToString:@"segueName"]) {
     SecondViewController *destinationVC = [segue destinationViewController];
     destinationVC.test = @"Test string";
 } }

Et dans le fichier SecondViewController.m :

-(void)viewDidLoad {
 [super viewDidLoad];
 // Faire toute configuration supplémentaire après le chargement de la vue.
 self.testLabel.text = self.test; 
}

0voto

Prikshet Sharma Points 101

SecondViewController = destinationViewController;

Une autre façon est de déclarer la méthode de délégué dans secondViewController. sur secondViewController:

@protocol SecondViewControllerDelegate 
@optional
- (void) InitilizeSecondViewController:(SecondViewController*) listVC;
@end

Implémenté dans firstViewController, utilisez la propriété de la sous-classe de la vue de secondViewController dans cette méthode. sur firstViewController:

- (void) InitilizeSecondViewController:(SecondViewController*) listVC
{
    listVC.btn0.textLabel.text = @"Aha";
}

Initialisez firstViewController en tant que délégué de secondViewController dans prepareForSegue. sur firstViewController:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    SecondViewController* list = segue.destinationViewController;
    list.delegate = self;
}

appelez cette méthode de délégué après la méthode viewDidLoad dans secondViewController. sur secondViewController:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [delegate InitilizeSecondViewController:self];
}

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