How to wait for a UIViewController to be completely initialized? - ios

I have a custom segue type (overriding init and perform methods of UIStoryboardSegue) and in init method I instantiate the destination view controller(VC). In prepareForSegue method of source VC I call a method of the destination VC that tries to reload the tableView of the destination VC. The problem is that the table view is not always initialized and I SOMETIMES get a nil de-reference error when I call the reloaddata of the tableview.
The question is that how can I wait till the VC is fully initialized and do not get this error?
I am using swift and would appreciate if you write any sample code for the answer in swift.

just make a call on the viewController's view to force its load.
[viewController view]; //will force a loadView if necessary
///then do what you're trying to do..

I think that the best approach in this case is to add a flag property in the destination VC, something like:
var forceReload: Bool
that you set from prepareForSegue in the source VC. This way, you can choose where to actually perform the data reload from the destination VC (for example, in viewDidLoad or viewWillAppear) by simply checking the value of that flag - of course if the flag is true, don't remember to reset it.
If you also need to pass data from the source to the destination VC, use one or more properties declared in the destination and set from the source.

Related

Understanding interaction between Swift files - how do I call from one to the other

I have my ViewController.swift which is the first to run when the app loads. I then segue to another SecondViewController.swift file.
The ViewController.swift instance is still in existence, correct?
So now, from SecondViewController.swift, I call ViewController().someFunction()
The ViewController's "viewDidLoad" does not get called, only the function I specifically called to.
Now: if I instantiate an Int variable in ViewController.swift "var testNum = 2" and in the ViewController's viewDidLoad I change its value to 14. Then I segue to SecondViewController. Then I call to ViewController().someFunction() and I print the value of testNum in that function, I get the instantiated value of 2 instead of the value that ViewController had which was 14, which leads me to believe that I've created a new copy of ViewController by calling a function in this way.
Is that right? Is that what I've done? And if so, is there a way to call a function in the existing ViewController instead of creating a new ViewController from within SecondViewController.swift?
THANKS!
I have my ViewController.swift which is the first to run when the app loads. I then segue to another SecondViewController.swift file.
Nitpicking: You don't segue between 'files', you segue between instances of classes defined in those files.
The ViewController.swift instance is still in existence, correct?
Yes. Well, the original instance of the class defined in that file, see above.
So now, from SecondViewController.swift, I call ViewController().someFunction()
The ViewController's "viewDidLoad" does not get called, only the function I specifically called to.
With ViewController() you create a new instance of the ViewController class and then directly invoke that method on it. Which doesn't make a lot of sense.
Now: if I instantiate an Int variable in ViewController.swift "var testNum = 2" and in the ViewController's viewDidLoad I change its value to 14. Then I segue to SecondViewController. Then I call to ViewController().someFunction() and I print the value of testNum in that function, I get the instantiated value of 2
When you call ViewController() you create a new instance of the class defined in the ViewController.swift. Quite (very much) likely this is not what you want to do.
It appears you rather want to hang on to the original instance of that VC. So you need to transfer that when doing the segue.
I'm sure you've read the View Controller Programming Guide for iOS, right? ;-) This is a relevant section:
The prepareForSegue:sender: method of the source view controller lets you pass data from the source view controller to the destination view controller. The UIStoryboardSegue object passed to the method contains a reference to the destination view controller along with other segue-related information.
Usually you wouldn't want to preserve a reference to the original view controller, but rather transfer relevant data from that into the VC you segue to (to decouple them).
If you're going to be storing values, I'd recommend creating a model class.
https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
This will keep the data independent of the controllers because, to answer your "The ViewController.swift instance is still in existence, correct?" question, it may or may not be in existence depending on memory. That's why creating a model is desirable because then you're not worried about it.
If you'd like examples, there are many online if you google MVC iOS. Hope that helps.

Code in viewDidLoad runs every time it is called

Hi all I am doing a course in Udemy, and the code calls for placing code in the viewDidLoad function as shown below:
override func viewDidLoad() {
super.viewDidLoad()
placesArray.append(["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"])
}
The array append should only run once, however, when I segue to another viewController and come back, it runs the code to append again. So I now have an array with 2 rows, both of which are Taj Mahal.
I thought that the viewDidLoad function only runs code once?
Is there a way around this?
Thanks.
Addendum:
I am using Swift, so I don't see any alloc and init while creating and launching the viewController. And weird as it sounds, the video tutorial has it working in the viewDidLoad and the trainer is using the storyboard to segue from the initial table view controller to a map view on a view controller and just has a back button on the map view that segue's back to the table view controller via the storyboard as well. - Could be because I have the latest version of the Swift language and the trainer was using an earlier version, (cause I noticed some slight differences in coding earlier) but you never know. Either way whenever he touches the back button it does not run the append code anymore.
I am trying to get in contact with the trainer as some of the suggestions here, though they are good don't seem to work.
I will put the solution in here once I get in contact with the trainer.
The viewDidLoad method is called when your view controller's view finishes loading. The view will load when a view controller's view property is nil and something attempts to access it.
UIViewController *myVC = [[UIViewController alloc] init];
UIView *aView = myVC.view; // this loads myVC's view; viewDidLoad is called when it completes loading.
If the view has unloaded (usually due to memory limitations), it will be called when the view property is next accessed.
Manipulation of data sets should generally not be done within view methods. Consider moving this to the init of the view controller (or to a different "datasource" class).
I suppose you are trying to do data initialisation in viewDidLoad. If there is no other operation on placesArray before viewDidLoad, then instead of append, what about setting the placesArray directly:
placesArray = ["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"]
Then even if your view is unloaded for some reasons. Taj Mahal will still be added once only.
viewDidLoad is called whenever the view controller's view property is set. When does this happen? It depends on how the view controller is contained:
UINavigationController
- View Controller views are loaded as they are added to the navigation stack and "unloaded" (although the viewDidUnload method is deprecated) as they are removed.
UITabBarController
- View Controller views are loaded as they are added to the tab bar regardless of whether they are on screen or not. They stay loaded as you change from tab to tab.
Depending on your needs and use case, you can create your own view controller container that does what you need. Checkout the Apple docs on the proper way to do this:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html

Pass values to previous view controller in a navigationviewcontroller stack

In UINavigationViewController, if I wanna pass values from one controller to next, just call - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender , but what should I do if I wanna pass values from one viewController to previous viewController
I remember running across this same issue a few projects back. I can't find the my code to answer this question, but I did find a few tutorials.
http://prateekvjoshi.com/2014/02/16/ios-app-passing-data-between-view-controllers/
http://www.infragistics.com/community/blogs/torrey-betts/archive/2014/05/29/passing-data-between-view-controllers-ios-obj-c.aspx
and hence the concept of delegate came forth from segues.
Basically Segues are transition from one view to another but the child view is over the parent view, (inside a stack) so the parent view is still loaded.
So if u put segues everywhere and pass values between them then objects will keep on be creating and stored inside a stack and thus the cycle carries on.
So delegates was introduced.
Delegate is a method by which a child view controller(the later one) sends information using the inbuild delegate methods or self created protocol methods to the Previous view controller(the first one).
Here the one sending the information(later view) declares a delegate object, and a delegate method.
Which is then implemented by the recieving class(first view). So even after the later view is popped from the stack, the information is sent back to the root view by the delegate method.
Go through the documentation, its given in a more appropriate way
Hope this helps
Set previous view controller as delegate of current view controller and pass any values you want. This is standard approach.

segue not going to destination controller

In my app I have a manually triggered push segue called "details". It is wired from the source controller to the destination controller and it's identifier is set. In my code I call
[self performSegueWithIdentifier:#"details" sender:sender];
I can see the prepareForSegue function firing and have verified that the destination controller is of the proper type. I pass the destination controller a few bits of data it needs to display correctly. Oddly enough NOTHING happens. The app does not go to the destination controller.
Here are some things to look out for if you are having an issue with performSegueWithIdentifier while using a storyboard
You have correctly hooked up your transitions in your Storyboard
Your Segue Identifier matches the one on your Storyboard
Your ViewController's class has not become deallocated
(This can sometimes occur accidentally by calling from another class)
Try setting sender to "self" rather than sender
Also, if you are planning on passing data between ViewControllers it is suggested that you use the prepareForSegue method, allowing you to pass values before segue'ing.

iOS >> prepareForSegue >> IBOutlet Update Doesn't Work?

I'm trying to update a Label in the 2nd VC from the 1st VC within the prepareForSegue method.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
MYSecondViewController* secondVC = (MYSecondViewController*)segue.destinationViewController;
secondVC.titleLabel.text = #"First VC Says: You Are Second!!"; //This doesn't work
secondVC.dataPassString = #"First VC Says: You Are Second!!"; //This works + secondVC viewDidLoad
}
If I update the Label directly, it doesn't work.
If I update a String Property and then assign it to the Label in the Second VC viewDidLoad, it does work.
Does it mean that upon prepareForSegue call the second VC viewDidLoad method was not called yet?
Was some init method called (so the NSString object could pass)? If yes, which one?
Is there a way to update IBOutlets in the 2nd VC from the 1st VC?
The short answer is: Don't do that.
You should treat another view controller's views as private and never try to manipulate them. It breaks the OOD principle of encapsulation.
What you want to do is to add (string or other type) properties to your destination view controller, and set THOSE in prepareForSegue. Then in your destination view controller's viewWillAppear method, copy those property values into the view controllers' views.
In your case, the datePassString property is exactly what you want.
That way, if you change the structure of your second view controller down the road, and decide to display the information to a different view, you don't break the link between the 2 view controllers. Your destination view controller can still fetch the data from it's source, and do something different with it.
P.S. as the other poster said, the reason setting secondVC.titleLabel.text fails is that in prepareForSegue, the destination view controller's views haven't been loaded yet.
If you add in this line in your "prepareForSegue" method:
if(!secondVC.titleLabel)
NSLog(#"titleLabel is null and it likely hasn't been loaded yet")
You'll see that the view hasn't been loaded until it's time for it to appear (which happens after prepareForSegue). That's why the datePassString property you're using is working while the IBOutlets are null until the view is loaded.

Resources