I have an app with a UITabBar.
I'm using the UITabBarAppearance to configure it.
When I start the app, the tab items are correct:
but when I minimise and then reopen the app they look like this:
Tapping on the tab bar items then restores them back to their original look i.e. the text is correctly drawn.
Any suggestions why this might be happening?
FYI The appearance code I'm using is this:
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.shadowImage = nil
appearance.backgroundColor = UIColor.custom.navBackground
let textAttributes = [NSAttributedString.Key.foregroundColor: UIColor.custom.pink]
appearance.stackedLayoutAppearance.normal.titleTextAttributes = textAttributes
appearance.stackedLayoutAppearance.selected.titleTextAttributes = textAttributes
appearance.inlineLayoutAppearance.normal.titleTextAttributes = textAttributes
appearance.inlineLayoutAppearance.selected.titleTextAttributes = textAttributes
self.tabBar.standardAppearance = appearance
self.tabBar.scrollEdgeAppearance = appearance
Related
After update to iOS 15, I implemented UITabBar configuration this way:
let backgroundColor = UIColor.grey
let selectedItemTextColor = UIColor.blue
let unselectedItemTextColor = UIColor.black
if #available(iOS 15, *) {
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.backgroundColor = backgroundColor
tabBarAppearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: selectedItemTextColor]
tabBarAppearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: unselectedItemTextColor]
tabBar.standardAppearance = tabBarAppearance
tabBar.scrollEdgeAppearance = tabBarAppearance
} else {
UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor: selectedItemTextColor], for: .selected)
UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor: unselectedItemTextColor], for: .normal)
tabBar.barTintColor = backgroundColor
}
This works fine for iOS 15 and older versions.
But in my project I need to set selected/unselected text color for one of tab bar items, different from other items. Set it in runtime.
There are 5 tab bar items. In some moment, I need this behavior: four of them should have blue/black text color (for selected/unselected states) and one should have red/green color.
Until iOS 15, I used this code to set colors of needed item in every moment of time:
let indexOfItemToChange = 4
tabBar.items[indexOfItemToChange].setTitleTextAttributes([.foregroundColor: UIColor.red], for: .selected)
tabBar.items[indexOfItemToChange].setTitleTextAttributes([.foregroundColor: UIColor.green], for: .normal)
After update update to iOS 15 it makes no effect. I have tried to set it like this:
let indexOfItemToChange = 4
tabBar.items[indexOfItemToChange].standardAppearance?.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.green]
tabBar.items[indexOfItemToChange].standardAppearance?.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.red]
But it makes no effect too. Even if before setting of tab bar item colors I set UITabBar appearances for each tab bar item:
tabBar.items.forEach {
$0.standardAppearance = standardAppearance
$0.scrollEdgeAppearance = scrollEdgeAppearance
}
Some one faced this issue? Any advice?
PS. In my project there are no xibs and storyboards. So I need to solve my problem only programmatically.
I have experienced a strange issue in the app where the selected tab bar colour changes from the colours I have set back to the tint colour after opening a view controller with the following option:
hidesBottomBarWhenPushed = true
Here is the code I have used to set the font colour prior to finding the issue inside of the initialiser of my customer UITabBarController:
let attributes = ...
UITabBarItem.appearance().setTitleTextAttributes(attributes, for: .normal)
I couldn't find a similar issue on StackOverflow and managed to figure out the solution with the help of the team in my company, so I thought I would share it here in case it helps anybody in the future.
The only way to fix the above issue that we've managed to find is adding the following code to the initializer of our customer UITabBarViewController
if #available(iOS 13, *) {
let appearance = UITabBarAppearance()
appearance.backgroundColor = UIColor.white
appearance.shadowImage = UIImage()
appearance.shadowColor = UIColor.white
let defaultAttributes = ...
appearance.stackedLayoutAppearance.normal.iconColor = UIColor.red
appearance.stackedLayoutAppearance.normal.titleTextAttributes = defaultAttributes
appearance.stackedLayoutAppearance.selected.iconColor = UIColor.red
appearance.stackedLayoutAppearance.selected.titleTextAttributes = attributes
tabBar.standardAppearance = appearance
}
I recently updated my Xcode to 11.4. When I run the app on the device, i've noticed that all my navigations item's titles gone fully black when being set from storyboard.
You can't change neither from code, the following line of code doesn't work anymore
self.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
I only make it work using some iOS 13 stuffs UINavigationBarAppearance
#available(iOS 13.0, *)
private func setupNavigationBar() {
let app = UINavigationBarAppearance()
app.titleTextAttributes = [.foregroundColor: UIColor.white]
app.backgroundColor = Constants.Color.barColor
self.navigationController?.navigationBar.compactAppearance = app
self.navigationController?.navigationBar.standardAppearance = app
self.navigationController?.navigationBar.scrollEdgeAppearance = app
self.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
}
Can somebody explain me why??? This is a crucial bug, or some new hidden feature?
This fixed it for me, using UINavigationBarAppearance instead, from: Customizing Your App’s Navigation Bar
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.black
appearance.titleTextAttributes = [.foregroundColor: UIColor.white] // With a red background, make the title more readable.
self.navigationBar.standardAppearance = appearance
self.navigationBar.scrollEdgeAppearance = appearance
self.navigationBar.compactAppearance = appearance // For iPhone small navigation bar in landscape.
} else {
self.navigationBar.barTintColor = UIColor.black
self.navigationBar.tintColor = UIColor.white
self.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
}
Note: I subclassed UINavigationController, and this was called from the override of viewWillAppear.
...or for AppDelegate, app-wide:
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.black
appearance.titleTextAttributes = [
NSAttributedStringKey.foregroundColor: UIColor.white
]
let buttonAppearance = UIBarButtonItemAppearance()
buttonAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.buttonAppearance = buttonAppearance
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UIBarButtonItem.appearance().tintColor = UIColor.white
} else {
UINavigationBar.appearance().barTintColor = UIColor.black
UINavigationBar.appearance().titleTextAttributes = [
NSAttributedStringKey.foregroundColor: UIColor.white
]
UINavigationBar.appearance().tintColor = UIColor.white
UIBarButtonItem.appearance().tintColor = UIColor.white
}
...for AppDelegate, app-wide, in Objective-C:
if (#available(iOS 13, *)) {
UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init];
[appearance configureWithOpaqueBackground];
appearance.backgroundColor = UIColor.whiteColor;
appearance.titleTextAttributes = titleAttributes;
UIBarButtonItemAppearance *buttonAppearance = [[UIBarButtonItemAppearance alloc] init];
buttonAppearance.normal.titleTextAttributes = barButtonItemAttributes;
appearance.buttonAppearance = buttonAppearance;
UINavigationBar.appearance.standardAppearance = appearance;
UINavigationBar.appearance.scrollEdgeAppearance = appearance;
UINavigationBar.appearance.compactAppearance = appearance;
[[UINavigationBar appearance] setTintColor:UIColor.blackColor];
} else {
[[UINavigationBar appearance] setBarTintColor:UIColor.whiteColor];
[[UINavigationBar appearance] setTintColor:UIColor.blackColor];
[[UINavigationBar appearance] setTranslucent:false];
[[UINavigationBar appearance] setTitleTextAttributes: titleAttributes];
[[UIBarButtonItem appearance] setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
}
On the storyboard, for your Navigation Controller change the "Bar Tint" to its "Default" value, then on your code you can change it as you normally would.
Apple finally fixed it in version 11.4.1
https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_1_release_notes
Not sure if it's a bug or not.
The way we fixed it is by setting the "Status Bar Style" to either dark or light content in project setting. This will force the Status Bar text color a certain way rather than being determined based on the devices being in Light or Dark mode.
In addition, you need to set the value "View controller-based status bar appearance" to "NO" in your Info.plist. without that value the "Status Bar style" will be overridden.
Next create a custom navigation controller and implement it in your storyboards.
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
setNavBar()
}
func setNavBar() {
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.blue
appearance.titleTextAttributes = [.foregroundColor: UIColor.yellow]
self.navigationBar.standardAppearance = appearance
self.navigationBar.scrollEdgeAppearance = appearance
self.navigationBar.compactAppearance = appearance
} else {
self.navigationBar.barTintColor = UIColor.blue
self.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.yellow]
}
}
}
*Colors are set so you can see them clearly working.
I found it was better to set the code in ViewDidLoad rather than ViewDidAppear because my colors were not being set on the initial load, only after navigating back and reloading.
I also found that this issue might be tied to the "Bar Tint" of a NavBar. when we were first trying to resolve it, we set the "Bar Tint" to default and that seemed resolve the error too. However, it made it so we couldn't get the NavBar background color what we wanted. So in my storyboards I made sure to set this value to default just for good measure.
Hope it helps
no need for the workaround.it is a bug in Xcode Interface Builder. Apple release Update for Xcode 11.4.1
from Apple developer release notes
Interface Builder
Fixed an issue that caused some UINavigationBar appearance properties
set in storyboard and XIB documents to be ignored when building with
Xcode 11.4. (60883063) (FB7639654)
https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_1_release_notes
Similar to Stu Carney's response on 3/25, I added a few more implementation details.
Create a subclass of UINavigationController. Add the following to viewWillAppear:
let isDarkMode = UserDefaults.standard.bool(forKey: "DarkMode")
let titleColor: UIColor = isDarkMode ? .white : .black
let navBarColor: UIColor = isDarkMode ? .black : .white
let tintColor: UIColor = isDarkMode ? .yellow : .red //back button text and arrow color, as well as right bar button item
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = navBarColor
appearance.titleTextAttributes = [.foregroundColor: titleColor]
appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
self.navigationBar.standardAppearance = appearance
self.navigationBar.scrollEdgeAppearance = appearance
self.navigationBar.compactAppearance = appearance // For iPhone small navigation bar in landscape.
self.navigationBar.tintColor = tintColor //changes back button text and arrow color, as well as right bar button item
} else {
self.navigationBar.barTintColor = navBarColor
self.navigationBar.tintColor = tintColor
self.navigationBar.titleTextAttributes = [.foregroundColor: titleColor]
self.navigationBar.largeTitleTextAttributes = [.foregroundColor: titleColor]
}
Then override preferredStatusBarStyle:
override var preferredStatusBarStyle: UIStatusBarStyle {
let isDarkMode = UserDefaults.standard.bool(forKey: "DarkMode")
return isDarkMode ? .lightContent : .default
}
If you want to update the navigation bar and status bar dynamically, like from a UISwitch IBAction or selector method, add the following:
navigationController?.loadView()
navigationController?.topViewController?.setNeedsStatusBarAppearanceUpdate()
Also, be sure to set all your navigation bars and bar buttons to the default colors in IB. Xcode seems to have a bug where the the IB colors override the colors set programatically.
In my case, after I upgraded Xcode from 11.3 to 11.4 this bug occurred.
So I have to change my code to blow in order to set an image as background in the navigation bar.
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
let backgroundImage = UIImage(named: "{NAVBAR_IMAGE_NAME}")?.resizableImage(withCapInsets: UIEdgeInsets.zero, resizingMode: .stretch)
appearance.backgroundImage = backgroundImage
self.navigationController?.navigationBar.compactAppearance = appearance
self.navigationController?.navigationBar.standardAppearance = appearance
self.navigationController?.navigationBar.scrollEdgeAppearance = appearance
} else {
self.navigationController?.navigationBar.barTintColor = Utils.themeColor
let backgroundImage = UIImage(named: "{NAVBAR_IMAGE_NAME}")?.resizableImage(withCapInsets: UIEdgeInsets.zero, resizingMode: .stretch)
self.navigationController?.navigationBar.setBackgroundImage(backgroundImage, for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
}
I had the same issue and was wondering that coding is not the solution. There MUST be a way. And found out that checking any option under Navigation bar -> Appearances in storyboard attribute inspector makes the title go black. So uncheck all of them. But not sure how I can have scroll Edge option on to color the status bar as well and still get the title colored.
In my application, I want to remove the UINavigationBar Back Button title.
I have done the following codes
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// do other staffs
initNavigationBar()
return true
}
private func initNavigationBar() {
let appearance = UINavigationBar.appearance()
appearance.barTintColor = GLOBAL_TINT_COLOR // a globally declared colour
appearance.tintColor = .white
appearance.barStyle = .black
if #available(iOS 13.0, *) {
let backButtonAppearance = UIBarButtonItemAppearance()
backButtonAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.clear]
appearance.standardAppearance.backButtonAppearance = backButtonAppearance
appearance.compactAppearance?.backButtonAppearance = backButtonAppearance
appearance.scrollEdgeAppearance?.backButtonAppearance = backButtonAppearance
} else {
// Hide navigation bar back button items
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal)
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .highlighted)
}
}
However, this code always works for iOS 10-12, but not working for iOS 13. Am I missing something?
In other cases, I have found many answers regarding the topic, but found no solution for iOS 13
I never want to use to set back button title as an empty string, rather than fixing it using appearance.
Thanks
I had a similar issue some weeks ago. I did not find a way to do it globally for the entire application, so I resorted to customizing each navigation controller (which thankfully wasn't many).
I did something like this by extending UINavigationController:
#available(iOS 13, *)
func hideBackButton() {
let appearance = self.navigationBar.standardAppearance
let hideBackButtonTitleAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.clear
]
let normalBackButton = appearance.backButtonAppearance.normal
let highlightedBackButton = appearance.backButtonAppearance.highlighted
normalBackButton.titleTextAttributes = hideBackButtonTitleAttributes
highlightedBackButton.titleTextAttributes = hideBackButtonTitleAttributes
navigationBar.standardAppearance = appearance
}
Then I used the hideBackButton method like so:
navigationController?.hideBackButton()
If there is a better way to do this for the entire application, let me know.
I have added ios15 remove BackBar button title and Set Navigations:
if #available(iOS 15, *) {
let appearance = UINavigationBarAppearance()
let pargraphStyle = NSMutableParagraphStyle()
pargraphStyle.alignment = .center
let fontApply = UIFont.bold(size: 18)
UINavigationBar.appearance().tintColor = UIColor.themeColor
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.black
appearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.themeColor, NSAttributedString.Key.font: fontApply, NSAttributedString.Key.paragraphStyle: pargraphStyle]
appearance.shadowImage = nil
appearance.shadowColor = .clear
UINavigationBar.appearance().barStyle = .default
UINavigationBar.appearance().tintColor = UIColor.themeColor
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().backIndicatorImage = #imageLiteral(resourceName: "BackIcon")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = #imageLiteral(resourceName: "BackIcon")
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
// Set Back Bar Button Appearance
let appearanceBackButton = UIBarButtonItemAppearance()
let hideBackButtonTitleAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.clear
]
let normalBackButton = appearance.backButtonAppearance.normal
let highlightedBackButton = appearance.backButtonAppearance.highlighted
normalBackButton.titleTextAttributes = hideBackButtonTitleAttributes
highlightedBackButton.titleTextAttributes = hideBackButtonTitleAttributes
appearance.buttonAppearance = appearanceBackButton
}
You can set your back item to have no title like this:
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:nil style:UIBarButtonItemStylePlain target:nil action:nil];
Note that this affects what shows up when something else is pushed onto the navigation stack. If you set view controller A as the root of the navigation controller and set A's back item like that, you'll see it when you push view controller B onto the stack. Setting B's back item will not affect what you see in the Back item when B is visible.
In iOS 13, they have changed the way that the nav bar colors operate. Now they use UINavigationBarAppearance along with UIBarButtonItemAppearance to customize the nav bar, along with standardAppearance & scrollEdgeAppearance.
I'm looking for a way to have different nav bar tint colors for standardAppearance & scrollEdgeAppearance. Or the ability to change bar button icon colors for each appearance.
//set the standard nav bar appearance
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
navBarAppearance.backgroundColor = UIColor.mainAppColorForNavBar
//set bar button appearance
let buttonAppearance = UIBarButtonItemAppearance()
buttonAppearance.normal.titleTextAttributes = [.foregroundColor : UIColor.white]
navBarAppearance.buttonAppearance = buttonAppearance
UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]).standardAppearance = navBarAppearance
//set the scroll edge nav bar appearance
let scrollNavBarAppearance = UINavigationBarAppearance()
scrollNavBarAppearance.configureWithOpaqueBackground()
scrollNavBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.label]
scrollNavBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label]
//set bar button appearance
let scrollButtonAppearance = UIBarButtonItemAppearance()
scrollButtonAppearance.normal.titleTextAttributes = [.foregroundColor : UIColor.label]
scrollNavBarAppearance.buttonAppearance = scrollButtonAppearance
UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]).scrollEdgeAppearance = scrollNavBarAppearance
This will set the nav bar tint color but does not distinguish between standardAppearance & scrollEdgeAppearance.
UINavigationBar.appearance().tintColor = UIColor.white
currently in scrollEdgeAppearance (looks the way I want, no changes needed)
currently in standardAppearance (the button is lost because it's the same color as the background, I want to change the icon color to white in standardAppearance )
Any help is appreciated.
Thanks,
I ran into a similar issue for the back button and solved it with setting an image with a different rendering mode for each appearance. This avoids a tintColor being applied.
// demo image
let image = UIImage(systemName: "ellipsis.circle")!
// image with .alwaysOriginal rendering mode to avoid tintColor application
let imageForNavBarAppearance = image
.withTintColor(.black)
.withRenderingMode(.alwaysOriginal)
let imageForScrollNavBarAppearance = image
.withTintColor(.green)
.withRenderingMode(.alwaysOriginal)
// your UINavigationBarAppearance instances
navBarAppearance.setBackIndicatorImage(imageForNavBarAppearance, transitionMaskImage: imageForNavBarAppearance)
scrollNavBarAppearance.setBackIndicatorImage(imageForScrollNavBarAppearance, transitionMaskImage: imageForScrollNavBarAppearance)
This solves it for back buttons only.
There are two other options to set background images for bar item appearances.
UINavigationBarAppearance.buttonAppearance
The appearance configuration with the background image would be applied to all bar items.
This should be fine, since you only have one bar item.
UINavigationBarAppearance.doneButtonAppearance
If you'd create a done-style bar item for your top-right "+" symbol, this appearance configuration should apply.
let ap = UIBarButtonItemAppearance()
ap.normal.backgroundImage = image.withTintColor(.black).withRenderingMode(.alwaysOriginal)
let scrollAp = UIBarButtonItemAppearance()
scrollAp.normal.backgroundImage = image.withTintColor(.green).withRenderingMode(.alwaysOriginal)
// apply to all bar items
navBarAppearance.buttonAppearance = ap
scrollNavBarAppearance.buttonAppearance = scrollAp
// or apply to done buttons
navBarAppearance.doneButtonAppearance = ap
scrollNavBarAppearance.doneButtonAppearance = scrollAp
After playing around with iOS 13 UINavigationBarAppearance , UIBarButtonItemAppearance etc. I realised that setting a color to navigationBar.tintColor directly apparenty overrides any UIBarButtonItemAppearance configurations.
So here is my workaround:
Given that your UIViewController most likely uses an UICollectionView, UITableView, UIScrollView use it’s UIScrollViewDelegate to get scroll updates. Also possible via KVO:
private func observeScrollView() {
self.observer = scrollView.observe(
\UIScrollView.contentOffset,
options: .new
) { [weak self] scrollView, change in
guard let offsetY = change.newValue?.y else { return }
self?.handleScroll(offsetY)
}
}
Now you need to calculate if your LargeTitles are still showing. You can use navigationController?.navigationBar.frame.maxY to access it’s height.
private func handleScroll(_ offsetY: CGFloat) {
let offsetY = -offsetY
let threshold: CGFloat = navigationController?.navigationBar.frame.maxY ?? 88
let largeTitlesVisible = offsetY > threshold
self.largeTitlesVisible = largeTitlesVisible
}
Use property observers to catch toggles.
private var largeTitlesVisible: Bool = true {
didSet {
if oldValue != largeTitlesVisible {
updateNavbar(tranparent: largeTitlesVisible)
}
}
}
Now actually change the navbars appearance:
func updateNavbar(transparent: Bool) {
// overrides UIBarButtonItemAppearance, UINavigationBarAppearance configurations ...
navigationController.navigationBar.tintColor = transparent ? UIColor.clear : UIColor.systemGreen
}