I have a UINavigationController connected to a UITabBarCotnroller.
When i push a ViewController and in that class i write this code in their viewWillAppear method
self.navigationController?.navigationBarHidden = false
let yourBackImage = UIImage(named: "Back.png")
self.navigationController?.navigationBar.backIndicatorImage = yourBackImage
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = yourBackImage
self.navigationController!.navigationBar.backItem?.title = "";
By this code i am just setting a back button image with empty title.
But when i push another screen over the current pushed screen and then tap back it again shows the title with text "Back"
I am writing the above code in every view controller's viewWillAppear method which will be push.
While I believe setting the backIndicatorImage and backIndicatorTransitionMaskImage will work in viewDidAppear(), I found issues with setting the back text itself. I always had to set the text with a new button, and even then, it worked better for me to do it from the transitioning controller (as that's the view they would go back to, so I didn't have to care where the user was transitioning from).
You can try using this in your viewWillAppear(), but I'm using this in my prepare(for:, sender:) function.
let backItem = UIBarButtonItem()
backItem.title = "" // In my case, I was setting it here; you would blank it out
self.navigationItem.backBarButtonItem = backItem
Related
In my app, the first ViewController appears with right navigation bar items. I want to show different bar items on right side in child VC which is appeared by pushing. The items in the first VC shows fine, but the second doesn't. The bar button shows for a second when the VC disappeared.
// The things the first VC did
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.tintColor = .gray600
let stackView = UIStackView()
stackView.addArrangedSubviews(views: [registerButton, notificationButton])
stackView.spacing = 16
let rightBarButton = UIBarButtonItem(customView: stackView)
navigationController?.navigationBar.topItem?.rightBarButtonItem = rightBarButton
// The things the second did
navigationController?.navigationBar.tintColor = .gray600
navigationController?.navigationBar.topItem?.backButtonTitle = ""
navigationController?.navigationBar.backIndicatorImage = .back
navigationController?.navigationBar.backIndicatorTransitionMaskImage = .back
let rightBarButton = UIBarButtonItem(customView: editButton)
navigationController?.navigationBar.topItem?.rightBarButtonItem = rightBarButton
They did almost same things but the second doesn't work.
Here is the gif file i recorded. You can see Edit button for a second when the second VC disappeared. I tried to find the clues but i couldn't. Please check it and give me any comments. Thank you.
Delete the phrase navigationController?.navigationBar.topItem everywhere it appears, and never use it again, ever. It will totally break the navigation controller — as you yourself have demonstrated.
The only navigation item a view controller can talk to is its own navigationItem property. Thus the first vc is much more correct than the second vc, and so it works much better than the second vc.
you should not set a button and its title by using "topItem", instead you should set them by using navigationItem's rightBarButtonItem.
let rightBarButton = UIBarButtonItem(customView: editButton)
navigationItem.rightBarButtonItem = rightBarButton
let backButton = UIBarButtonItem(...)
navigationItem.leftBarButtonItem = backButton
I use a UISplitViewController and I change the back button color whenever showing the detail view through:
/* In ParentVC */
let backButton = UIBarButtonItem(title: "", style: .plain, target: self, action: nil)
backButton.tintColor = .red
self.navigationItem.backBarButtonItem = backButton
let vc = DetailVC()
self.splitViewController?.showDetailViewController(vc, sender: nil)
From inside the DetailVC the user is able to tap on a button to change the color of the VC, but the back button color is not changing. I found I can change the back button color by doing:
/* In DetailVC */
if let parentVC = self.splitViewController?.viewControllers.first?.children.first as? ParentVC {
parentVC.navigationItem.backBarButtonItem?.tintColor = getNewColor()
}
However, the back button's color doesn't change to the first color chosen until the user taps the color change button a second time. It's always one tap behind, so if I choose a different color every time, it will always be set to the previous color.
Does anyone know how to force reload the navigation bar's back button? Or knows why this is happening?
Thank you in advance!
For an iOS 14+ app I'd like to use navigationItem.backButtonDisplayMode = .minimal to hide the back button title, while still having the title available in the back button's long-press menu. Which works.. however I also want to change the back button image, to replace the default chevron.
But no matter what I try, I can't seem to find a solution that shows a custom back button image without a title, while also not showing a blank space in the back button's long-press menu, and not breaking the slide-to-go-back-gesture.
Anyone tried something similar, and succeeded?
So in the first view controller I show a title:
And then in the pushed view controller I want to show a custom back button image WITHOUT the "one" title (as seen below), and still have the long-press menu say "one" instead of a blank space.
This mostly gets me there actually, except that it breaks the gesture to slide to go back:
override func viewDidLoad() {
super.viewDidLoad()
let backImage = UIImage(named: "backImage")?.withRenderingMode(.alwaysOriginal)
navigationController?.navigationBar.backIndicatorImage = backImage
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
navigationItem.backButtonDisplayMode = .minimal
}
Update: actually it only seems to break on the simulator, it's all fine on an actual device. I now have a minimal project setup where it all works, now to find out why it doesn't work in my actual big project!
Okay, I finally figured out all the problems I was having.
Basically, this code works just fine:
override func viewDidLoad() {
super.viewDidLoad()
let backImage = UIImage(named: "backImage")?.withRenderingMode(.alwaysOriginal)
navigationController?.navigationBar.backIndicatorImage = backImage
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
navigationItem.backButtonDisplayMode = .minimal
}
But I was having problems with the swipe back gesture not working anymore. Turns out, that's a simulator bug, works fine on device. Then there was the problems that the custom back button image didn't actually show up in my view, because of this:
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = .pageBackground
appearance.titleTextAttributes = [.foregroundColor: UIColor.abbey]
appearance.shadowColor = .clear
navigationBar.scrollEdgeAppearance = appearance
navigationBar.standardAppearance = appearance
navigationBar.compactAppearance = appearance
As soon as you set a custom appearance, that completely wipes away the custom back button image. Simple fix, just set these things directly on the navigationBar without involving the appearance.
And now it all works!
To set the image, you can use:
navigationController?.navigationBar.backIndicatorImage = yourBackImage
navigationController?.navigationBar.backIndicatorTransitionMaskImage = yourBackImage
Let's say you have navigation from AVC to BVC.
If you want to disable the long press menu, you can follow this answer.
If you want the long press menu to work with the correct titles, you need to set navigationItem.title for your AVC to the right value, and navigationItem.backBarButtonItem should be nil(it's the default value) for BVC.
If you don't want to display the title in your AVC, you can hide it with titleView:
navigationItem.title = "title for long press navigation menu"
navigationItem.titleView = UIView()
I have subclassed UITabBarController to allow for some customization specific to my app. It is the root view controller of my UIWindow and displays itself correctly on launch, and even shows the correct tab's view hierarchy as well.
The problem is with the selected tabbar item's tint color. Inside viewDidLoad of the custom tab bar controller subclass, I have set both the unselected and selected tint colors for the tab bar. See below:
override func viewDidLoad() {
super.viewDidLoad()
tabBar.tintColor = .tabBarItemActiveTint
tabBar.unselectedItemTintColor = .tabBarItemInactiveTint
tabBar.barTintColor = .tabBarBg
let dashboardVC = DashboardViewController.build()
let settingsVC = SettingsTableViewController.build()
let settingsNavC = UINavigationController(rootViewController: settingsVC)
settingsNavC.navigationBar.barStyle = .black
viewControllers = [dashboardVC, settingsNavC]
selectedViewController = dashboardVC
// Accessing the view property of each tab's root view controller forces
// the system to run "viewDidLoad" which will configure the tab icon and
// title in the tab bar.
let _ = dashboardVC.view
let _ = settingsVC.view
}
As you can see, the controller has its child view hierarchies set, and the views are loaded at the bottom so their respective viewDidLoad methods run where I have code that sets the tabBarItem. Here's an example from the dashboard view controller:
tabBarItem = UITabBarItem(title: "Dashboard", image: UIImage(named: Theme.dashboardTabBarIcon), tag: 0)
Everything about this works except for the selected icon and title. When the app launches I can see the tab bar, the first view hierarchy (the dashboard) is visible onscreen and the tabs all function properly. But the dashboard's icon and title are in an unselected state. I have to actually tap the tab bar icon to get the state to change such that it is selected.
Once you tap one of the tabs, the selected state works as normal. The issue is only on the first presentation of the tab bar.
Here is an image showing the initial state of the tab bar on launch. Notice the dashboard icon is not selected, even though it is the presented view controller.
UPDATE
Skaal's answer below solved the problem for me.
For future reference: the key difference between the code presented here in my question and his sample in the answer is that the tabBarItem is set in viewDidLoad of his custom TabBarController class. By contrast, that code was placed within the viewDidLoad method of each constituent view controller class in my project. There must be a timing issue of when things are called that causes the tint color to not be set in one scenario and work properly in the other.
Key Takeaway: If you set up a tab bar controller programmatically, be sure to set your tabBarItem properties early on to ensure tint colors work properly.
You can use :
selectedIndex = 0 // the index of your dashboardVC
instead of selectedViewController
EDIT - Here is a working sample of UITabBarController:
class TabBarController: UITabBarController {
private lazy var firstController: UIViewController = {
let controller = UIViewController()
controller.title = "First"
controller.view.backgroundColor = .lightGray
return controller
}()
private lazy var secondController: UIViewController = {
let controller = UIViewController()
controller.title = "Second"
controller.view.backgroundColor = .darkGray
return controller
}()
private var controllers: [UIViewController] {
return [firstController, secondController]
}
override func viewDidLoad() {
super.viewDidLoad()
tabBar.tintColor = .magenta
tabBar.unselectedItemTintColor = .white
tabBar.barTintColor = .black
firstController.tabBarItem = UITabBarItem(title: "First", image: UIImage(), tag: 0) // replace with your image
secondController.tabBarItem = UITabBarItem(title: "Second", image: UIImage(), tag: 1) // replace with your image
viewControllers = controllers
selectedViewController = firstController
}
}
When I try to present a TabViewController, I get odd behavior from both my TabBar and NavigationBar as seen in the images below. It stays as shown in the "before" image until I touch the screen or push a button. At the point it jumps to the "after" image.
Before:
After:
Code used to present the TabViewController:
let delegate = UIApplication.shared.delegate as! AppDelegate
delegate.tabViewController = TabViewController()
self.present(delegate.tabViewController!, animated: true, completion: nil)
Initialization of the TabViewController:
override func viewDidLoad() {
super.viewDidLoad()
let groupTable = GroupTableViewController()
let nav = UINavigationController(rootViewController: groupTable)
nav.title = "Groups"
nav.tabBarItem.image = UIImage(named: "groups")
let vc2 = MeViewController()
vc2.title = "Me"
vc2.tabBarItem.image = UIImage(named: "user")
// let vc3 = SettingsViewController
// vc3.title = "Settings"
// vc3.tabBarItem.image = UIImage(named: "settings")
self.viewControllers = [nav, vc2]
self.selectedIndex = 0
}
Console log, but I don't think the error is relevant:
objc[63765]: Class PLBuildVersion is implemented in both /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/AssetsLibraryServices.framework/AssetsLibraryServices (0x11916f998) and /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/PhotoLibraryServices.framework/PhotoLibraryServices (0x118069d38).
One of the two will be used. Which one is undefined.
This is a new bug I've been experiencing seemingly after updating to Xcode 8.1/MacOS Sierra.
My XCode version is Version 8.1 beta (8T47). Could this be a bug in the beta?
I'm unsure what is causing this as I didn't make a code change when this started happening.
Thanks for the help.
The viewDidLoad of the tab view controller is really too late to be configuring the tab view controller with its two child view controllers. Either do this in the "Code used to present the TabViewController", or, if you really want to do it from within the tab view controller itself, do it from the tab view controller's initializer. Then all will be well.