I know that you can set the title of the back button from the IB or in prepareForSegue, but in my app I need to update the title according to events that can happen while the controller is visible.
I tried but nothing happens:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: title, style: .Plain, target: nil, action: nil)
This works though but has no back arrow:
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: title, style: .Plain, target: nil, action: "popVC")
Any ideas?
The backBarButtonItem is the item used for the back button of the next controller in the navigation stack.
So for example if you have a navigation controller with a root viewController A and you push a viewController B, then the back button title that you see once B is pushed is configured using A.
You could have something like this :
A.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Go back to A", style: .Plain, target: nil, action: nil)
Once B is pushed, you see a back button with "Go back to A".
In your case the tricky part is to find A in the navigation stack from B.
You can do it by searching in the viewControllers of the navigationController like so :
// This code works from the `B` view controller
let viewControllers = self.navigationController?.viewControllers ?? []
if let indexOfCurrent = viewControllers.indexOf(self) where (indexOfCurrent > viewControllers.startIndex) {
let indexOfPrevious = indexOfCurrent.advancedBy(-1)
let previousViewController = viewControllers[indexOfPrevious]
previousViewController.navigationItem.backBarButtonItem?.title = "New Title"
}
Edit
I don't know any clean way to refresh the navigation bar after that. Maybe you could ask a separate question just for that.
What you could do is pop and push the view controller without animation
if let navigationController = self.navigationController {
navigationController.popViewControllerAnimated(false)
navigationController.pushViewController(self, animated: false)
}
Or maybe try to create a new UIBarButtonItem instead of changing the title of the existing one.
try this:
let baritems = self.navigationController?.navigationBar.items
for item in baritems!{
if (item.leftBarButtonItem != nil){
item.title = "123"
}
}
This question is pretty old now, but I was a problem changing the back button text based on a nested tableViewController's selected cell.
It looks like the default back button for a navigationViewController bases its text on the title of the view controller it takes the user back to.
You should probably be careful, as my solution makes a couple assumptions that I'm not positive will always be true.
let vcs = navigationController?.viewControllers
vcs?[(vcs?.count)! - 2].navigationItem.title = "Your Text Here"
Assuming that our current view controller is at the top of the navigation stack -> vcs[vcs.count - 1]; there exists a view controller on that stack before this one -> vcs[vcs.count - 2]; and the navigationItem.title of that preceding view controller can be changed without unwanted side effects; we can change our back button text by changing the navigation title of that preceding view controller.
Related
I have a PageViewController that loads different photo albums, each foto album is loaded from a different view controller (album1ViewController... album3ViewController), as shown below.
Storyboard
Each view controller (album1... album3) is loaded from a navigation view controller so they load a nav bar. I wanted to add a save to camera roll button to the navigation bar so I put this code in the ItemVC (the one that loads the image) and also in the PageViewController, but it didn't activate the save button:
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveToCameraRoll))
So I figured that the only way to add the button programatically was inside the albumsVCs. (I try adding the bar in the storyboard in ItemsVC but it didn't show properly). But now I want to call the function SaveToCameraRoll in a button called by album#ViewController with the image in ItemsViewController. I've tryied first leaving the function inside ItemsVC and trying to call it from albumVC like this:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ItemController") as! ItemViewController
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Guardar en Fotos", style: .plain, target: self, action: #selector(vc.saveToCameraRoll))
But the app crashes. with this error:
unrecognized selector sent to instance 0x7fca05d32e60
Then I tried in albumVC adding the SaveToCameraRoll function like this:
#objc func saveToCameraRoll() {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ItemController") as! ItemViewController
let imageData = UIImagePNGRepresentation(vc.contentImageView.image!)
let compresedImage = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(compresedImage!, nil, nil, nil)
}
In the second case I algo get an error and a crash, in the line:
let imageData = UIImagePNGRepresentation(vc.contentImageView.image!)
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
Is there a way to add this function? or is there a simpler way to archive this?
Thanks!
I would suggest setting up your album view controllers to have a delegate property.
Define a protocol ParentProtocol that implements your saveToAlbum method.
Make you ItemController conform to that protocol.
In your page view controller, as you load pages, set their delegate property to self.
In your Album view controllers, create an IBAction method in the Album view controller that sends a saveToAlbum method to the delegate.
That's a pretty common pattern for forwarding messages from child view controllers up to the parent.
I created a project on Github recently called TabBarControllers that does something very similar with tab bar controllers (A tab bar controller manages 2 or more child view controllers as tabs.) in that case the child view controllers are loaded from the storyboard at startup through a segue. In your case you'll have to write code that sets the delegate as it loads the album view controllers, but the idea is very similar.
When pushing a controller to the navigation stack i execute:
self.navigationItem.title = "";
As i dont want the next view to show the name of the previous controller on the back button.
When i get back to that controller i do this:
override func viewWillAppear(_ animated: Bool) {
self.navigationItem.title = "Title Of View";
...
}
The title is set appropriately but it lags for about 1 second or so.. I remember using this techinique for quite some time without having a problem in the past. The code that sets the titles isn't within a network call or anything like that..
Any ideas about what might be causing this?
In order to hide the back button title you should set empty UIBarButtonItem instance to the self.navigationItem.backBarButtonItem
right after you push the new view controller, like so:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .done, target: nil, action: nil)
This way you don't have to manipulate the title of the view controller.
I have a navigation bar placed directly on my scene. On his I have a navigation item. I can successfully change title on it.
Now I want a back button to appear - I do not need any customization at present, just the ability to catch the click and default ios look for whatever plaform the app is running (Since Swift that is ios7+).
In viewDidLoad() I have written
outletCatalogNavItem.backBarButtonItem = UIBarButtonItem(title: "test", style .plain, target: self, action: "ownbackNavigationFuncCode");
In *ViewDidAppear()* outletCatalogNavItem.backBarButtonItem is non-nil
Note: I am not using any shared navigation control across scenes in my storyboard - nor wish to at this point since I am porting an app from another tool/language which has its own navigation/stack logic already (meaning I handle switching and navigation myself in code)
Embed your view controller in a UINavigationController. When you push/segue to a new view controller the < Back button you're looking for will appear on the left hand-side.
let backItem = UIBarButtonItem()
backItem.title = "Back"
navigationItem.backBarButtonItem = backItem
I have a pretty common scenario. A tree of unknown depth based on data, so I have a navigation controller with a tableView controller as the root controller. Every time I need to go deeper, I create a new tableview controller, populate it with data based on the previous selection, and push the new tableview controller onto the navigation controller stack:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
let cellText = cell?.textLabel?.text
let bcti = CustomDataClass(querystring: cellText!)
if(bcti.getStatus() == "Drill")
{
let bctvc = CustomTableViewControllerClass(tableinfo: bcti)
//Note: self in this instance is my custom UITableViewController class
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: cellText, style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
self.navigationController?.pushViewController(bctvc, animated: true)
}
This works fine, a new tableview controller is created based on the selected cell from the previous controller, pushing it on the navigation stack works fine as well. The weird thing is I can't seem to get the back bar item to display the selected value from the previous controller.
If I use the cellText variable containing the text of the previously selected cell (as shown in the code above) to assign the UIBarButton title, it seems to ignore that and default to having 'Back' as the title anyway.
The cellText variable clearly has the correct value in it, I verified with NSLog statements, and also even tried conditionally unwrapping it to be 100% sure it has a value at run-time. It's being used to progress in the first place, so I think we can hopefully rule out an optional nil issue.
What's really strange to me though is that hard-coding a string into the title works just fine. So for instance if I replace:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: cellText, style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
with:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Bananas", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
It correctly displays Bananas instead of Back. I can also declare a variable and hard code that with a different value and it works fine. I.E:
let tempvar = "Bananas!"
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: tempvar, style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
It seems to only be when I try to access the text associated with the cell that it doesn't work.
Does anyone know what I'm missing here? Something else that might be worth noting is that I'm using a Storyboard to set up my initial navigation controller & tableview controller, I'm only creating the new instances of the tableview controller programmatically.
Check what the documentation of backBarButtonItem says. The cell text might simply be too long.
Instead of trying to hardcode the same values, try to change the datasource so that it has very short text values and see if it works. E.g. the cells might be displaying the first line of the multiline text now and you might have hardcoded a copy of only the first (the only visible) line. Hardcode is bad, even for tests.
I have a settings bar button item (set as left bar button item). I only want to display it if the user is logged in.
I thought I could use the following for anonymous users
navigationItem.leftBarButtonItem = nil
But then how would I show it as soon as they logged in?
You can store a copy of the leftBarButtonItem in a strong property and update it after the users log in.
var leftBarButtonItem : UIBarButtonItem!
Inside viewDidLoad:
self.leftBarButtonItem = UIBarButtonItem(title: "test", style: UIBarButtonItem.Style.Plain, target: nil, action: nil)
In logic:
if loggedIn
{
self.navigationItem.leftBarButtonItem = self.leftBarButtonItem
}
else
{
self.navigationItem.leftBarButtonItem = nil
}
Best Way is just custom your Bar buttom with image. Set barbuttom.image = nil to Hide again assign the image to show. And dont forget to make the barbutton isEnabled as false.
I have more that 2 menuitems and remove/add menuitem is an overhead. This code snippet worked for me.
func showMenuItem(){
menuItemQuit.customView?.isHidden = false
menuItemQuit.plainView.isHidden = false
}
func hideMenuItem(){
menuItemQuit.customView?.isHidden = true
menuItemQuit.plainView.isHidden = true
}
if you want to hide/show UIBarButtonItem : For Swift 3
Used below simple code :
Declaration :
var doneButton = UIBarButtonItem()
In ViewDidLoad() or ViewWillAppear() or where you want to hide it : [hide bar button]
self.navigationItem.rightBarButtonItem = nil
where you want to show bar button : [use anywhere in your code]
self.navigationItem.rightBarButtonItem = self.doneButton
doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.dismissPicker))
Swift 5.x
I faced the same dilemma and unfortunately no solution worked for me. Adding and removing buttons and related segues is unnecessarily too much of code when it includes multiple buttons on multiple screens. I have taken this approach for one or two buttons in the past and it becomes pretty ugly pretty fast.
The code menuItemQuit.customView?.isHidden = false doesn't seem to work on iOS 13 and above either, otherwise it would have made life so much easier.
My approach was to simply disable the bar button and change its tint to the navigation colour's tint.
In my app What.To.Eat I display bar buttons based on user's login status. Every element of the app is themed so that I could control all the colors based on various factors.
The navigation bar's color is named commonButtonColor and the bar buttons tint color is named commonButtonColor.
When I have to hide a bar button, I simply do the following:
let nav = self.navigationController?.navigationBar
nav?.tintColor = Theme.shared.titleText
nav?.barTintColor = Theme.shared.headerBg
if person.loggedIn {
mealPrefsBarButton.tintColor = Theme.shared.commonButtonColor
mealPrefsButton.isEnabled = true
} else {
mealPrefsBarButton.tintColor = Theme.shared.headerBg
mealPrefsButton.isEnabled = false
}
Where theme colors are defined in a separate file like this:
static var headerBg: UIColor {
return UIColor(red: 0.965, green: 0.969, blue: 0.973, alpha: 1.00)
}
The above is a simplified version of what I do in the app to make it clear what I am doing. I hope it would help someone trying to achieve the same. It is simple solution and works just perfectly with a few lines of code.
As an example from the app, this is how two buttons appear and disappear based on whether the My Recipes button is selected or not:
I have a same problem and solved. I have a bar button item with image
barbtnClose.isEnabled = false
barbtnClose.image = nil
barbtnClose.customView?.isHidden = true // do not work in iOS 13
Swift 5
A better solution and works even if you have set a custom navigation bar.
Hide navigation bar button item or back button leftBarButtonItem / rightBarButtonItem
if login == true {
self.navigationItem.leftBarButtonItem = nil
} else {
print("set your bar button or return")
}
Hide back bar button in navigation controller with swift 5
self.navigationItem.leftBarButtonItem = nil
self.navigationItem.hidesBackButton = true