0

I have been trying to have a better understanding on how to pass data between view controllers and everything is make more sense but there is one thing I would like to understand better.

I came across this thread/tutorial here at StackOverFlow (Passing Data between View Controllers), I tried it and it worked as expected, but the one thing I don't know understand is why we can change a BOOL property located in the second view controller but NOT a Label. In other words if I add a second property and try to change the label in the prepareForSegue: method it doesn't work, why?

In section Passing Data Forward using Segue's I tried adding a second property for a label in the second view controller, right where isSomethingEnabled is, like this...

@property(nonatomic) BOOL *isSomethingEnabled;
@property (weak, nonatomic) IBOutlet UILabel *myLabel;

Than in prepareForSegue: method located in the first view controller I did this..

 -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
            if([segue.identifier isEqualToString:@"showDetailSegue"])
            {
                ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
                controller.isSomethingEnabled = YES;// this works fine
                controller.myLabel.text = @"Hello"; // here, no error but it doesn't update the label
            }   
}

Why you can modify a BOOL property located in a second view controller but not a label? Can someone explain this a little bit?

Thanks a lot

Community
  • 1
  • 1
fs_tigre
  • 10,650
  • 13
  • 73
  • 146

2 Answers2

3

As Grzegorz Krukowski said the UIViewController isn't loaded at that moment. You can set @property values and access them in the viewDidLoad method and setup your Labels accordingly.

Hannes
  • 3,752
  • 2
  • 37
  • 47
  • Got it, I never thought about it, so simple. I created an NSString property in the second view controller and set its value from main view controller in the prepareForSegue method, then in viewDidLoad I set the label to the value of the NSString. Works fine. Thanks a lot. – fs_tigre Jan 31 '14 at 02:39
3

First of all, you should not do this. You should treat another view controller's views as private. The reason for this is the concept of encapsulation. A view controller's views are part of the view's appearance, not it's API. If at a future date you decide to refactor the VC so that it uses the text string in question as the title in a navigation controller, you should be free to do this. If another view controller reaches inside your view controller and manipulates it's view objects directly, you must keep those view objects unchanged forever, or you run the risk of breaking the way the other view controller interacts with it. That's bad, and 6 months from now you won't remember that 3 outside view controllers manipulate your view controller's views, and when you forget and change your views and decide to move a piece of text to a different UI object, your app will stop working correctly.

What you SHOULD do is to add a string property to your ViewControllerB, and set THAT in your prepareForSegue. Then in ViewControllerB's viewWillAppear:animated method, fetch the contents of that string property and install it in your label's text property.

Then the string property becomes part of your view controller's API contract (its public interface.) By adding a public string property to your interface, you guarantee that you will accept string values to that property and that you will do the right thing with them.

Now, as to why it doesn't work. A view controller's view hierarchy is not loaded until it's displayed. At the time of prepareForSegue, the view controller has been initialized, but it's views haven't been loaded yet. All its IBOutlets are nil. (this is another reason why you should not try to manipulate another view controller's views BTW)

Your prepareForSegue method looks like this: (wrong)

 -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
  if([segue.identifier isEqualToString:@"showDetailSegue"])
  {
    ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
    controller.isSomethingEnabled = YES;// this works fine
    controller.myLabel.text = @"Hello"; // here, no error but it doesn't update the label
  }   
}

Instead, your code should look like this:

 -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
  if([segue.identifier isEqualToString:@"showDetailSegue"])
  {
    ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
    controller.isSomethingEnabled = YES;// this works fine
    controller.textToShow = @"Hello"; //This is the change
  }   
}

Then, in your ViewControllerB's viewWillAppear:animated method:

- (void) viewWillAppear: animated;
{
  [super viewWillAppear: animated];
  //install the text from our textToShow property into the label
  self.myLabel.text = self.textToShow; 
}
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • `What you SHOULD do is to add a string property to your ViewControllerB, and set THAT in your prepareForSegue. Then in ViewControllerB's viewWillAppear:animated method, fetch the contents of that string property and install it in your label's text property.` I believe this is what I did, no?... I created an NSString property in the second view controller and set its value from main view controller in the prepareForSegue method, then in viewDidLoad I set the label to the value of the NSString. The difference I see is that I set the string in viewDidLoad instead of viewWillAppear:animated. – fs_tigre Jan 31 '14 at 12:49
  • 1
    @fs_tigre, no. In the code you posted, you are trying to set the text content of controller's contents directly. That's wrong. – Duncan C Jan 31 '14 at 15:16
  • 1
    (I edited my post to show the changes I'm talking about) – Duncan C Jan 31 '14 at 15:23
  • Got it, make sense, thanks a lot for your suggestions and good points. I really appreciate it. – fs_tigre Jan 31 '14 at 15:44