Reputation: 349
I was under the impression that viewDidLoad will be called AFTER prepareForSegue finishes. This is even how Hegarty teaches his Stanford class (as recently as Feb 2013).
However, for the first time today, I have noticed that viewDidLoad was called BEFORE prepareForSegue was finished. Therefore, the properties that I was setting in prepareForSegue were not available to the destinationViewController within the destinations viewDidLoad method.
This seems contrary to expected behavior.
UPDATE
I just figured out what was going on. In my destinationViewController I had a custom setter that would reload the tableView each time the "model" was updated:
DestinationViewController
- (void)setManagedObjectsArray:(NSArray *)managedObjectsArray
{
_managedObjectsArray = [managedObjectsArray copy];
[self.tableView reloadData];
}
It turns out, since the destinationViewController is a subclass of UITableViewController...calling 'self.tableView' forces the view to load. According to Apple's documentation, calling the view property of a view controller can force the view to load. The view of a UITableViewController is the tableView.
Therefore, in prepareForSegue the following line was forcing the view of the destinationViewController to load:
vc.managedObjectsArray = <custom method that returns an array>;
To fix the problem, I changed the custom setter of the destinationViewController's model to:
- (void)setManagedObjectsArray:(NSArray *)managedObjectsArray
{
_managedObjectsArray = [managedObjectsArray copy];
if ([self isViewLoaded]) {
[self.tableView reloadData];
}
}
This will only reload the tableView if the tableView is on screen. Thus not forcing the view to load during prepareForSegue.
If anyone objects to this process, please share your thoughts. Otherwise, I hope this prevents a long sleepless night for someone.
Upvotes: 29
Views: 12825
Reputation: 21571
In my case setting destination presentationController's delegate in prepareSegue caused viewDidLoad to be called. I moved segue.destination.presentationController?.delegate = self to the end of prepareSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// do stuff ....
segue.destination.presentationController?.delegate = self
}
Upvotes: 0
Reputation: 189
I had a similar problem with my dynamic cell which showed a modal when selected. prepareForSegue
was executed before didSelect:atIndexPath
. What helped me was that in the storyboard I reassigned the segue to start from the controller and not the dynamic cell prototype. I solved the race condition (?) and everything is working perfectly!
Upvotes: 0
Reputation: 13414
Thought I'd add a little explanation on what happened here:
prepareForSegue
is called before the view is displayed
viewDidload
is called the first time the view is accessed (view is lazy loaded).
What probably happened is that you accessed the view
in prepareForSegue
triggering the view loading manually.
Usually the flow is:
preformSegue
prepareForSegue
ViewController
is added to the hierarchyviewDidLoad
.But what probably happened in your case is:
preformSegue
prepareForSegue
prepareForSegue
you access the view
view
is automatically loaded => viewDidLoad
is invokedperformSegue
viewDidLoad
(already called)So yes usually the view
property is not accessed before the ViewController
is added to the hierarchy, but if it is, viewDidLoad
can be triggered earlier.
Upvotes: 3
Reputation: 1258
I had run into similar confusions in the past. The general rules of thumb that I learned are:
Another lesson learned with regards to prepareForSegue is to avoid redundant processing. For example, if you already segue a tableView cell to a VC via storyboard, you could run into similar race condition if you attempt to process BOTH tableView:didSelectRowAtIndexPath and prepareForSegue. One can avoid such by either utilizing manual segue or forgo any processing at didSelectRowAtIndexPath.
Upvotes: 25
Reputation: 11
the simple solution is to place
[self.tableView reloadData];
in viewWillAppear
:
Upvotes: 1
Reputation: 635
Thanks for sharing, helped me figure my problem out.
In my case, the destinationViewController is a UITabBarController and modifying it's viewControllers Array triggered the viewDidLoad:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITabBarController *tabBarController = segue.destinationViewController;
tabBarController.viewControllers = ...
tabBarController.something = something;
}
in the viewDidLoad I needed the something attribute to be set, so I had to move it up:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITabBarController *tabBarController = segue.destinationViewController;
tabBarController.something = something;
tabBarController.viewControllers = ...
}
Upvotes: 2