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,
In order to set the UINavigationBar
appearance in iOS 13
:
let proxyAppearance = UINavigationBar.appearance()
let scrollEdgeAppearance = UINavigationBarAppearance()
scrollEdgeAppearance.backgroundColor = <your favourite color 1>
let scrollEdgeAppearance = UINavigationBarAppearance()
scrollEdgeAppearance.backgroundColor = <your favourite color 1>
let compactAppearance = UINavigationBarAppearance()
compactAppearance.backgroundColor = <your favourite color 2>
let standardAppearance = UINavigationBarAppearance()
standardAppearance.backgroundColor = <your favourite color 3>
proxyAppearance.scrollEdgeAppearance = scrollEdgeAppearance
proxyAppearance.compactAppearance = compactAppearance
proxyAppearance.standardAppearance = standardAppearance
Include this code in a method marked with @available(iOS 13.0, *)
. Hope this will be useful to you. This does the job for me.
Here's how to configure a navigation bar that changes its button text color from green to red as you scroll:
let bar = self.navigationController!.navigationBar
let app = UINavigationBarAppearance()
let b = app.buttonAppearance
b.normal.titleTextAttributes = [.foregroundColor:UIColor.red]
bar.standardAppearance = app
let app2 = UINavigationBarAppearance()
let b2 = app2.buttonAppearance
b2.normal.titleTextAttributes = [.foregroundColor:UIColor.green]
bar.scrollEdgeAppearance = app2
But my sense is that you're not supposed to do that.
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
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.