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.
Related
I set up a right navigation button like so inviewWillAppear in . a class ChatMessageViewController..
let button2 = UIBarButtonItem(image: nil, style: .plain, target: self, action: #selector(blockPressed(sender:)))
button2.title = "Block"
self.navigationItem.rightBarButtonItem = button2
Now on the click of blockPressed another shared function is called like so...
#objc fileprivate func blockPressed(sender: UIButton) {
XMPPConfig.shared.blockUser(userJID: theUserJID!) //XMPPConfig is another class having some common functions and delegate methods.
}
(This function basically blocks a certain user like blocking a whatsapp user. Once blocking happens, certain delegate methods are called. One such delegate method after which I change the Block button is given as follows..)
func xmppBlocking(_ sender: XMPPBlocking!, didBlockJID xmppJID: XMPPJID!) {
print("successfully blocked!")
ChatMessageViewController.shared.setupUnBlock()
}
Doing this also properly calls setupUnBlock() function in ChatMessageViewController like so...
func setupUnBlock() {
if XMPPConfig.shared.sectionGroupsFlag == false {
let button2 = UIBarButtonItem(image: nil, style: .plain, target: self, action: #selector(unblockPressed(sender:)))
button2.title = "Unblock"
self.navigationItem.rightBarButtonItem = button2
}
}
But the button title still remains unchanged...i.e. it is still "Block"..what could be the reason for this...?
You should initialize your UINavigationBarButton using another initializer:
let right = UIBarButtonItem(title: "Some title", style: .plain, target: self, action: #selector(rightNavBarButtonPressed))
You are using UIBarButtonItem(image:... instead.
I tried to duplicate your code and apparently everything works.
So, the problem may be due to three possible problems, as far as i can see.
it may be thread problem. This is unlikley unless you have not turned on the thread checker in xcode.. But inside both unblock and block codes, enter
print(Thread.current)
to see both blocks of codes in setting the rightbarButton is in main thread. If not, you should know how to solve them.
It may be view update problem, unlikely still, but worth a try.. So in your block of codes where you are adding rightBarButton, add one more line of code.
self.navigationController?.viewIfLoaded?.setNeedsLayout()
to update the navigationController's view after you have set the rightBarButtonItem.
The most likely problem in my opinion is that, you have been doubling calling block barButtonItem codes. This means when you have called unblock barButtonItem setup codes, you accidentally called the block barButtonItem too.
To debug this is easy, you just put a statement in each of block of the codes, to see if they appear together.
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 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.
I'm writing a to-do list type app, in order to learn swift for iOS development. I had a settings button on the Navigation bar, on the right side, and whenever a person clicked a button on top of the table view, they could add a new item to their list. That causes a "cancel" button to appear on the left side of the bar and a "add" button to appear on the right side. When the person is done, however, those buttons disappear, and so I programatically re-create the settings button, with a call to a function that is supposed to call the segue.
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "modify"), style: UIBarButtonItemStyle.Plain, target: nil, action: Selector("showSettings:"))
This is the code that creates the button on the navbar (and it is indeed created)
func showSettings(sender: AnyObject?) {
print("segueShouldBeDone!")
performSegueWithIdentifier("showSettings", sender: sender)
}
And this is the function that is supposed to call the segue (I added the print to see if it was at least being called, and it isn't).
I've had this same problem in another place in my code, but I had given up on fixing it cause it wasn't that important for me then. But now it is interfering with how the app works, so I wanted to know how to fix it.
Any help would be great.
Your showSettings: selector isn't being called because you specified nil instead of self for the bar button item's target. Change the line to:
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "modify"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("showSettings:"))
This assume the showSettings: method is in the same class that contains this line of code.
Read the documentation for this initializer, specifically the info about the target parameter.