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.
Related
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 have a split view controller and the main view has a "Note" button on a navigation bar which should push a view(Note Gallery ViewController) that contains a list of saved notes onto Detail View. In the following screenshot, "Categories" is the Master View and "List of Events" is the Detail View. As you can see, there is a segue between the "List of Events" and "Note Gallery".
What I would like to do is pushing the "Note Gallery" as a Detail view when "Note" button on the Main View is tapped. So far, I have tried using delegate/protocal approach where protocol is defined inside the Master View and it is implemented inside the Detail View. If I call self.performSegue to push "Note Gallery" view, "Note Gallery" view does not show navigation bar (Even if I force it to show with setNavigationBarHidden with false value). If I call performSegue from the navigation controller (self.navigationController?.performSegue), I get identifier not found exception. I don't want to move Note button to the Detail view since I want to make Note button accessible from the Main View. How can I resolve this issu, and here's an excerpt of my code :
/* Master View */
// Define delegate for updating children views
protocol ChildViewDelegate: class {
func pushInNoteView()
}
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {
weak var delegate: ChildViewDelegate?
// Handles tapping on Note button
#IBAction func noteTapped(_ sender: UIBarButtonItem) {
// Push Note View onto any active detailed view
self.delegate?.pushInNoteView()
}
}
/* Detail View */
extension CategoryDetailTableViewController: ChildViewDelegate {
func pushInNoteView() {
// This approach does not show navigation bar
self.performSegue(withIdentifier: "showNoteView", sender: self)
// This approach gives identifier not found exception
self.navigationController?.performSegue(withIdentifier: "showNoteView", sender: self)
}
}
I assume the problem is that you did not give identifier to your segue in main.storyboard.
Here is how to do it.
In your main.storyboard select your segue as shown in image and assign it an identifier in identity inspector as shown in top right side of the image.
Now create navigation controller and make "root view controller" relationship with your Master View. Then use performSegue(withIdentifier: "segue", sender: nil) to move to next view controller.
Hope that I've solved your problem!
Here are the mistakes I have made :
The reason why I'm getting identifier not defined error is because the segue is not connected between the navigation controller and the "Note Gallery" view. It's connected between the "Note Gallery" view and the "Category Detail" view which in turn connected to the navigation controller. Therefore, the identifier is not defined for the navigation controller.
The reason why navigation bar is hidden is because I'm not pushing "Note Gallery" view onto the navigation controller. Instead, I was just calling prepare segue.
Solution :
It's fairly simple. I just push the "Note Gallery" view on the Navigation Controller just like this :
if let noteVC = storyboard?.instantiateViewController(withIdentifier: "NoteGalleryView") {
self.navigationController?.pushViewController(noteVC, animated: true)
}
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 am currently implementing the XLPagerTabStrip (https://github.com/xmartlabs/XLPagerTabStrip) which effectively creates a tab bar at the top of the view controller. I want to be able to segue to a new view controller from one of the tabbed controllers and be able to use the navigation bar to move backwards (or a custom version of the navigation bar if this isn't possible).
XLPagerTabStrip provides the moveToViewController and moveToViewControllerAtIndex functions to navigate between child view controllers, but this method doesn't allow use of a navigation bar to go backwards.
Conceptually XLPagerTabStrip is a collection of view controllers declared and initialized during the XLPagerTabStrip model creation.
It has virtually no sense to use a UINavigationController if you already have all the viewcontrollers available.
You can create a global var previousIndex to store the previous viewController index and allow users to go back by using canonical methods:
func moveToViewControllerAtIndex(index: Int)
func moveToViewControllerAtIndex(index: Int, animated: Bool)
func moveToViewController(viewController: UIViewController)
func moveToViewController(viewController: UIViewController, animated: Bool)
About a new viewController, suppose you have 4 viewControllers that built your container (XLPagerTabStrip) named for example z1, z2, z3 e z4.
You can embed to z4 a UINavigationController (so it have the z4 controller as rootViewController) and start to push or pop your external views. When you want to return to your z4 you can do popToRootViewControllerAnimated to your UINavigationController
When you are go back to z4 , here you can handle your global var previousIndex to moving inside XLPagerTabStrip.
I'm not familiar with XLPagerTabStrip, but I had a similar problem recently and the solution was to use an unwind segue to go back to the previous view controller. It's pretty trivial to implement so probably worth a try.
To navigate back to your previous view tab controller, you had initially navigated from;
Embed your new view controller, from which you wish to navigate
away from in a navigation bar
Connect it's Navigation Bar Button to the Parent view containing the
tab bar by dragging a segue between the 2 views
Create a global variable in App delegate to store current index
which you will use in the Parent view to determine what tab view
controller to be shown
var previousIndex: Int = 0 //0 being a random tab index I have chosen
In your new view controller's (the one you wish to segue from)
viewdidload function, create an instance of your global variable as
shown below and assign a value to represent a representative index
of the child tab bar view controller which houses it.
//Global variable instance to set tab index on segue
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.previousIndex = 2
You can write this for as many child-tab connected views as you wish, remembering to set the appropriate child-tab index you wish to segue back to
Now, create a class property to reference your global variable and a function in your Parent view as shown below
let appDelegatefetch = UIApplication.shared.delegate as! AppDelegate
The function
func moveToViewControllerAtIndex(){
if (appDelegatefetch.previousIndex == 1){
self.moveToViewControllerAtIndex((self.appDelegatefetch.previousIndex), animated: false)
} else if (appDelegatefetch.previousIndex == 2){
self.moveToViewControllerAtIndex((self.appDelegatefetch.previousIndex), animated: false)
}
}
You may now call this function in the Parent View Controller's viewDidLoad, as shown below.
moveToViewControllerAtIndex()
Run your project and that's it.
I have a view controller which has embedded a navigation controller.
From this view controller I am being able to change the title of the navigation bar with navigationItem.title = "title".
Furthermore I have another tableview controller and I want to update the tile of the navigation controller also from here, this tableview controller acts as a Slide Out Menu for the first view controller.
Navigation controller and tableview controller are not connected directly but I use a library called SWRevealViewController to create and connect the slide out menu with first view controller.
I have tried these codes:ViewController().navigationItem.title = "secondTitle"
I have also tried to put the process of changing the title in first controller in function and create an instance like :ViewController().updateNavTitle(), also tried to crete a segues but without any result.
Create a String variable in your first class and access this property in your Slide Out controller and and update it. Now in your first controller's in viewWillAppear method update the title like below.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if self.updatedTitle != nil {
self.navigationItem.title = self.updatedTitle//Create this String variable.
}
}