I'm working on rebuilding my app programmatically instead of using storyboards and running into issues with my UINavigationController. When the app first loads the root ViewController, the bar containing "carrier" and the time is a light version of the color that I've assigned and the search bar exists instead of a normal navigation bar.
When I tap/click a cell on my UITableView to get to the details screen then go back, my UINavigationBar appears correctly but the search bar disappears. I'm sure I've confused something but I'm not sure where I messed up or if I am missing something entirely. Any and all help would be greatly appreciated. I've also included an image as an example of whats going on.
Nonexistant UINavigationController > Detail screen > Back.gif
I have setup a TabBarController in the SceneDelegate SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// MARK: - Properties
var window: UIWindow?
let tabBarDelegate = TabBarDelegate()
let userAuthToken = UserDefaults.standard.string(forKey: "token")
let userKeyToken = UserDefaults.standard.string(forKey: "key")
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Disable dark mode
if #available(iOS 13.0, *) {
window?.overrideUserInterfaceStyle = .light
}
// IF USER IS LOGGED IN
if let windowScene = (scene as? UIWindowScene) {
if let _ = userAuthToken {
self.window = UIWindow(windowScene: windowScene)
// CREATE TAB BAR //
let tabController = UITabBarController()
tabController.tabBar.backgroundColor = .white
// Instantiate the storyboards
let cannabisStoryboard = UIStoryboard(name: "Cannabis", bundle: nil)
let profileStoryboard = UIStoryboard(name: "Profile", bundle: nil)
// Instantiate the view controllers to storyboards
let cannabisVC = cannabisStoryboard.instantiateViewController(withIdentifier: "Cannabis") as! CannabisViewController
let profileVC = profileStoryboard.instantiateViewController(withIdentifier: "Profile") as! ProfileViewController
// Displays the items in below order in tab bar
let vcData: [(UIViewController, UIImage, UIImage)] = [
(cannabisVC, UIImage(named: "Cannabis_icon")!, UIImage(named: "Cannabis_icon_selected")!),
(profileVC, UIImage(named: "Profile_icon")!, UIImage(named: "Profile_icon_selected")!),
]
let vcs = vcData.map { (vc, defaultImage, selectedImage) -> UINavigationController in
let nav = UINavigationController(rootViewController: vc)
nav.tabBarItem.image = defaultImage
nav.tabBarItem.selectedImage = selectedImage
return nav
}
// Assign to tab bar controller
tabController.viewControllers = vcs
tabController.tabBar.isTranslucent = false
tabController.delegate = tabBarDelegate
// Disables rendering for tab bar images
if let items = tabController.tabBar.items {
for item in items {
if let image = item.image {
item.image = image.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
if let selectedImage = item.selectedImage {
item.selectedImage = selectedImage.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
// Hides title
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
// Customize Navigation bar
UINavigationBar.appearance().backgroundColor = UIColor(rgb: 0x00ffcc)
// Disable dark mode
window!.overrideUserInterfaceStyle = .light
window?.rootViewController = tabController
window?.makeKeyAndVisible()
} else {
// let loginStoryboard = UIStoryboard(name: "Login", bundle: nil)
// let loginViewController = loginStoryboard.instantiateViewController(withIdentifier: "Login") as! LoginViewController
// Disable dark mode
window!.overrideUserInterfaceStyle = .light
// window?.rootViewController = loginViewController
window?.rootViewController = LoginViewController()
window?.makeKeyAndVisible()
}
window?.windowScene = windowScene
}
}
Root ViewController (stripped down to relevant info)
class CannabisViewController: UIViewController {
// MARK:- Outlets
let tableView = UITableView()
// MARK:- Properties
var cannabisDetailViewController: CannabisDetailsViewController? = nil
// Search
let searchController = UISearchController(searchResultsController: nil)
var isSearchBarEmpty: Bool { return searchController.searchBar.text?.isEmpty ?? true }
var filteredStrains = [Cannabis]()
var isFiltering: Bool { return searchController.isActive && !isSearchBarEmpty }
// MARK: - ViewWillLayoutSubViews
override func viewWillLayoutSubviews() {
let navigationBar: UINavigationBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 44))
// navigationController?.setViewControllers([CannabisViewController()], animated: true)
self.view.addSubview(navigationBar)
}
// MARK: - ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
configurePage()
// MARK: Self sizing table view cell
tableView.estimatedRowHeight = CGFloat(88.0)
tableView.rowHeight = UITableView.automaticDimension
// MARK: DataSource/Delegate
tableView.dataSource = self
tableView.delegate = self
// Removes default lines from table views
tableView.tableFooterView = UIView()
tableView.separatorStyle = .none
// MARK: Navigation: logo in center
let logoHeader = UIImageView(image: UIImage(named: "logoHeader"))
self.navigationItem.titleView = logoHeader
// MARK: API
getCannabisList()
// MARK: Search bar controller
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search for a strain!"
navigationItem.searchController = searchController
definesPresentationContext = true
}
// Configure TableView
func configurePage() {
// Configure Tableview
view.addSubview(tableView)
tableView.anchor(top: view.topAnchor, left: view.leftAnchor,
bottom: view.bottomAnchor, right: view.rightAnchor)
}
}
SearchController (Still in the root ViewController)
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let userSearch = searchBar.text!.trimmingCharacters(in: .whitespaces)
search(searchText: userSearch)
}
I can see a couple of issues here, but first to initialize your window in the SceneDelegate you would use the UIWindowScene
:
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
self.window = window
let rootViewController = RootViewController()
window.rootViewController = UINavigationController(rootViewController: rootViewController)
window.makeKeyAndVisible()
}
If you want to disable Dark Mode globally in your app, simply add a key UIUserInterfaceStyle
to your Info.plist
and set its value to Dark
(or Light
). By doing this, you won't need to update each view controller because it will overwrite the global app default style. I highly encourage you to add support for Dark Mode though!
If you want to change your navigation bar appearance:
if #available(iOS 13.0, *) {
let navigationAppearance = UINavigationBarAppearance()
navigationAppearance.configureWithOpaqueBackground()
navigationAppearance.backgroundColor = .white
navigationAppearance.titleTextAttributes = // ...
navigationAppearance.largeTitleTextAttributes = // ...
UINavigationBar.appearance().tintColor = .systemBlue
UINavigationBar.appearance().barTintColor = .white
UINavigationBar.appearance().standardAppearance = navigationAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationAppearance
} else {
UINavigationBar.appearance().backgroundColor = .white
UINavigationBar.appearance().barTintColor = .white
UINavigationBar.appearance().tintColor = .systemBlue
UINavigationBar.appearance().titleTextAttributes = // ...
UINavigationBar.appearance().largeTitleTextAttributes = // ...
}
I made a minimal project for you to see a working example with the search bar, in which the flickering of the status bar doesn't happen, and the UISearchBar
's hide/show animation works properly when pushing/popping your DetailViewController
:
RootViewController:
import UIKit
class RootViewController: UIViewController {
private let reuseIdentifier = "reuseIdentifier"
lazy var tableView: UITableView = {
$0.delegate = self
$0.dataSource = self
$0.register(UITableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
return $0
}(UITableView(frame: .zero, style: .grouped))
private lazy var searchController: UISearchController = {
$0.searchResultsUpdater = self
$0.delegate = self
$0.searchBar.delegate = self
$0.obscuresBackgroundDuringPresentation = false
$0.hidesNavigationBarDuringPresentation = false
$0.searchBar.backgroundColor = .white
$0.searchBar.tintColor = .systemBlue
return $0
}(UISearchController(searchResultsController: nil))
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupConstraints()
}
func setupViews() {
title = "Source"
view.backgroundColor = .white
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
view.addSubview(tableView)
// ...
}
func setupConstraints() {
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
}
extension RootViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailViewController = DetailViewController()
navigationController?.pushViewController(detailViewController, animated: true)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
}
extension RootViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
cell.textLabel?.text = "Cell at indexPath \(indexPath)"
return cell
}
}
extension RootViewController: UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {}
func updateSearchResults(for searchController: UISearchController) {}
}
DetailViewController:
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Detail"
view.backgroundColor = .systemGray6
}
}