TabBar and NavigationBar do not change colors - ios

I have run into some issues running xcode 14 and ios 16.
The navigation bar does not change colors, and the Tab Bar is the same color as the background (I think it should have a different tint).
class MainTabController: UITabBarController {
// MARK: - Properties
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureViewControllers()
}
// MARK: - Helpers
func configureViewControllers() {
let feed = FeedController()
let nav1 = templateNavigationController(image: UIImage(named: "home_unselected"), rootViewController: feed)
let explore = ExploreController()
let nav2 = templateNavigationController(image: UIImage(named: "search_unselected"), rootViewController: explore)
let notifications = NotificationsController()
let nav3 = templateNavigationController(image: UIImage(named: "search_unselected"), rootViewController: notifications)
let conversations = ConversationsController()
let nav4 = templateNavigationController(image: UIImage(named: "search_unselected"), rootViewController: conversations)
viewControllers = [nav1, nav2, nav3, nav4]
}
func templateNavigationController(image: UIImage?, rootViewController: UIViewController) -> UINavigationController {
let nav = UINavigationController(rootViewController: rootViewController)
nav.tabBarItem.image = image
nav.navigationBar.barTintColor = .white
return nav
}
}
I have tried setting the appearance of the navigation bar:
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
nav.navigationBar.standardAppearance = appearance
nav.navigationBar.scrollEdgeAppearance = nav.navigationBar.standardAppearance
this seems a bit redundant, and do you have to do this everytime for each nav controller?

No, you don't have to do this for each Navigation Controller.
You can make an extension of UINavigationBar.
extension UINavigationBar {
static func create() {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.backgroundColor = .white
appearance().standardAppearance = navBarAppearance
appearance().scrollEdgeAppearance = navBarAppearance
appearance().compactAppearance = navBarAppearance
}
}
and call UINavigationBar.create() from your application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
Similar approach can be done for the UITabBar

Related

Config Navigation bar only work if viewControllers are created inside viewWillAppear in UITabBarController

I have a UITabBarController with 2 ViewControllers: HomeViewController and ProfileViewController
Inside HomeViewController, I have to change the NavigationBar appearance with the following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let nav = self.navigationController {
nav.navigationBar.layer.zPosition = 0
}
let logo = UIImage(named: "plentinaText")?.withRenderingMode(.alwaysTemplate)
let container = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 0))
let imageView = UIImageView(image: logo)
imageView.tintColor = .white
imageView.contentMode = .scaleAspectFit
container.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: container.topAnchor),
imageView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -10),
imageView.centerXAnchor.constraint(equalTo: container.centerXAnchor),
imageView.widthAnchor.constraint(equalToConstant: view.frame.width)
])
self.tabBarController?.navigationItem.titleView = container
}
Problem is, this block of code only work if I create HomeViewController ProfileViewController inside viewWillAppear in UITabBarController.
If I put it in ViewDidLoad, it don't even have a NavigationBar. But when I push to another vc and go back to HomeViewController, the Nav bar will work as I wanted.
//
// HomeTabbarController.swift
// Plentina
//
// Created on 10/29/21.
//
import UIKit
class HomeTabbarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
UITabBar.appearance().barTintColor = PlenitaColors.buttonBackGround // your color
if #available(iOS 13.0, *) {
UITabBar.appearance().unselectedItemTintColor = UIColor.systemGray4
} else {
UITabBar.appearance().unselectedItemTintColor = UIColor.lightGray
}
UITabBar.appearance().tintColor = .white
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = PlenitaColors.buttonBackGround
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// tabbar 1
let storyboard = UIStoryboard(name: "Custom", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "photo") as? HomeViewController
let item1 = viewController
let unselectedIcon1 = UIImage(named: "tabbarHome")?.withRenderingMode(.alwaysTemplate)
let selectedIcon1 = UIImage(named: "tabbarHome")!.withRenderingMode(.alwaysTemplate)
let icon1 = UITabBarItem(title: "Loans", image: unselectedIcon1, selectedImage: selectedIcon1)
item1!.tabBarItem = icon1
// tabbar 2
let unselectedIcon2 = UIImage(named: "tabbarProfile")?.withRenderingMode(.alwaysTemplate)
let selectedIcon2 = UIImage(named: "tabbarProfile")!.withRenderingMode(.alwaysTemplate)
let item2 = ProfileViewController()
let icon2 = UITabBarItem(title: "Profile", image: unselectedIcon2, selectedImage: selectedIcon2)
item2.tabBarItem = icon2
let controllers = [item1!, item2] // array of the root view controllers displayed by the tab bar interface
self.viewControllers = controllers
}
// Delegate methods
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Should select viewController: \(viewController.title ?? "") ?")
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
I found a workaround. I just need to leave self.viewControllers = controllers in viewWillAppear and move the rest to ViewDidLoad, but I still don't know why?
class HomeTabbarController: UITabBarController, UITabBarControllerDelegate {
var controllers: [Any]?
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
UITabBar.appearance().barTintColor = PlenitaColors.buttonBackGround // your color
if #available(iOS 13.0, *) {
UITabBar.appearance().unselectedItemTintColor = UIColor.systemGray4
} else {
UITabBar.appearance().unselectedItemTintColor = UIColor.lightGray
}
UITabBar.appearance().tintColor = .white
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = PlenitaColors.buttonBackGround
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
// tabbar 1
let storyboard = UIStoryboard(name: "Custom", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "photo") as? HomeViewController
let item1 = viewController
let unselectedIcon1 = UIImage(named: "tabbarHome")?.withRenderingMode(.alwaysTemplate)
let selectedIcon1 = UIImage(named: "tabbarHome")!.withRenderingMode(.alwaysTemplate)
let icon1 = UITabBarItem(title: "Loans", image: unselectedIcon1, selectedImage: selectedIcon1)
item1!.tabBarItem = icon1
// tabbar 2
let unselectedIcon2 = UIImage(named: "tabbarProfile")?.withRenderingMode(.alwaysTemplate)
let selectedIcon2 = UIImage(named: "tabbarProfile")!.withRenderingMode(.alwaysTemplate)
let item2 = ProfileViewController()
let icon2 = UITabBarItem(title: "Profile", image: unselectedIcon2, selectedImage: selectedIcon2)
item2.tabBarItem = icon2
controllers = [item1!, item2] // array of the root view controllers displayed by the tab bar interface
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.viewControllers = controllers as? [UIViewController]
}
// Delegate methods
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Should select viewController: \(viewController.title ?? "") ?")
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

iOS: Navigation Bar of a Programmatically created UINavigationController Not Expanding to Safe Area

I want the navigation bar to expand to safe area in a programmatically created UINavigationController. I'm working on a project where they create the initial view controller programmatically and setup its navigation bar in the SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
let initialViewController = initViewController()
let navigationCotnroller = UINavigationController(rootViewController: initialViewController)
navigationBarConfiguration(navigationCotnroller)
window?.rootViewController = navigationCotnroller
window?.makeKeyAndVisible()
}
private func initViewController () -> UIViewController {
let view_controller_to_be_returned = DeviceSearchVC()
view_controller_to_be_returned.title = "Devices"
return view_controller_to_be_returned
}
private func navigationBarConfiguration (_ controller: UINavigationController) {
controller.navigationBar.prefersLargeTitles = true
controller.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
controller.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
controller.navigationBar.tintColor = .white
controller.navigationBar.backgroundColor = UIColor.systemBlue
}
It looks like this:
I want the navigation bar to expand to the safe area.
I tried something like this:
extension UIViewController: UINavigationBarDelegate{
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
but that didn't work
Just add this to the end of your navigationBarConfiguration func
if #available(iOS 13.0, *) {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.backgroundColor = UIColor.systemBlue
controller.navigationBar.standardAppearance = navBarAppearance
controller.navigationBar.scrollEdgeAppearance = navBarAppearance
} else {
controller.edgesForExtendedLayout = []
}

Change UINavigationController color and font

I'm writing my app router like this:
final class AppRouter {
let navigationController: UINavigationController
init(window: UIWindow) {
navigationController = UINavigationController()
window.rootViewController = navigationController
...
}
I'm calling router initialiser in application:didFinishLaunchingWithOptions: method.
I was trying to change it style (colour, font and other) by changing it properties, child properties, using UINavigationBar.appearance()
Nothing works. I was setting translucent to false. Only storyboard changes are making any effect, but then I have storyboard based navigation, that I don't want to have.
I have seen many posts about this issue, nothing is working.
If someone have cookbook, that is working on newest iOS (currently 11.4), please share!
Edit:
Like I said making changes like:
UINavigationBar.appearance().barTintColor = color
UINavigationBar.appearance().isTranslucent = false
this is used in didFinishLaunching.
or in constructor:
navigationController.navigationBar.barTintColor = color
Both methods fail to set color of navigation controller bar.
Edit 2:
App delegate calls:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window
window.makeKeyAndVisible()
appRouter = AppRouter(window: window)
return true
}
Use below extension for UINavigationController
extension UINavigationController
{
func setMainTopNavigationBarAttribute() -> Void
{
self.navigationBar.shadowImage = UIImage()
self.navigationBar.isTranslucent = false
self.navigationBar.barTintColor = UIColor.black
self.navigationBar.tintColor = UIColor.white
self.navigationBar.backgroundColor = UIColor.clear
let navBarAttributesDictionary: [NSAttributedStringKey: Any]? = [
NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.font: UIFont(name: "HelveticaNeue-Bold", size: 18.0)
]
self.navigationBar.titleTextAttributes = navBarAttributesDictionary
}
}
final class AppRouter {
let navigationController: UINavigationController
init(window: UIWindow) {
navigationController = UINavigationController()
window.rootViewController = navigationController
navigationController.setMainTopNavigationBarAttribute()
}

Navigation controller title not showing up?

So when I have my OptionsViewController as the rootViewController in the AppDelegate didFinishLaunchingWithOptions...
let rootVC = OptionsViewController()
let navigationController = UINavigationController(rootViewController: rootVC)
navigationController.navigationBar.barTintColor = .white
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.tintColor = .black
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window!.rootViewController = navigationController
self.window?.makeKeyAndVisible()
...setting the title of the OptionViewController works if I do this in viewDidLoad():
title = "Route Options"
But when I push OptionsViewController onto the navigation stack the title doesn't show up.
I.e. if I start w/ a different view as the rootViewController in AppDelegate:
let rootVC = HomeViewController()
let navigationController = UINavigationController(rootViewController: rootVC)
navigationController.navigationBar.barTintColor = .white
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.tintColor = .black
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window!.rootViewController = navigationController
self.window?.makeKeyAndVisible()
And in HomeViewController I push my OptionViewController like this:
let optionsVC = OptionsViewController()
navigationController?.pushViewController(optionsVC, animated: true)
The title does not show up!
The only way I've managed for the title to show up is by doing (in OptionViewController)
navigationController?.navigationBar.topItem?.title = "Route Options"
But it shows up as the back button rather than in the middle, which is not what I want.
If anyone could tell me how I could set the title so that it is on the middle of the navigation bar when it is pushed onto the navigationController stack that would be great!
Code
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let rootVC = HomeViewController()
let navigationController = UINavigationController(rootViewController: rootVC)
let barAppearance = UINavigationBar.appearance()
barAppearance.barTintColor = UIColor.blue
barAppearance.tintColor = UIColor.white
barAppearance.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window!.rootViewController = navigationController
self.window?.makeKeyAndVisible()
return true
}
HomeViewController.swift
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, DestinationDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let optionsVC = OptionsViewController()
self.definesPresentationContext = false //else going to try and present optionVC on homeVC when in optionVC
navigationController?.pushViewController(optionsVC, animated: true)
}
tableView.deselectRow(at: indexPath, animated: true)
}
}
OptionsViewController.swift
class OptionsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
DestinationDelegate, SearchBarCancelDelegate,UISearchBarDelegate,
CLLocationManagerDelegate {
override func viewDidLoad() {
self.title = "Route Options"
}
You need to set the navigationItem.title to desired value. If you want an image you set navigationItem.titleView
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Your title here"
}
For others coming here based on the title, don't forget to set your ViewController class in IB to the appropriate Swift file.
After doing that I was able to set the title without a problem using
self.navigationItem.title = "my title"
or
self.title = "my title"
Try it:
In HomeViewController:
let optionsVC = OptionsViewController()
navigationController?.viewControllers = [optionsVC]
And in your OptionsViewController:
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.isTranslucent = false
navigationItem.title = "Your Title"
}
first you need to set UINavigationBar color and text color .
try this in didFinishLaunchingWithOptions.
let barAppearance = UINavigationBar.appearance()
barAppearance.barTintColor = UIColor.blue
barAppearance.tintColor = UIColor.white
barAppearance.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
if you want to remove the string after backbutton
add these too
let barItemAppearace = UIBarButtonItem.appearance()
barItemAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
And just set your title in viewDidLoad() or
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.title = "Your Title"
}
Just add the below line to set the title for navigation item
self.title = "Title 1"
Using Objective-C I also had the problem that it didn't show me the title but going to the next Scene the title appeared next to the back button.
I don't really know why, but I solved it by programming the relative ViewController of that Scene in Swift instead of Objective-C.
After that, it was enough to use the command:
self.title = "My Title"
and so I was able to write what I want programmatically, using if-statement or other methodologies.
Maybe this could be useful to someone who has this problem with Objective-C.
All you need is initialisation navigationItem.title by string containing any printable symbols in viewDidLoad function. String like "" or " " will not worked.
Like this:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "."
}
Then code navigationItem.title = "Any string" will work well anywhere in your ViewController.
This bag was fixed in iOs 16.2

UINavigationBar does not extend behind status bar

I feel like I'm completely overlooking something, since this is so basic.
In a completely bare bones setup:
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
let rootController = MainViewController()
rootNavigationController = UINavigationController(rootViewController: rootController)
window.rootViewController = rootNavigationController;
window.makeKeyAndVisible()
// Appearance
UINavigationBar.appearance().barTintColor = UIColor.DailyRate.blueColor
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UINavigationBar.appearance().opaque = false
I get a navigation bar that does not extend behind the status, which should be default behavior.
I just tried and I got proper result. Please find my complete code. I can not find few things in your code(I don't get what you mean by DailyRate) , remaining things are same as your code.
var window: UIWindow?
var rootNavigationController : UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window!.backgroundColor = UIColor.whiteColor()
let rootController = ViewController()
rootNavigationController = UINavigationController(rootViewController: rootController)
window!.rootViewController = rootNavigationController;
window!.makeKeyAndVisible()
// Appearance
UINavigationBar.appearance().barTintColor = UIColor.blueColor()
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UINavigationBar.appearance().opaque = false
// Override point for customization after application launch.
return true
}
And the result is in following attachment.
I encountered this problem because I was removing the built-in line border at the bottom of the navigation bar, like this:
if let navigationBar = self.navigationController?.navigationBar {
navigationBar.shadowImage = UIImage()
navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
navigationBar.backgroundColor = UIColor.redColor()
}
I was doing the above code inside my view controller's viewWillAppear because some of my VCs have other colors for the navigation bar and I don't want to modify the universal appearance.
The solution was to just create a 1 pt x 1 pt image with the color I want and use it instead of a new empty UIImage instance, like this:
if let navigationBar = self.navigationController?.navigationBar {
let colorImage = UIImage.imageWithColor(self.category.color)
navigationBar.shadowImage = colorImage
navigationBar.setBackgroundImage(colorImage, forBarMetrics: .Default)
navigationBar.tintColor = UIColor.whiteColor()
}
imageWithColor is a function I defined in an extension to UIImage:
class func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRectMake(0, 0, 1, 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
It turned out that it was a timing issue.
The root hierarchy was actually set up in the initializer of a separate class called UIManager. However, this class was initialized at the same time as the AppDelegate
var uiManager = UIManager()
and not in application(_, didFinishLaunchingWithOptions _) method, thus creating this weird scenario.
So all I did was
var uiManager: UIManager?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
uiManager = UIManager()
}
And now everything is back to normal.
Thanks to #govindarao-kondala for planting the right idea in my head!

Resources