I'm trying to pass an argument to a view controller in containment:
let childVC = ChildViewController()
addChild(childVC)
childVC.view.frame = frame
view.addSubview(childVC.view)
childVC.didMove(toParent: self)
ChildViewController has multiple properties, one of which has to be passed on from the parent view controller.
I've tried a few things, but none worked:
let childVC = ChildViewController(someProperty: someProperty)
or
let childVC = ChildViewController()
childVC.someProperty = someProperty
This line:
let childVC = ChildViewController()
Is almost always wrong. That will create an empty instance of ChildViewController with no views, outlets, or actions set up.
Generally you want to instantiate a view controller from a Storboard or a nibfile.
If you are asking how you install contents in a child view controller's views, the answer is "Don't do that."
If you want your parent view controller to have a child view controller, the easiest way to do that is to put a container view on the parent view controller in IB. Then control-drag from that container view onto the view controller you want to be a child, and when prompted, select "embed segue" as the type of link you want to create.
That will cause the system to install the child view controller in the container view and hook up all the plumbing to make it work
The parent view controller's prepare(for:sender:) method will be called right after the child view controller is instantiated, but before it's views are loaded. You can put code in your prepare(for:sender:) method to pass values to the child view controller (not set it's views directly, but set properties or call methods)
Related
I have a tab bar controller that has two tabs. One is a regular UIViewController and the other is a navigation controller. The navigation controller, I am able to push another view controller on it with a custom inputContainerView with no problems. But when I put a navigationViewController (name from mapbox) on the first tab as a child view, the custom inputContainerView no longer shows up. Even after I remove the child view controller from the first tab.
Adding child to tab 1
...
addChild(navigationViewController)
navigationViewController.view.frame = view.bounds
view.addSubview(navigationViewController.view)
navigationViewController.didMove(toParent: self)
}
func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
navigationViewController.willMove(toParent: nil)
navigationViewController.view.removeFromSuperview()
navigationViewController.removeFromParent()
}
tab 2
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
override var canBecomeFirstResponder: Bool { return true }
The sequence I am trying to achieve is to add a child view controller to the first tab (navigationViewController which is named this from mapbox) click on the second tab and push a ui view controller on with the inputContainerView showing up. It shows up fine before I add the child view on the first tab but disappears after that
this is what the documentation says:
This method creates a parent-child relationship between the current view controller and the object in the childController parameter. This relationship is necessary when embedding the child view controller’s view into the current view controller’s content. If the new child view controller is already the child of a container view controller, it is removed from that container before being added.
Check the Highlighted part.
I am trying to display a child view controller over the top of all elements on screen (including navigation bars), and the only way I've found that works is to add it as a child view controller to my window's rootViewController:
guard let window = UIApplication.shared.keyWindow,
let view = window.rootViewController?.view
else { return }
window.rootViewController?.addChildViewController(attachmentViewController)
view.addSubview(attachmentViewController.view)
attachmentViewController.view.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view)
make.bottom.equalTo(view)
}
attachmentViewController.didMove(toParentViewController: window.rootViewController)
However, this doesn't call the viewDidAppear or viewWillDisappear methods... Why is that? I really need it to.
Instead of doing all that, simply present the view controller (don't push it as suggested).
let destination = SomeViewController.instantiateFromStoryboard(self.storyboard!)
present(destination, animated: true, completion: nil)
Focusing on the "why is that?" of your question.
When you call addChildViewController to a view you're not changing the "stack" of view controllers at all or the state of the host view controller; you're just adding a view controller as a child controller of the main view.
Usually when you work with child view controllers you orchestrate calls like willMove and didMove to trigger the view controller lifecycle behaviour.
In your case, you may be better off with a push or present. Present will give you the capability of overlaying a view controller.
As a note, I have used an approach similar to what you describe for managing sign in/out states adding either a signed in child view controller or a signed out view controller. In which case, when they change I usually call methods like:
// To add the child
addChildViewController(child)
view.addSubview(child.view)
child.didMove(toParentViewController: self)
// To remove the child.
child.willMove(toParentViewController: nil)
child.removeFromParentViewController()
child.view.removeFromSuperview()
I see the description of this method in Apple says
func addChildViewController(_ childController: UIViewController)
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.
I see, so many examples that people use addChildViewController everywhere without containerViewController.
For example: I did not use containerView. I added like in the below? İt is correct?
// Create child VC
let childVC = UIViewController()
// Set child VC
self.addChildViewController(childVC)
// Add child VC's view to parent
self.view.addSubview(childVC.view)
// Register child VC
childVC.didMove(toParentViewController: self)
// Setup constraints for layout
childVC.view.translatesAutoresizingMaskIntoConstraints = false
childVC.view.topAnchor.constraint(equalTo: heroView.bottomAnchor).isActive = true
childVC.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
childVC.view.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
childVC.view.heightAnchor.constraint(equalToConstant: height).isActive = true
Like the documentation says, that method is intended to be used by view controllers that can contain another view controller. An example would be the navigation and tab bar controllers.
If you implemented a custom controller that, for example, put one controller on the top half on the screen and one on the bottom half, when you set the bottomHalfViewController property, you would call the addChildViewController method to let your controller know that it should handle that view controller as it's child.
This means that it will forward all view lifecycle calls like viewWillAppear:
I have a container view with a navigation bar which changes buttons and actions dependant upon which view controller is showing.
When the last view controller is shown I need to be able to go back to the previous screen and unwind the segue.
I've tried unwinding the segue and this doesn't work. I've also tried dismissing the view controller and this doesn't work also.
I'm trying to do this outside of the container view via the parent controller where the navigation bar is located.
My controller inside the parent view controller:
let processController = storyBoard.instantiateViewController(withIdentifier: "ProcessController") as! ProcessController
I also have the #IBAction:
#IBAction func backToCard(unwindSegue:UIStoryboardSegue) {}
This is also named inside the storyboard as backToCard
When inside the parent controller I've also tried the following:
self.performSegue(withIdentifier: "backToCard", sender: self)
processController.view.removeFromSuperview()
processController.dismiss(animated: true, completion: nil)
It seems like the processController isn't talking to the view inside the container view.
I could use some help on this.
Thanks
I have a Container View that contains a view. I want to change this view to another, calling a method of the parent view when the user interacts with an object of this child view.
How can I do this?
let say the container is a class named 'ContainerView' and it has a function called 'changeSubView'
from the child view you can call it using:
let containerView = self.superview as! ContainerView
containerView.changeSubView()