I have read about similar problems where the problem was caused by having multiple navigation controllers, but I only have one. This is my navigation flow.
VC = UIViewController, NC = UINavigationController
VC1 -modal-> NC -root-> VC2 -show-> VC3
VC1 is not embedded in a navigation controller, I'm starting that modal segue using performSegueWithIdentifier:sender:.
VC2 then is using a show segue to present VC3, which is the one where the back button is not visible. It still works though. But, it does appear if I exit to the home screen and then enters the app again, as shown here:
https://gfycat.com/VelvetyThisHamster.
Any ideas why this is happening?
edit: To make things clear: I want the button both visible and functioning (it's not that it's working that's the problem, but that it's hidden)
EDIT 2:
If I change my navigation flow to this
NC -root-> VC2 -show-> VC3
then the back button works as intended. So the question is, how can I add a regular view controller without a navigation controller before the first navigation controller? I want it before because VC1 should not have a navigation bar and VC2 should be presented modally.
try this
Hidden
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
//use this
self.navigationItem.setHidesBackButton(true, animated: false)
//else use this
self.navigationItem.leftBarButtonItem = nil
}
Show
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
//use this
self.navigationItem.setHidesBackButton(false, animated: false)
//else
self.navigationController.navigationItem.backBarButtonItem.enabled = TRUE
}
Update
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
//use this
self.navigationItem.setHidesBackButton(false, animated: false)
//else
let backButton = UIBarButtonItem(title: "leftbutton", style: UIBarButtonItemStyle.Plain, target: self, action: "buttonMethod")
self.navigationItem.leftBarButtonItem = backButton
}
func buttonMethod() {
print("Perform action")
}
I think I found the source of the problem, so I'll post it here in case someone else runs into the same problem.
The modal presentation between VC1 and NC was made from a background queue (by calling performSegueWithIdentifier:sender: in the completion handler of an NSURLSessionDataTask to be precise). By dispatching that line of code to the main queue the problem seems to disappear.
Turn out, I had NavigationBar tint color set to "Clear". Once I changed it, the back button appeared.
Related
I have a UILabel in my ViewController that has a NavigationController (let's say view controller A) with a tap gesture recognizer attached to the label. When the label is tapped another view appears (let's call it B). The user picks some text in B and the view dismisses back to A with the label text updated with the selection. So I created a delegation between A and B to get the selection. The problem is that I do not see the NavigationBar when B appears. Is there a way to fix this?
ViewController A
#IBOutlet weak var sectionName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let sectionLabelTap = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
sectionName.isUserInteractionEnabled = true
sectionName.addGestureRecognizer(sectionLabelTap)
}
#objc func labelTapped(_ sender: UITapGestureRecognizer) {
let sectionNameVC = storyboard?.instantiateViewController(withIdentifier: "SectionName") as! SectionNameTableViewController
sectionNameVC.selectionNameDelegate = self
sectionNameVC.userData = userData
present(sectionNameVC, animated: true, completion: nil)
}
In order to display the Navigation bar the UIViewController needs to have a UINavigationController.
You can add that sectionNameVC ViewController into a UINavigationController to persevere the present animation.
In that case your code might look something like this:
#objc func labelTapped(_ sender: UITapGestureRecognizer) {
let sectionNameVC = storyboard?.instantiateViewController(withIdentifier: "SectionName") as! SectionNameTableViewController
sectionNameVC.selectionNameDelegate = self
sectionNameVC.userData = userData
let naviagtionController = UINavigationController(rootViewController: sectionNameVC)
present(naviagtionController, animated: true, completion: nil)
}
Or you can simply call pushViewController on the View Controller A's navigation Controller, like this:
self.navigationController?.pushViewController(sectionNameVC, animated: true)
This will add sectionNameVC into the View Controller A's navigation Controller stack. In this case the transition animation will be different, the sectionNameVC will come from your right.
You are missing the concept between "Presenting" View Controller & "Navigating" the View Controller. You will get the answer, once you understood the concept. Here, it is..
When you are presenting the ViewController, you are completely replacing the stack container to the new view controller.
STACK holds the addresses of the ViewControllers you push or pop via navigating.
e.g:
present(sectionNameVC, animated: true, completion: nil)
On the other hand, if you are navigating to other view controller by pushing it. In this case, you can go back to previous controller by simple popping the ViewController address from stack.
e.g:
self.navigationController?.pushViewController(sectionNameVC, animated: true)
self.navigationController?.popViewController(animated: true)
So, If you navigate then, only you will get navigation Bar.
Now, in your case, you are presenting the ViewController and hence, navigation bar is not showing.
Consider a storyboard where we have UITabBarController, in it any UIViewController(lets call it VC) embedded in a UINavigationController. We want VC to have a BarButtonItems on its navigation bar. This storyboard is presented by push segue from another storyboard (having another navigation controller).
Everything looks OK in XCode, but navigation bar does not change in VC at the runtime. However when I change presenting this storyboard from push to modal, everything seems to be fine. IMHO it is because of embedding the navigation controller but I do not see any reason why it is not working. Any idea how to fix it legally (presenting by push) and without any pain would be helpful.
Thanks in advance
So I think you will have to employ some code to fix your issue but not much. I built a test project to test this and will attach images along with code.
First if I understand you correctly you have a navigationController push the new storyboard in question. See attached image.
I named the storyboard being pushed because that is what is happening. Then in my storyboard named Push here is the setup.
In the first view controller of the tabbarcontroller I added the below code. Obviously this hides the navigation controller that pushed us here. If you then visit controller number 2 our new navigation controller and items show. If hiding the navigation controller in the tabbarcontroller view controller 1 is not what you want to do then. continue reading.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//or to unhide from returning the opposite ->self.parent?.navigationController?.isNavigationBarHidden = true
self.parent?.navigationController?.isNavigationBarHidden = true
}
If you did not want to hide the navigation controller in the first view controller but when visiting controller 2 you want to see your items then add this to your viewWillAppear and in the first controller in viewWillAppear change the code from true to false.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Do any additional setup after loading the view, typically from a nib.
self.parent?.navigationController?.isNavigationBarHidden = true
}
This hides the parent navigation controller as basically that was covering up your navigation controller in your example. So above hides the parent navigation controller. This is also why presenting modally worked. Your navigation controller was hidden from the start. Hope this helps.
**Edit
If you want the navigation controller in tab 2 view controller but you want to keep the parent in tab one to be able to go back with the back button you can set this in viewWillAppear instead so it would look like this in view controller 1.
//tabcontroller vc 1
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = false
}
And in tabcontroller view controller 2 with the item in the bar you could do this.
//tabbarcontroller vc 2 with own navigationcontroller
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.parent?.navigationController?.isNavigationBarHidden = true
}
Finally if you want the back button visible in both controllers but want different right buttons do it programmatically in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.navigationItem.setRightBarButton(UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(FirstViewController.editSomthing)), animated: true)
}
And if you want to remove it in the other controller
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.navigationItem.rightBarButtonItem = nil;
}
In Both of the above examples directly above this, we are keeping the parent navigation controller so you would not need to embed your view controllers of the tab controller inside uinavigation controller.
You could also use a combo of the above code if you want the hide/show parent navigation controller in viewWillAppear as well. Some of this is dependent on the view hierarchy you choose now and in the future.
I have a View Controller which is the 2nd View Controller and need a navigation bar. The 1st one is a controller which doesn't need a Nav Bar and 3rd View Controller need a navigation bar.
As per stacks 3rd view controller will be no top of stack.
I have implemented these methods in 2nd View Controller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if self.navigationController?.navigationBarHidden == true{
self.navigationController?.navigationBarHidden = false
}
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
self.navigationController?.navigationBar.tintColor = Quikr_Util.colorWithHexString("#0083cb")
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.navigationBarHidden == false{
self.navigationController?.navigationBarHidden = true
}
}
The things were working fine if a use a back bar button to go back from 3rd to 2nd View Controller.
Things got messy when I started to slide from one View Controller to another for example, when I slide half and then release.
It means sliding from 3rd to 2nd but release in between so it goes to 3rd View Controller rather than second .
What may be the best way to hide and unhide view navigation bars .
Secondly, how do sliding works , which functions will get called when sliding happens?
For your first question
To hide and show the Navigation Controller, in 2nd and 3rd View Controllers, You can use:
Hide:
navigationController?.setNavigationBarHidden(true, animated: false)
Show:
navigationController?.setNavigationBarHidden(false, animated: false)
There is not really a need to check if it is hidden or not.
For the second question, you can use UIGestureRecognizer
Edit:
Just to be clear, instead of hiding and showing the in the same file, hide and show in viewDidAppear() in needed swift files
I have a main page, which has a button that will go to another page.
I want when i go the second page, to have a back button at the top left of the navigation bar.
I added a navigation controller to the main view controller, and then i added a push segue to my second view controller (second page), so an automatic back button created.
what i want is that i do NOT want the navigation bar to be in the main view controller, and I don't want it to be in the second view controller, I just want to have the back button in the second view controller.
I thought about it and i came up doing:
self.navigationController?.setNavigationBarHidden(true, animated: false)
in the main view controller, that actually hide the navigation, but that makes the second view ctonroller to loose its back button.
do you have any solution to my problem?
this is the main view controller (which has the navigation bar, but i would like to not have it)
this is the second view controller, which has the back button,
I have no problem if i leave the navigatino bar, if it was transparent, any idea please? (and by transparent, i mean i can see my image bellow it)
Update 1
after the first comment gives me a hint, i tried to applied it like this:
class CustomNavigationBar: UINavigationBar {
override func drawRect(rect: CGRect) {
super.drawRect(rect)
}
}
and I set the class of my navigation bar in the UINavigationControlelr to my custom navigation bar.
and in my main view controlelr i add this:
self.navigationController?.navigationBar.translucent = true;
self.navigationController?.navigationBar.backgroundColor = UIColor.clearColor()
but the result is that my Main view controller, still has a place (though it is empty) for the navigation bar. can't i make this place as transparent to see the image bellow it?
add this to the main view controller
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
I haven't been able to figure something out in swift in my AppDelegate. I've been working on this little problem for weeks (in the background, not continuously) with lots of googling. I must be missing something.
When a remote notification comes in to my iOS app I'd like to modally present the View appropriate for the message, with a UINavigationController that has a Back button. The idea is that when the user is finished dealing with the notification, they can press "Back" and go back to where they were. I can't get a "Back" button to show up in my navigation controller programmatically.
Not all of my existing views are embedded in navigation controllers. So, rather than finding an existing UINavigationController in the stack, I've been creating a new UINavigationController and trying to set it up and present it like this:
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var mvc = mainStoryboard.instantiateViewControllerWithIdentifier("MyViewIdentifier") as! MySpecialViewController
/* here is where I setup the necessary variables in the mvc view controller, using data from the remote message. You don't need to see all that */
/* this seems to work, I do get what looks to be a navigation controller */
let newNavController = UINavigationController(rootViewController: mvc)
/* this part doesn't work, I don't get a "Back" button */
let btn = UIBarButtonItem(title: "back", style: UIBarButtonItemStyle.Done, target: mvc, action: "backPressed")
newNavController.navigationItem.leftBarButtonItem = btn
/* this block seems to work. The idea is to work through the hierarchy of any modal presentations
until we get to the screen that is actually showing right now. I'm not
sure this will work in all situations, but it seems to work in my app's hierarchy. */
var currentViewController = self.window!.rootViewController
while currentViewController?.presentedViewController != nil {
currentViewController = currentViewController?.presentedViewController
}
/* this I suppose shows my new view controller modally, except without
the "back" button it's hard to be sure the navigation controller is even there. */
currentViewController?.presentViewController(newNavController, animated: true, completion: nil)
I do have a method called "backPressed" in the relevant view controller.
Any clues as to why the navigation bar button isn't showing up?
Edit: I knew that backBarButtonItem only shows up when we aren't at the top of the stack. But, leftBarButtonItem should show up even if we're at the top of the stack?
Here is simple example for you which can set navigation bar Programmatically from first View:
if let resultController = storyboard!.instantiateViewControllerWithIdentifier("SecondView") as? SecondView {
let navController = UINavigationController(rootViewController: resultController) // Creating a navigation controller with resultController at the root of the navigation stack.
self.presentViewController(navController, animated:true, completion: nil)
}
If you wan to add back button into that navigation then use this code into SecondView.swift class:
override func viewDidLoad() {
super.viewDidLoad()
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
navigationItem.leftBarButtonItem = backButton
}
func goBack(){
dismissViewControllerAnimated(true, completion: nil)
}
If you want to do all of this from firstView then here is your code:
#IBAction func btnPressed(sender: AnyObject) {
if let resultController = storyboard!.instantiateViewControllerWithIdentifier("SecondView") as? SecondView {
resultController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
let navController = UINavigationController(rootViewController: resultController) // Creating a navigation controller with VC1 at the root of the navigation stack.
self.presentViewController(navController, animated:true, completion: nil)
}
}
func goBack(){
dismissViewControllerAnimated(true, completion: nil)
}
Hope this will help you.
I take it that your view controller is used by more than one segue?
Within storyboard, you can embed the view controller in a navigation controller.
For the modal presentation, you segue to the navigation controller instead of the view controller. This will supply a bar for the modal presentation.
A Done button is more likely used to dismiss a modal presentation. (Users might be confused by that atypical use of a back button, as your presented navigation controller is not popping a view controller off its stack.)