IBOutlet properties are not updated when using the prepareForSegue method

I am having the problem of passing a value for the IBOutlet property for destinationViewController, but it works fine on a regular property, see code below

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"NewsCellToDetail"]) { testViewController *viewController = segue.destinationViewController; viewController.titleLabel.text = @"test"; // set the IBOutlet label text to something NSLog(@"%@",viewController.titleLabel.text); // this will output to nil viewController.textTest = @"testing2"; // set the property to something NSLog(@"%@", viewController.textTest) // this will output the string testing2 } 

This is the code for the testviewcontroller.h header file

 #import <UIKit/UIKit.h> @interface NewsDetailViewController : UIViewController @property (strong, nonatomic) IBOutlet UILabel *titleLabel; @property (strong, nonatomic) NSString *textTest; @end 

I am already synthesizing both properties.

Thanks for the help.

+4
source share
4 answers

I had the same issue recently. But when I debug it step by step, I find a possible reason. (Sorry, I'm new to Objective-C too, so my next explanation may not be so accurate and professional ... My previous experience is mostly web development.)

If you set a breakpoint immediately after the line you call

testViewController *viewController = segue.destinationViewController;

when you create and run the project, you will find that the UITextField property in the destinationViewController is not assigned or triggered (memory 0x0) at the breakpoint. Meanwhile, the NSString property is already allocated and initialized (so you can set its value).

I think UITextfield is a child view, so it is only triggered when its parent view (destination view) is triggered. But NSString is a property that is not associated with any view, so it is assigned and initiated using the view controller that declares it.

When I do another test of this, I find it very interesting: the second view is loaded during the runs - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender . I am making test code as below:

In the first view of the controller .m file:

 - (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 is the UITextfield property I declared in the second view controller //txtScene2 is the UITextfield property I declared in the first view controller //passValue is the NSString property I declared in the second view controller } 

In the second view, the .m file is:

 - (void)viewDidLoad { NSLog(@"6. txtScene1: %@; passValue: %@", txtScene1,passValue); [super viewDidLoad]; } 

It is noted that I add sequence numbers before NSLog messages. I found that the final sequence of journal results was 1,2,3,6,4,5, not 1,2,3,4,5,6. And in log 3, the result of txtScene1 was null (not initiated), but after log 4 (the second view was loaded), in log 5 txtScene1 was not null and was triggered. This showed that the second view was loaded during the session. Therefore, I assume that during the segue transition, the sequence of initialization of the objects of the second view of the controller will be: the second view controller → the NSString property (and other similar properties, such as NSInteger, etc.) → the second view → the UITextfield property (and other subview properties) .

So, I changed my codes in the .m file of the first file, as shown below:

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

This code works fine, and the value is passed directly to the UITextfield property in the second view.

The hope of the above explanation is clear and helpful to you.

+10
source

I come to this answer a little late, but I recently ran into this problem, and the reason for this would be obvious if we were still creating things manually, rather than letting the storyboard handle it. This happens, for the same reason that you never control the view when manually creating view controllers: segue destinationViewController did not call loadView: still, which in the storyboard passes through deserialization of all view objects from the associated NIBs.

A very easy way to see this in action:

  • Create two ViewController scenes (ViewController1 and ViewController2)
  • Add a button to ViewController1 and perform an action from the button to ViewController2
  • Add a view to ViewControler2 and an IBOutlet to this view
  • In prepareForSegue: from ViewController1, try to reference this viewpoint of ViewController2 - you will find its zero, and its borders / borders are zero.

This is because the ViewController2 view has not yet been added to view the stack, but the controller has been initialized. That way, you should never try to manipulate the ViewController2 view in the prepareForSegue file: or else everything you do will be lost. Lifecycle Apple ViewController Programming Guide Link: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html

The answer accepted here causes loadView to run in prepareForSegue: by accessing the destination .view property, so things fail, and it will have unknown / difficult to reproduce the results if you try to manipulate the view in viewDidLoad to account for any data load, because without a view, loaded into viewStack, any links to the parent view for frame references will be zero.

TL DR; - If you have data that needs to be transferred, as in the case of the OP, set it using the public properties in the destination, and then load this data into the view subzones in the viewDidLoad of the destination controller.

Edit:

A similar question here - IBOutlet - is it null inside a custom UIView (using STORYBOARD)

You can also use viewDidLayoutSubviews: for any manipulation of subview.

+14
source

The viewDidLoad method from the destination controller does exactly what it says. It loads all the components of the view. Therefore, any attempts to change them before this will be lost.

The best way to pass data is to store it in properties that are not loaded during viewDidLoad, but when the object is initialized.

Usually in FirstViewController.m:

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

And in the file SecondViewController.m

  -(void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.testLabel.text = self.test; } 
+2
source

secondViewController = destinationViewController;

Another way is to declare a delegate method on secondViewController. on secondViewController:

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

Implemented in firstViewController, this method uses a subclass property of the form secondViewController. on firstViewController:

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

Initialize firstViewController as a delegate of the secondViewController in prepareForSegue. on firstViewController:

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

call this delegate method after the viewDidLoad method in the second controller. on secondViewController:

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

Source: https://habr.com/ru/post/1379946/


All Articles