Bar Button tint color in iOS 13 - ios

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
}

Related

UITabBar titles are truncating after minimising and restoring my app

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

Hide Navigation bar separator line on iOS 13

I have a view controller with a navigation bar with a large title. When I push the controller, only on iOS 13 is a line visible under the Navigation bar. How can I solve it?
I have already tried several solutions on Stack but they have not worked like:
let navigationBar = navigationController?.navigationBar
let navigationBarAppearence = UINavigationBarAppearance()
navigationBarAppearence.shadowColor = .clear
navigationBar?.scrollEdgeAppearance = navigationBarAppearence
With this snippet, even if I change the "clear color" with red color it is visible only in the first controller, in the pushed controller it is always gray.
How can I solve it?
Edit
I've solved with:
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.shadowColor = nil
navigationController?.navigationBar.standardAppearance = appearance
}
import UIKit
public protocol HideableHairlineHelper {
func hideHairline()
func showHairline()
}
extension HideableHairlineHelper where Self: UIViewController {
public func hideHairline() {
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.toolbar.setShadowImage(UIImage(), forToolbarPosition: .any)
}
public func showHairline() {
self.navigationController?.navigationBar.shadowImage = nil
}
}
I've tried the above-suggested ones and failed to remove the navigation separator line. Eventually, I've figured it out that to use TransparentBackground
The trick is to initialize UINavigationBarAppearance with TransparentBackground. Then you could easily remove the horizontal line of the navigation bar.
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor.green // Required background color
Then add the appearance changes to the navigation item as the apple suggested.
self.navigationItem.standardAppearance = appearance
self.navigationItem.scrollEdgeAppearance = appearance
self.navigationItem.compactAppearance = appearance
This is my NavBar template I use on my controllers, there's no line.
navigationController?.setNavigationBarHidden(false, animated: false)
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.isTranslucent = true
let titleAttributes =
[
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18),
NSAttributedString.Key.foregroundColor: UIColor.red,
]
self.navigationController?.navigationBar.titleTextAttributes = titleAttributes
I obviously change the font size and colour around but that's the basic function I put for each controller to get it clear(translucent).

Xcode 11.4. Navigation's Title Color gone BLACK from storyboard

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.

Dynamically Change iOS Nav Bar Color (Dark Mode)

I'm trying to implement a toggle for dark mode in my app - this would involve toggling the nav bar color to black when the UIViewController is already visible on the screen. I'm aware of how to do this by setting
UINavigationBar.appearance().barTintColor = .black
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UINavigationBar.appearance().isTranslucent = false
in the AppDelegate, however, this won't work in this case as it needs to be done dynamically.
I've also tried navigationController?.navigationBar.barTintColor = UIColor.white but this doesn't work either.
UPDATE:
I think a lot of the responses were confused with the purpose of this question - this is NOT at all related to the iOS13 Dark Mode which was just released - it is an INDEPENDENT dark mode feature I want to add to my app (similar to other apps like Messenger etc which had dark mode available IN-APP before iOS 13 was released). What I need to do is dynamically update the color of the UINavigationBar AFTER it has been already displayed on the screen, the same way I can change the background color of a view by doing view.backgroundColor = .white and this will update the color in real-time on screen.
Achieved this by making the nav bar translucent (in AppDelegate):
let barAppearance = UINavigationBar.appearance()
barAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: appRed]
barAppearance.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
barAppearance.shadowImage = UIImage()
barAppearance.isTranslucent = true
Next I create a view and place it behind the nav bar as follows (using SnapKit):
let coverView = UIView()
cover.snp.makeConstraints {
make in
make.left.right.top.equalTo(self)
make.bottom.equalTo(self.snp.top).offset(universalNumber(num: parent!.topbarHeight))
}
where the parent is my UIViewController and topBarHeight is:
extension UIViewController {
/**
* Height of status bar + navigation bar (if navigation bar exist)
*/
var topbarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}
Finally, to update the color I set
coverView.backgroundColor = universalWhite()
where
func universalWhite() -> UIColor {
let defaults = UserDefaults.standard
let darkMode = defaults.bool(forKey: "darkMode")
if darkMode {
return .black
} else {
return .white
}
}
You may want to check out the traits provided with the UIViewController to determine the current Interface style, instead of a manual check for versions running iOS 13+. Using this method, you can define colors in your Assets folder for both appearances.
For below iOS 13, you can use something similar to what #byaruah stated, but that is not a global effect. You should also consider using the UINavigationBar.appearance() functionality for a global approach.

How to definitively set UITabBar background color and tint color

I have been trying to set my UITabBar's tint color and background color for quite some time now and nothing seems to work.
So far I have tried:
tabBarController?.tabBar.backgroundColor = UIColor.orangeColor()
tabBarController?.tabBar.barTintColor = UIColor.whiteColor()
as well as:
UITabBar.appearance().tintColor = UIColor.orangeColor()
Neither of these seemed to have any effect on my tab bar. I'd also like to mention that I have the VC embedded in a navigation controller for which the global tint color that I set works perfectly fine.
If you want to set tabbar's tint and barTint color implicitly then in your Appdelegate.swift,
UITabBar.appearance().barTintColor = .orange
UITabBar.appearance().tintColor = .green
If you want to set tabbar's tint and barTint color for specific viewController then in ViewController.swift,
self.tabBarController?.tabBar.tintColor = .orange
self.tabBarController?.tabBar.barTintColor = .green
Set tab bar background color with barTintColor:
self.tabBar.barTintColor = UIColor.blueColor()
//or
UITabBar.appearance().barTintColor = UIColor.blueColor()
And for tab bar tint color:
self.tabBar.tintColor = UIColor.whiteColor() // Selected tab color
//or
UITabBar.appearance().tintColor = UIColor.whiteColor()
In similar fashion to how UINavigationBar is by default transparent on iOS 15 when there is no content behind it, the UITabBar works the same way. This might either be a nice visual refresh you get for free (since it is turned on by default once you build with Xcode 13) or it might cause a lot of issues for your app.
if #available(iOS 13.0, *) {
let tabBarAppearance: UITabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithDefaultBackground()
tabBarAppearance.backgroundColor = UIColor.tabBarBackground
UITabBar.appearance().standardAppearance = tabBarAppearance
}
if #available(iOS 15.0, *) {
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
}
Also you can set it from UIEditor like so
I always like to do some kinds of settings on the storyboard. Here is the #IBDesignable extension
#IBDesignable extension UITabBar {
#IBInspectable var barTintColor: UIColor? {
set {
guard let uiColor = newValue else { return }
UITabBar.appearance().barTintColor = uiColor
}
get {
guard let color = UITabBar.appearance().barTintColor else { return nil }
return color
}
}}
Swift 4+ version
UITabBar.appearance().barTintColor = UIColor.red
UITabBar.appearance().tintColor = UIColor.white

Resources