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
I'm trying to update the interface contents of a ContainerView on iOS (UIViewController embedded in a UIView) from the UIViewController that it's being displayed in. However, the ContainerView just won't update its content.
The ContainerView and the ViewController are associated with different classes. I can pass data between the two View Controllers by using a few methods like these:
- (void)displayStringInContainer:(NSString *)string
The string gets successfully passed to the ContainerView from the ViewController, however when I try to display that string in an interface element - nothing happens (even though the code is getting called):
self.buttonName.titleLabel.text = string;
I've even tried calling setNeedsDisplay on the button, but nothing happens. Note that this is happening with all interface items.
Here's how I call the method on the ContainerView from my ViewController:
ContainerViewController *cvc = [[ContainerViewController alloc] init];
[cvc displayStringInContainer:#"Text"];
I've done quite a bit of searching, but haven't found anything (also tried to look on the Apple Dev Site, but it's been down for the past three days :P). Does anyone know how to update the content of a ContainerViewController from another ViewController? Why isn't this working? I've been scratching my head on this for a while now.
Alloc init'ing cvc is not the right way to get your reference -- that's a new instance, not the same instance as the one embedded in your view. You can access that instance in code from the parent controller with self.childViewControllers[0] (assuming you have only one container view). You can also get the reference by implementing prepareForSegue and use segue.destinationController (that will be your embedded controller).
What you seem to be missing in your understanding, is that the controller you get when you use a container view in the storyboard is a child view controller. It's the same as if you had called [self addChildViewController:whatever] in code and then added the child's view as a subview of your view.
I'm just dipping my feet for the first time into iOS development, and one of the first things I've had to do is implement a custom container view controller - lets call it SideBarViewController - that swaps out which of several possible child view controllers it shows, almost exactly like a standard Tab Bar Controller. (It's pretty much a Tab Bar Controller but with a hideable side menu instead of a tab bar.)
As per the instructions in the Apple documentation, I call addChildViewController whenever I add a child ViewController to my container. My code for swapping out the current child view controller being shown by the SideBarViewController looks like this:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers
objectAtIndex:0];
[oldViewController removeFromParentViewController];
[oldViewController.view removeFromSuperview];
newViewController.view.frame = CGRectMake(
0, 0, self.view.frame.size.width, self.view.frame.size.height
);
[self addChildViewController: newViewController];
[self.view addSubview: newViewController.view];
}
Then I started trying to figure out just what addChildViewController does here, and I realised that I have no idea. Besides sticking the new ViewController in the .childViewControllers array, it seems to have no effect on anything. Actions and outlets from the child controller's view to the child controller that I've set on the storyboard still work just fine even if I never call addChildViewController, and I can't imagine what else it could affect.
Indeed, if I rewrite my code to not call addChildViewController, and instead look like this...
- (void)showViewController:(UIViewController *)newViewController {
// Get the current child from a member variable of `SideBarViewController`
UIViewController* oldViewController = currentChildViewController;
[oldViewController.view removeFromSuperview];
newViewController.view.frame = CGRectMake(
0, 0, self.view.frame.size.width, self.view.frame.size.height
);
[self.view addSubview: newViewController.view];
currentChildViewController = newViewController;
}
... then my app still works perfectly, so far as I can tell!
The Apple documentation doesn't shed much light on what addChildViewController does, or why we're supposed to call it. The entire extent of the relevant description of what the method does or why it should be used in its section in the UIViewController Class Reference is, at present:
Adds the given view controller as a child.
...
This method is only intended to be called by an implementation of a custom container view controller. If you override this method, you must call super in your implementation.
There's also this paragraph earlier on the same page:
Your container view controller must associate a child view controller with itself before adding the child’s root view to the view hierarchy. This allows iOS to properly route events to child view controllers and the views those controllers manage. Likewise, after it removes a child’s root view from its view hierarchy, it should disconnect that child view controller from itself. To make or break these associations, your container calls specific methods defined by the base class. These methods are not intended to be called by clients of your container class; they are to be used only by your container’s implementation to provide the expected containment behavior.
Here are the essential methods you might need to call:
addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:
but it doesn't offer any clue as to what the 'events' or 'expected containment behavior' that it's talking about are, or why (or even when) calling these methods is 'essential'.
The examples of custom container view controllers in the "Custom Container View Controllers" section of the Apple documentation all call this method, so I assume that it serves some important purpose beyond just popping the child ViewController onto an array, but I can't figure out what that purpose is. What does this method do, and why should I call it?
I think an example is worth a thousand words.
I was working on a library app and wanted to show a nice notepad view that appears when the user wants to add a note.
After trying some solutions, I ended up inventing my own custom solution to show the notepad. So when I want to show the notepad, I create a new instance of NotepadViewController and add its root view as a subview to the main view. So far so good.
Then I noticed that the notepad image is partially hidden under the keyboard in landscape mode.
So I wanted to change the notepad image and shift it up. And to do so, I wrote the proper code in willAnimateRotationToInterfaceOrientation:duration: method, but when I ran the app nothing happened! And after debugging I noticed that none of UIViewController's rotation methods is actually called in NotepadViewController. Only those methods in the main view controller are being called.
To solve this, I needed to call all the methods from NotepadViewController manually when they're called in the main view controller. This will soon make things complicated and create an extra dependency between unrelated components in the app.
That was in the past, before the concept of child view controllers is introduced. But now, you only need to addChildViewController to the main view controller and everything will just work as expected without any more manual work.
Edit:
There are two categories of events that are forwarded to child view controllers:
1- Appearance Methods:
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
2- Rotation Methods:
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
You can also control what event categories you want to be forwarded automatically by overriding shouldAutomaticallyForwardRotationMethods and shouldAutomaticallyForwardAppearanceMethods.
I was wondering about this question too. I watched Session 102 of the WWDC 2011 videos and Mr. View Controller, Bruce D. Nilo, said this:
viewWillAppear:, viewDidAppear:, etc have nothing to do with addChildViewController:. All that addChildViewController: does is to say "This view controller is a child of that one" and it has nothing to do with view appearance. When they get called is associated with when views move in and out of the window hierarchy.
So it seems that the call to addChildViewController: does very little. The side effects of the call are the important part. They come from the parentViewController and childViewControllers relationships. Here are some of the side effects that I know:
Forwarding appearance methods to child view controllers
Forwarding rotation methods
(Possibly) forwarding memory warnings
Avoiding inconsistent VC hierarchies, especially in transitionFromViewController:toViewController:… where both VCs need to have the same parent
Allowing custom container view controllers to take part in State Preservation and Restoration
Taking part in the responder chain
Hooking up the navigationController, tabBarController, etc properties
-[UIViewController addChildViewController:] only adds the passed in view controller in an array of viewControllers that a viewController (the parent) wants to keep reference of. You should actually add those viewController's views on screen yourself by adding them as a subviews of another view (e.g. the parentViewController's view). There's also a convenience object in Interface Builder to use childrenViewControllers in Storyboards.
Previously, to keep reference of other viewControllers of which you used the views of, you had to keep manual reference of them in #properties. Having a build-in property like childViewControllers and consequently parentViewController is a convenient way to manage such interactions and build composed viewControllers like the UISplitViewController that you find on iPad apps.
Moreover, childrenViewControllers also automatically receive all the system events that the parent receives: -viewWillAppear, -viewWillDisappear, etc. Previously you should have called this methods manually on your "childrenViewControllers".
That's it.
What does addChildViewController actually do?
It is the first step of view containment, a process by which we keep the view hierarchy in sync with the view controller hierarchy, for those cases where we have a subview that has encapsulated its logic in its own view controller (to simplify the parent view controller, to enable a reusable child view with its own logic, etc.).
So, addChildViewController adds the child view controller to an array of childViewControllers, which keeps track of the children, facilitates them getting all the view events, keeps a strong reference to the child for you, etc.
But note, addChildViewController is only the first step. You also have to call didMoveToParentViewController, too:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers objectAtIndex:0];
[oldViewController willMoveToParentViewController:nil]; // tell it you are going to remove the child
[oldViewController.view removeFromSuperview]; // remove view
[oldViewController removeFromParentViewController]; // tell it you have removed child; this calls `didMoveToParentViewController` for you
newViewController.view.frame = self.view.bounds; // use `bounds`, not `frame`
newViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // be explicit regarding resizing mask if setting `frame`
[self addChildViewController:newViewController]; // tell it that you are going to add a child; this calls `willMoveToParentViewController` for you
[self.view addSubview:newViewController.view]; // add the view
[newViewController didMoveToParentViewController:self]; // tell it that you are done
}
As an aside, please note the sequence of calls, in which the order that you call these is important. When adding, for example, you call addChildViewController, addSubview, and didMoveToParentViewController, in that order. Note, as the documentation for didMoveToParentViewController says:
If you are implementing your own container view controller, it must call the didMoveToParentViewController: method of the child view controller after the transition to the new controller is complete or, if there is no transition, immediately after calling the addChildViewController: method.
And, if you are wondering why we don't call willMoveToParentViewController in this case, too, it is because, as the documentation says, addChildViewController does that for you:
When your custom container calls the addChildViewController: method, it automatically calls the willMoveToParentViewController: method of the view controller to be added as a child before adding it.
Likewise, when removing, you call willMoveToParentViewController, removeFromSuperview, and removeFromParentViewController. As the documentation for willMoveToParentViewController says:
If you are implementing your own container view controller, it must call the willMoveToParentViewController: method of the child view controller before calling the removeFromParentViewController method, passing in a parent value of nil.
And, again, if you are wondering why we don't call didMoveToParentViewController when removing the child, that is because, as the documentation says, removeFromParentViewController does that for you:
The removeFromParentViewController method automatically calls the didMoveToParentViewController: method of the child view controller after it removes the child.
FYI, if animating the removal of the subview, put the call to removeFromParentViewController in the animation completion handler.
But if you perform the correct sequence of containment calls, outlined above, then the child will receive all of the appropriate view-related events.
For more information (in particular, why these willMoveToParentViewController and didMoveToParentViewController calls are so important), see WWDC 2011 video Implementing UIViewController Containment. Also see the Implementing a Container View Controller section of the UIViewController documentation.
As a minor observation, make sure that when you are adding the child’s view as a subview, reference the bounds of the parent view controller’s view, not the frame. The frame of the parent’s view is in the coordinate system of its superview. The bounds is in its own coordinate system.
You might not notice the difference when the parent view occupies the full screen, but as soon as you employ this in a scenario where the parent’s view doesn't happen to take up the full screen, you will start to encounter frame misalignment. Always use bounds when setting up coordinates for children. (Or use constraints, which gets you out of that silliness, altogether.)
Perhaps needless to say, if you want to just add the child when the parent is instantiated, one can do view controller containment entirely in storyboards without any of these add/remove and willMove/didMove calls at all. Just use “Container View” and pass whatever data is needed by the child during initialization using prepareForSegue.
For example, if the parent had a property called bar and you wanted to update a property called baz in the child:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[ChildViewController class]]) {
ChildViewController *destination = segue.destinationViewController;
destination.baz = self.bar;
}
}
Now, if you want to programmatically add/remove children, then use as outlined above. But storyboard “Container View” can handle all the view containment calls for simple scenarios with very little code.
In my application I have two view controllers.First Viewcontroller is the rootViewController of the application window. when The Button in First ViewController is clicked I add the view of the second Viewcontroller as subView to the first one's view, there is a Button in the second ViewController's view ,My problem is that the application get crashed when I tap that Button
-(void)theCheckoutViewisExpandedwitPatient:(id)patient
{
SecondViewController *sample=[[SecondViewController alloc]init];
CGRect rect=sample.view.frame;
rect.origin.y=30;
rect.origin.x=305;
[sample.view setFrame:rect];
[self.view addSubview:[sample view]];
}
The issue is that SecondViewController is not assigned to strong variable / property, therefore it is deallocated when the method returns.
Any variable pointing to an object, inside a method (called automatic variable if I remember correctly), will be removed from the memory when the method returns. As a result, object pointed to by that variable will be released. If this object is not retained anywhere else, by for example assigning to a strong property or strong instance variable, it will be deallocated. Now, what you are doing is, you grab second view controller's view and stick it into view hierarchy of the view controller's view where this method is defined. The method returns, variable is popped off the stack, SampleViewController is not retained in any way, so it gets released. Any actions that "new" view performs, that result in a call to its view controller (the second view controller), such as button tap event notification, will end up in a crash, as that view controller is no longer in the memory.
Btw. You are simply not doing it right. Look at View Controller Containment API, if you wanna write custom containers.
Yes problem is here. When you click on button , its try to fire method which is in secondviewcontroller. But before your call, secondviewcontroller will out of scope(get dealloc) after this method call theCheckoutViewisExpandedwitPatient.
We can simply say [button->target not alive]
Does a UIView retain it's associated view controller?
For example, in the following code the view is retained by the parent view. It would be handy if this view also retained its ViewController so that I could go ahead and release the controller in the loadView method.
- (void) loadView {
...
MyViewController* ctrl = [[MyViewController alloc] init];
[self.view addSubview: ctrl.view];
[ctrl release];
}
The alternative, I suppose, is to keep track of the controller as an instance variable and release it when appropriate.
Thanks
No it doesnt. You need a member variable, as you mentioned already.
(A view doesn't even know its own viewController)
You've got things somewhat backward: a view controller retains its view, not the other way around. Typically, one view controller manages the entire hierarchy of views that are on the screen together. While iOS 5 does allow you to use more than one view controller at a time, doing so correctly requires more than just adding one controller's view as a subview of another controller's view. For an easy to digest explanation of the process, read the preview of the View Controllers chapter of Matt Neuberg's book, Programming iOS 5, 2nd Edition. If you add one view controller as the child of another view controller, the parent will retain the child and you won't need to create a separate property for it. But do read the docs before you try it in order to avoid a lot of head scratching.