I have an odd case -- a view controller that creates its own view in loadView and which is then added to an existing view.
Here is the code that creates and adds the VC:
self.doneButtonViewController = [[DoneButtonViewController alloc] init];
[self.view addSubview:self.doneButtonViewController.view];
This code is executed in viewDidLoad of the "parent" VC.
The odd thing is that the viewWillAppear method of the added VC is never invoked (nor is viewDidAppear), but the viewWillDisappear method of the added VC is invoked (at the appropriate time), just as one would expect.
Any clue as to why viewWillAppear is not getting invoked?
The application isn't aware of the subview's view controller if you do this, you need to introduce view controller containment to make the root view controller aware. Doing so will handle any events like this.
Because loadView could be called more than once pre iOS 6, I'd advise creating the view controller within init, and then add the subview within loadView. It should be like this:
- (id)init {
...
self.doneButtonViewController = [[DoneButtonViewController alloc] init];
[self addChildViewController:self.doneButtonViewController];
[self.doneButtonViewController didMoveToParentViewController:self];
...
}
- (void)loadView {
...
[self.view addSubview:self.doneButtonViewController.view];
...
}
See "Implementing a Container View Controller" at http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
As for me, adding child view controller in parent view controller can solve the problem that "viewWillAppear" of the child view controller not get called.
Related
This is puzzling me.
The context
The original tutorial I'm following.
Where the segue is added to the Main View via a custom segue:
- (void) perform {
MainViewController *source = (MainViewController *)self.sourceViewController;
UIViewController *destination = (UIViewController *) self.destinationViewController;
for(UIView *view in source.main.subviews){
[view removeFromSuperview];
}
source.currentViewController = destination;
destination.view.frame = CGRectMake(0, 0, source.main.frame.size.width, source.main.frame.size.height);
[source.main addSubview:destination.view];
}
The TextField is connected as delegate in the child View Controller. All things being equal I get the app crashed without any message.
The workaround
In the Main View Controller, in -(void)prepareForSegue: I've added [segue.destinationViewController setDelegate:self]; in the meantime I've added a property in the child View Controller id<UITextFieldDelegate> delegate and modified the textfield delegate as self.delegate.
This works, but the trouble is that I've to set the delegated methods in Main View Controller which is not quite efficient as I have more View Controllers to add.
The Objective
How do I set each View Controller to be the delegate for itself without crashing?
The immediate cause of your error is that the view controller that your views belong to is being deallocated. The fact that your views are on screen while their view controller is deallocated highlights a fundamental flaw in the approach of taking views off one view controller and adding them to another. View controller containment is the correct way to solve an issue like this.
Changing the currentViewController property to strong will fix the memory management issue you're seeing, but it's just a bandaid. Your currentViewController will still be missing rotation methods, appearance and disappearance methods, layout methods, and so forth. View controller containment ensures these methods get called for the view controller whose views are on screen.
Here is an altered version of your project that illustrates how to use view controller containment. I think that will be a better solution than manually removing and adding subviews of the view controllers themselves. See the Apple docs for more info on custom view controller containers.
At first, let's see crash report. Please, do the following:
1. Add Exception Breakpoint
2. Edit it as in the picture
You should create a custom class for the destinationViewController wich will implement UITextFieldDelegate
#interface DestinationViewController <UITextFieldDelegate>
#end
And from storyboard add the class to UIViewController that has TextField
And make the connections for elements and TextField delegate.
Implement delegate methods.
You will not need the implementation of prepareForSegue: anymore. You will have two different classes with different elements. Only if you need to pass something from source to destination then you use prepareForSegue:
Hope you'll understand
I have a UIViewController that I am loading from inside another view controller and then adding its view to a UIScrollView.
self.statisticsController = [self.storyboard instantiateViewControllerWithIdentifier:#"StatisticsViewController"];
self.statisticsController.match = self.match;
[self.scrollView addSubview:self.statisticsController.view];
I've put breakpoints in the statistics view controller and viewDidLoad is being called but viewWillAppear isn't.
Is it because I'm not pushing it onto the hierarchy or something?
You should add statisticsController as a child view controller of the controller whose view you're adding it to.
self.statisticsController = [self.storyboard instantiateViewControllerWithIdentifier:#"StatisticsViewController"];
self.statisticsController.match = self.match;
[self.scrollView addSubview:self.statisticsController.view];
[self addChildViewController:self.statisticsController];
[self.statisticsController didMoveToParentViewController:self];
I'm not sure this will make viewDidAppear get called, but you can override didMoveToParentViewController: in the child controller, and that will be called, so you can put any code that you would have put in viewDidAppear in there.
I encounter -viewWillAppear: not called problem again. After googling, I came here. I did some tests, and find out that the calling order of -addSubview and -addChildViewController: is important.
Case 1. will trigger -viewWillAppear: of controller, but Case 2, it WON'T call -viewWillAppear:.
Case 1:
controller?.willMoveToParentViewController(self)
// Call addSubview first
self.scrollView.addSubview(controller!.view)
self.addChildViewController(controller!)
controller!.didMoveToParentViewController(self)
Case 2:
controller?.willMoveToParentViewController(self)
// Call adChildViewController first
self.addChildViewController(controller!)
self.scrollView.addSubview(controller!.view)
controller!.didMoveToParentViewController(self)
By default, appearance callbacks are automatically forwarded to children.
It's determined with shouldAutomaticallyForwardAppearanceMethods property. Check value of this propery, if it's NO and if your child viewController should appear right on container's appearance, you should notify child with following methods in container's controller life-cycle implementation:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
for (UIViewController *child in self.childViewControllers) {
[child beginAppearanceTransition:YES animated:animated];
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.child endAppearanceTransition];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
for (UIViewController *child in self.childViewControllers) {
[child beginAppearanceTransition:NO animated:animated];
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.child endAppearanceTransition];
}
Customizing Appearance and Rotation Callback Behavior
Fixed my problem! Hope it would be helpful.
As mentioned in another answer, the parent view controller might not call viewWillAppear etc. when shouldAutomaticallyForwardAppearanceMethods is set to false. UINavigationController and UITabBarController are known to do that. In this case, you need to call beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) on the child view controller with isAppearing set to true when the view appears and vice versa.
You have to place these calls at appropriate places in your code, normally when you add and remove your child view controller.
Don't forget to call endAppearanceTransition on your child view controller when your custom transition has ended, otherwise viewDidAppear and viewDidDisappear are not called.
Per Apple (https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html), the correct order of API calls to add a child view controller is:
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[childVC didMoveToParentViewController:self];
But I still had the problem where viewWillAppear in the child VC was not sporadically getting called. My issue was that there was a race condition that could cause the code above to get executed before viewDidAppear in the container view controller was called. Ensuring that viewDidAppear had already been called (or deferring the addition of the child VC until it was) solved it for me.
The previous answers are correct, but in case it helps someone - if you override loadView in the child view controller, then none of the other UIViewController methods get called.
Took me some time to realize why my code wasn't running properly, until I realized that I had accidentally overridden loadView instead of viewDidLoad.
Check if your parent VC is a UINavigationViewController (or any other container). In this case the shouldAutomaticallyForwardAppearanceMethods is False and the appearance methods are not called.
I can't understand your questions and your description.
My problem was similar to this only.
CustomTabBarController -> CustomUINavigationController -> RootViewcontroller
viewWillAppear of CustomUINavigationController and RootViewController are not getting called unless you switched to another tab and come back.
The solution is call super.viewWillAppear(animated: true)
override func viewWillAppear(_ animated: Bool) {
**super.viewWillAppear(true)**
}
I struggled for more than a day for this small mistake.
View appearance methods also will not get forwarded if your view controller hasn't loaded its view. This could happen if you override loadView in your child view controller, and the view is already added to the view hierarchy.
In that case, you could do
addChild(childVC)
childVC.loadViewIfNeeded()
childVC.didMove(toParent: self)
viewController's view is not loaded just after that viewController is pushed into navigation controller.
This is my code snippet.
- (void)myMethodInClassA {
// window's root view controller is navigation controller
UINavigationController *naviCtrl = (UINavigationController*)[UIApplication sharedApplication].keyWindow.rootViewController;
MyViewController *myVC = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
[naviCtrl pushViewController:myVC animated:NO];
// at this point, myVC's view is NOT loaded
}
When I call myMethodInClassA, myVC's viewDidLoad is called AFTER that method returns. I'd expected that myVC's view is loaded just after navigation controller's pushViewController:animated: is called and before myMethodInClassA returns.
When exactly view controller's view is loaded? Apple's documentation just says it is loaded when it is first accessed. It's a bit ambiguous. why doesn't navigation controller's pushViewController: access view controller's view?
p.s. sorry for initial ambiguous question.
Pushing a view controller (VC) onto a navigation controller's stack makes the VC into a child view controller of the navigation controller (which is a container view controller). Creating such a child-parent relationship is a distinct step which does not cause the child VC's view to be loaded immediately. Rather the container VC loads the view at a later time. I believe there is no explicit specification for what "later" means - usually it will be when the container VC has decided that the time has come to integrate the child VC's view into the container VC's view hierarchy. But basically it simply happens at the discretion of the container VC's implementation.
That being said, anyone can force a VC's view to be loaded by simply accessing the VC's view property. For instance, in your code you could add this line
myVC.view;
which would trigger loadView and then viewDidLoad in MyViewController.
However, in your case if MyViewController needs to react to the event that it has been associated with a container VC, then it would be better to override one (or both?) of the following methods in MyViewController:
- (void) willMoveToParentViewController:(UIViewController*)parent
{
// write your code here
}
- (void) didMoveToParentViewController:(UIViewController*)parent
{
// write your code here
}
You need to be aware, though, that willMoveToParentViewController and didMoveToParentViewController are also invoked when MyViewController is popped from its parent navigation controller's stack. You can detect that this is the case by checking the parent argument for nil.
(Swift 2)
Since this question doesn't have an accepted answer...
What I ended up doing is create a convenience init at the child view controller:
convenience init() {
self.init(nibName: "ChildViewController", bundle: nil)
//initializing the view Controller form specified NIB file
}
and in the parentViewController's viewDidLoad():
let commentsView = CommentsViewController()
self.addChildViewController(commentsView)
self.momentsScrollView.addSubview(commentsView.view)
commentsView.didMoveToParentViewController(self)
As stated above,viewDidLoad gets called once when a view is pushed,you might want to do your stuff in viewWillAppear or viewDidAppear.
Ya if that ViewController will be already pushed in navigationController stack then ViewDidLoad method will not be called again.
First time when you will push that ViewController then viewDidLoad will be called.
So if you need that your some functionality is to be executed every time then implement it in viewWillAppear method because it will be called every-time you push your viewController.
Hope it helps you.
are you pushing the view controller for the first tym?if YES then only viewDidLoad() of the controller will be called and if its already pushed and this is not the first tyn then viewWillAppear () will be called.(or) if you are making a new instance every tym u push it then viewDidLoad() will be called.
I find that I have to call loadViewIfNeeded()
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621446-loadviewifneeded
I am working with Automatic Reference Counting.
I have a custom UIViewController subclass and whenever I call -presentViewController: animated:completion: or remove its view from the superview I would like to NSLog something like "I am dealloced" so I know that the view controller has successfully been removed. I have implemented the -dealloc method in my view controller. However I started a test project where I just had two UIViewController instances (no retain cycles) and -dealloc is not called either when I push the second UIViewController modally or when I remove the superview or when I remove it from the parent view controller. Am I missing something ? In my original project (not the test case) Instruments shows me that those controllers leave a memory footprint that I can't get rid off.
If you want to switch view controllers, and have the one you're switching away from be deallocated, then just switch the root view controller of the window. So, if you're in VC1 and want to go to VC2, then do this in VC1:
VC2 *vc2 = [[VC2 alloc] init]; // or however else is appropriate to get an instance of this class
self.view.window.rootViewController = vc2;
If you haven't created any property to point to vc1, then it will be deallocated after making this switch.
If you want to use a modal presentation or a modal segue (to get the animation when you switch controllers), you can still get the initial controller to be deallocated by switching the root view controller after the presentation from the viewDidAppear method of vc2:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.window.rootViewController = self;
}
To get a print when the View Controller is deallocated you can implement the dealloc method as
- (void) dealloc {
NSLog(#"The instance of MyViewController was deallocated");
}
Then to get a print when the View Controller left the view you can implement
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(#"The instance of MyViewController left the main view")
}
If you use -presentViewController:animated:completion: you are retaining the parentViewController every time you call this method. ModalViewControllers are simply pushed on top of the other ViewController.
ModalViewControllers are only designed for some kind of information / User Input and stuff like that. If you want to dealloc the ParentViewController you have to deal with your own implementation.
dealloc method isn't called when the class is retained (or something in this class is retained) and not reeleased. It is justly for projects with both ARC and without it. So check your code twice.
I would like to use my viewDidLoad function in my tableViewController.
How can I make viewDidLoad run in my controller?
tableViewController = [[TableViewController alloc] init];
UITableView *tableView = [[UITableView alloc] init];
tableViewController.view = tableView;
....
From Apple documentation:
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView method. You usually override this method to perform additional initialization on views that were loaded from nib files.
So you can try to instantiate it from NIB or overwrite the loadView method. Another step from Apple documentation:
If you cannot define your views in a storyboard or a nib file, override the loadView method to manually instantiate a view hierarchy and assign it to the view property.
viewDidLoad will be called when the view is actually loaded, which will happen after you present your view controller, by, e.g.:
adding it to a navigation controller,
adding it to a tab bar controller,
presenting it modally.
This is the missing bit in your code. If you explain how you would like to present your view controller, I may help further. Also, have a look at this: Presenting View Controllers.
(I assume the fact that you tried to override the view property of your table view controller was just an attempt "to make things work" -- but you do not need to do anything about that, the view controller will be correctly set up with a table view inside of it).
tableViewController = [[TableViewController alloc] init];
tableViewController.tableView // This is your newly generated tableview
viewDidLoad will be called after you assign the tableView to another parentview