Question:
How do I change the color/theme/style of the UISearchController in tvOS?
( Would appear I need to set UIStatusBarStyle some way, since the preferredStatusBarStyle override does not exist in tvOS for UISearchController)
Description:
Method called in my AppDelegate to programmatically create a searchNavigationController with on top a UISearchController and on bottom my custom UITableViewController called "searchResultsController" (SearchViewController)
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
Image of the visual representation of the above method:
What I've tried:
I've attempted to change the style via different approaches and none of them gave the desired outcome.
This has no effect on the searchBar:
searchController.searchBar.searchBarStyle = .minimal //or .prominent or .default
This only makes the searchBar (actual input area black.. with black text..):
searchController.searchBar.backgroundColor = .black
This only makes the whole background view of the UISearchController black. Everything is black and thus the keyboard can not be seen.
searchController.view.backgroundColor = .black
The ideal solution would be a theme change from the standard .default to something .dark. Because not only does the background color have to change but the borders and text colors must too.
Attempted to implement this solution:
//UISearchController
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
However, this override does not seem to exist for tvOS. Any reason why?
For all those wanting to achieve this on tvOS with a TabBarController and storyboard, what I did was add a container view to my view controller and added the tab bar controller as a child.
class SearchController: UIViewController {
#IBOutlet var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController.init(searchResultsController: nil)
searchController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
searchController.view.frame = containerView.bounds
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = .black
searchNavigationController.tabBarItem.title = "Search"
containerView.addSubview(searchNavigationController.view)
searchNavigationController.didMove(toParent: self)
}
}
There is a work around to this problem, however I don't really understand why they don't keep the same system as for iOS. Anyway, the main things to do to change the SearchBar style to black is to set the keyboardAppearance to dark, the searchController's view's background color and to set the searchBar's background color to white.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
guard let win = window else {
return true
}
if let tabController = win.rootViewController as? UITabBarController {
tabController.viewControllers?.append(configueSearchController())
}
win.backgroundColor = UIColor.white
win.makeKeyAndVisible()
return true
}
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
searchController.view.backgroundColor = UIColor.black
searchController.searchBar.keyboardAppearance = UIKeyboardAppearance.dark
searchController.searchBar.tintColor = UIColor.black
searchController.searchBar.backgroundColor = UIColor.white
searchController.hidesNavigationBarDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.sizeToFit()
//searchResultsController.tableView.tableHeaderView = searchController.searchBar
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = .black
searchNavigationController.tabBarItem.title = "Search"
return searchNavigationController
}
I am not an expert on tvOS, but here's what I've found.
I started with the UIKit Catalog: Creating and Customizing UIKit Controls example code from Apple. The first thing that I noticed is that the code you supplied above instantiates the searchResultsController as a SearchViewController, whereas Apple's example code instantiates the searchResultsController as a SearchResultsViewController. I assume you meant to do this.
After playing around with the example code, I was able to make very limited style changes. Here is a screenshot of the closest I have come to what it seems you are trying to achieve.
And here is the code that affected that change.
func packagedSearchController() -> UIViewController {
// Load a `SearchResultsViewController` from its storyboard.
let storyboard = UIStoryboard(name: "ViewControllerSamples", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchResultsViewController.storyboardIdentifier) as? SearchResultsViewController else {
fatalError("Unable to instatiate a SearchResultsViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. iceland)", comment: "")
//** This is the part you are interested in **//
searchResultsController.view.backgroundColor = .black
searchController.view.backgroundColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
searchController.searchBar.backgroundColor = .white
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
Unfortunately, I was unable to set the SearchController's view to .black without loosing the keyboard in the darkness. I looked and looked for a solution to the disappearing keyboard problem thinking that there must be a way to style the keyboard, or at least modify the selection state, all to no avail. My disappointment culminated in this statement from the App Programming Guide for tvOS: Designing the Keyboard Experience;
An inline keyboard displays all of the possible inputs on a single line. Use UISearchController to create a keyboard that can be fully integrated with third-party content. However, there are very few customization options when you use UISearchController. You can not access the text field itself, customize the traits, or add input accessories.
I suspect that if you could get a reference to that keyboard, you may be able to affect some change. That being said, I have not been able to find a real solution to date. Maybe you could attach a custom inputAccessoryViewController to the SearchController and effect a change that way?
Sorry I couldn't be more helpful.
Related
I am using MVVM+C pattern to build my app. Currently I am facing a problem with changing the native back button title and image of navigation bar to the custom image without the title. I've tried a lots of solutions what I was able to find, but nothing set the different title or even an image. I've ended up with this code in AppDelegate.swift:
let navigationController: UINavigationController = .init()
if #available(iOS 13.0, *) {
let appearence = UINavigationBarAppearance()
appearence.configureWithOpaqueBackground()
appearence.backgroundColor = .backgroundColor
appearence.shadowColor = nil
appearence.shadowImage = nil
navigationController.navigationBar.standardAppearance = appearence
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
} else {
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.barTintColor = .backgroundColor
navigationController.navigationBar.shadowImage = nil
navigationController.navigationBar.shadowColor = nil
}
// This code is not working at all, always get "Back" as a default with default image =====
let backButtonBackgroundImage = UIImage(named: "backButton")
navigationController.navigationBar.backIndicatorImage = backButtonBackgroundImage
navigationController.navigationBar.backIndicatorTransitionMaskImage = backButtonBackgroundImage
let backBarButtton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationController.navigationItem.backBarButtonItem = backBarButtton
// =========
navigationController.navigationBar.tintColor = .primary
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
Also, I've followed the official documentation but without any success. As default I've set the navigation bar as hidden (because is not needed for multiple times) and I am showing it in ViewWillAppear and hiding in ViewWillDisappear methods.
Is there someone who has an idea of what's going on? Thanks!
The result of this code:
Expected result:
This is what I get with the new code:
SOLUTION:
After using code from Scott I was able to change the image and look of the navigation bar but I lost the ability to swipe back. After adding this code to the UINavigationBar extension I was able to get it back:
extension UINavigationController: UIGestureRecognizerDelegate {
#objc func goBack(sender: Any?) {
self.popViewController(animated: true)
}
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
Below is some Playground code that shows a UINavigationController with a custom back button that is an image.
Note that what it does is hides the system provided back button, then substitutes another button that still performs the "back" action but on a custom UINavigationController.
There may be a more efficient way to duplicate the functionality of "back" that doesn't involve a custom class and a custom target-action setup, but I couldn't find one quickly so finding that solution can be left as an exercise for the reader.
import UIKit
import SwiftUI
import PlaygroundSupport
NSSetUncaughtExceptionHandler { error in
debugPrint(error)
}
class MyNavController : UINavigationController {
#objc func goBack(sender: Any?) {
self.popViewController(animated: true)
}
}
let navDestination1 = UIViewController()
navDestination1.navigationItem.title = "Destination 1"
let navigationController = MyNavController(rootViewController: navDestination1)
if #available(iOS 13.0, *) {
let appearence = UINavigationBarAppearance()
appearence.configureWithOpaqueBackground()
appearence.backgroundColor = .purple
appearence.shadowColor = nil
appearence.shadowImage = nil
navigationController.navigationBar.standardAppearance = appearence
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
} else {
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.barTintColor = .purple
navigationController.navigationBar.shadowImage = nil
}
let navDestination2 = UITableViewController()
navDestination2.navigationItem.title = "Destination 2"
navDestination2.navigationItem.hidesBackButton = true
navDestination2.navigationItem.leftBarButtonItem = UIBarButtonItem(
image: UIImage(systemName: "multiply.circle.fill"),
style: UIBarButtonItem.Style.done,
target: navigationController,
action: #selector(MyNavController.goBack))
navigationController.pushViewController(navDestination2, animated: true)
navigationController.view.bounds = CGRect(x: 0,y: 0,width: 320,height: 480)
PlaygroundSupport.PlaygroundPage.current.liveView = navigationController
Question:
How do I set the "focused" state of a UISearchBar text color from the default white, to black?
I created the search bar programmatically, see end of post for that code.
Description:
Previously I was using this extension to set the search bar color:
public extension UISearchBar {
public func setTextColor(color: UIColor) {
let svs = subviews.flatMap { $0.subviews }
guard let tf = (svs.filter { $0 is UITextField }).first as? UITextField else { return }
tf.textColor = color
}
}
Which worked great and is still working. However, search bars seem to be "focusable" in a sense. If they are focused, then the text color switches to white (which is the same as the search bar background color) making the text invisible.
Under my search bar I have a table view, where I populate it with data regarding what the person was searching.
When I focus any of the table cells, the UISearchBar text color goes to the black I had originally specified.
Programmatically created SearchBar
Called in AppDelegate.swift
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Search", bundle: nil)
guard let searchResultsController = storyboard.instantiateViewController(withIdentifier: SearchViewController.storyboardIdentifier) as? SearchViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
searchController.view.backgroundColor = Constants.Color.backgroundcolor
searchController.searchBar.keyboardAppearance = UIKeyboardAppearance.dark
searchController.searchBar.tintColor = Constants.Color.backgroundcolor
searchController.searchBar.backgroundColor = Constants.Color.backgroundColorSearch
searchController.searchBar.setTextColor(color: .black)
searchController.hidesNavigationBarDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.sizeToFit()
//searchResultsController.tableView.tableHeaderView = searchController.searchBar
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer: UISearchContainerViewController = UISearchContainerViewController(searchController: searchController)
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
searchNavigationController.navigationBar.isTranslucent = true
searchNavigationController.navigationBar.tintColor = Constants.Color.backgroundcolor
searchNavigationController.tabBarItem.title = "Search"
return searchNavigationController
}
In my app have two UIViewControllers (first, where user choose character, and second, where characters info is shown).
Trying do everything from code. So my code:
AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
guard let window = self.window else {
return false
}
UIApplication.shared.statusBarStyle = .lightContent
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
window.rootViewController = navigationController
window.makeKeyAndVisible()
return true
}
ViewController: UIViewController
...
func buttonPressed(sender: UIButton) {
guard let typeName = sender.title(for: .normal), let unitType = UnitType(rawValue: typeName) else {
return
}
let unitViewController = UnitViewController(unitType: unitType)
let navigationController = UINavigationController(rootViewController: unitViewController)
self.present(navigationController, animated: true, completion: nil)
}
And UnitViewController: UIViewController (where chosen character's info is shown):
...
fileprivate func setupNavigationBar() {
let backButton = UIBarButtonItem(title: "<", style: .plain, target: self, action: #selector(backToViewController(sender:)))
self.navigationItem.setLeftBarButton(backButton, animated: true)
self.navigationItem.titleView?.backgroundColor = AppColor.Black.color
self.navigationItem.titleView?.tintColor = .white
self.navigationItem.title = "\(self.unitType.rawValue)"
}
func backToViewController(sender: AnyObject) {
self.dismiss(animated: true) {
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
self.present(navigationController, animated: true, completion: nil)
}
}
So I have two screens:
Well, and I have some questions:
1. Getting
2016-12-02 05:48:21.855 The API Awakens[16402:551729] Warning: Attempt
to present on
whose view is not
in the window hierarchy!
warning when press 'back' button in UnitViewController. What I am doing wrong?
Gray background color and black color of NavigationBar. How change it to black and white?
How get 'system' back button for UIBarButtonItem, not just .plain with title "<"? So my UnitViewController navigation bar should looks like this:
Any help will be greatly appreciated!
UPDATE 1:
1. Warning gone, thanks to #dip
2. Made navigation bar dark, thanks to #aznelite89.
But, there is my code in AppDelegate.swift:
UINavigationBar.appearance().barTintColor = AppColor.Black.color
AppColor.Black is exact same color I'm using for background in ViewController, but that how it looks now:
Looks like alpha of NavigationBar is not 1.0...
UPDATE 2:
Different between color of NavigationBar and color I've used is 13 in RGB values, so I've hacked it setting color of NavigationBar with RGB value less by 13 than original color... It's ok now
Try this :-
let viewController = ViewController()
viewController.modalPresentationStyle = .overCurrentContext
window?.rootViewController?.present(viewController, animated: true, completion: nil)
Add this in app delegate.swift :-
UINavigationBar.appearance().barTintColor = UIColor.black
You may change these attributes on your storyboard attribute inspector
I am using UISearchController (not UISearchDisplayController), i wanted to show a separate view when search is active. I dont want to use the current view.
Tried with self.searchController = UISearchController(searchResultsController: self.resultsController ). But this doesn't show anything.
Even tried this
func presentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchResultsController?.view.isHidden = false
}
}
func didPresentSearchController(_ searchController: UISearchController) {
searchController.searchResultsController?.view.isHidden = false
}
My default table data before search is active is as below, i want this table to hide and show separate view and pass the typed keyword to that new view. How can i achieve this?
As there is no direct segue to viewController, we need to explicitly refer the viewController we wanted to use.
I have used the below code and it loads my new controller when my searchBar is active.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
self.resultsController = storyboard.instantiateViewController(withIdentifier: "searchResults") as! SearchResultsTableViewController
self.searchController = UISearchController(searchResultsController: self.resultsController )
Let me know if any better approach is available.
I have created a navigation bar with UISearchController.
Below is my code:
#IBAction func showSearchController(sender: AnyObject) {
let searchController = UISearchController(searchResultsController: searchResultController)
searchController.searchBar.delegate = self
/*
searchController.searchBar.placeholder = "hhhhhhhhhhh"
searchController.navigationItem.title = "hhhhhhhhhhh"
searchController.navigationController?.navigationBar.topItem?.title = "hhhhhhhh" -->i try these codes for to change*/
self.presentViewController(searchController, animated: true, completion: nil)
}
But I can't change the text.
var searchTextField = searchController.searchBar.valueForKey("_searchField") as! UITextField
searchTextField.placeholder = "Your Custom Text"
Use this to change your place holder text