3

I have a Storyboard and instantiate a view controller from code like this:

NSLog(@"1");
MyController *vc = (MyController *)[[UIStoryboard storyBoardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"MyController"];
NSLog(@"4");

The implementation of the MyController class includes the following:

- (void)viewDidLoad {
    NSLog(@"2");
    [super viewDidLoad];
    [NSThread sleepForTimeInterval:2];
    NSLog(@"3");
}

I expect to see the following output when I run my code:

1
2
3
4

But it seems that -viewDidLoad is not even called until I access MyController's view:

1
4

If I modify the first like this:

NSLog(@"1");
MyController *vc = (MyController *)[[UIStoryboard storyBoardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"MyController"];
[vc view]; // Just accessing
NSLog(@"4");

Then it the output will be:

1
4
2
3

How can I be sure that my properties are initialized (-viewDidLoad ran) and I can give parameters to my controller? What is the recommended way to do this?

gklka
  • 2,459
  • 1
  • 26
  • 53
  • It seems that instantiating the new VC is run on a background thread (which is good). What's wrong with your accessing tweak? Doesn't that confirm that the code was loaded? – Tim Nov 09 '15 at 23:27
  • I want to set value of the view controller's outlets, which are accessible only after `-viewDidLoad`. – gklka Nov 10 '15 at 07:05
  • Is there a reason you're not using segues? That's almost all I ever use to present VCs from storyboard. – Tim Nov 10 '15 at 14:04
  • This particular controller is being opened from a map callout view. You cannot do it using direct segues (like from button to controller), only indirect (from controller to controller). And the later one has the same problem (see this: http://stackoverflow.com/questions/32639850/pass-variable-to-view-controller-before-viewdidload) – gklka Nov 10 '15 at 15:06
  • Guess I still don't understand what you're setting up in the new VC that can't be set up via a segue. I would set a variable via prepareForSegue, then do my setup of different visuals (buttons hidden/shown) in viewWillAppear, rather than in viewDidLoad. – Tim Nov 10 '15 at 16:06
  • The particular case is the following: the user taps some point on the map, which opens a direction planning controller with "from" and "to" fields. I want to fill in the "from" textfield with the address what the user tapped, but I can't, since the outlet is not set yet. – gklka Nov 10 '15 at 16:53
  • 1
    If you make a class variable like `var fromAddress : String?` and pass to that via prepareForSegue, why wouldn't it work? Then in viewWillAppear, set textfield.text to fromAddress. – Tim Nov 10 '15 at 18:21
  • @Tim it would work. I just wanted to avoid introducing these unnecessary properties. – gklka Nov 10 '15 at 22:16

2 Answers2

1

This is because viewDidLoad called only after you present view controller modally or push to navigation controller, or display it somehow. So if you want to receive 1,2,3,4, you need something like this:

NSLog(@"1");
MyController *vc = (MyController *)[[UIStoryboard storyBoardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"MyController"];
[self presentViewController:vc animated:YES completion:^{
    NSLog(@"4");
}];
Vitalii Gozhenko
  • 9,220
  • 2
  • 48
  • 66
1

You should write your VC such that it doesn't matter if the view is loaded before or after properties are set.

If you have a property like this:

@property (nonatomic, copy) NSString *superAwesomeLabelText;

And you need to set the text of your super-awesome label, have the following:

In viewDidLoad

self.superAwesomeLabel.text = (self.superAwesomeLabelText) ?: @"Default lame text";

The setter for superAwesomeLabelText:

- (void)setSuperAwesomeLabelText:(NSString*)superAwesomeLabelText {
    _superAwesomeLabelText = [superAwesomeLabelText copy];

   self.superAwesomeLabel.text = superAwesomeLabelText;
}

Now it doesn't matter whether the view is loaded before or after the property is set.

Avi
  • 7,469
  • 2
  • 21
  • 22
  • 1
    Related point: One of the most important lessons I learned, way back in the day, with regards to OOP, is to avoid property dependencies. An object should be usable without setting any properties and it should never matter in which order properties are set and/or methods are called. Now, that's an ideal, and we often can't do that in practical programming. But never lose sight of the ideal! – Avi Nov 10 '15 at 08:02
  • But how can you achieve this if you are using a controller for two, almost identical purposes, and you don't have an init method to use? – gklka Nov 10 '15 at 08:56
  • How can you not have an init method to use? And achieve what, exactly? – Avi Nov 10 '15 at 08:57
  • If I define my UI in the Storyboard, I can instantiate it only using `-instantiateViewControllerWithIdentifier:`. This method gives me an initialized instance, I can't reinit it. – gklka Nov 10 '15 at 09:03
  • 1) You can use `initWithNibNamed:...` or `initWithCoder:...`, one of which is called when you instantiate from a storyboard (I don't remember which). 2) The original post referred to `viewDidLoad`, which makes sense, as that's the earliest point you know you have a view to customize. – Avi Nov 10 '15 at 09:20
  • Can you help me how can I instantiate a view controller from Storyboard (not separate xib files) using the methods you mentioned? – gklka Nov 10 '15 at 10:31