I created a new project. I have a NavigationController. In the RootViewController I have a containerView with a table and just one cell. If I click on the cell I push a new UIViewController. So my Main.storyboard looks like this:
What I want:
I want first to have a white NavigationBartitle. Then pushing to secondVC I want to change the NavigationBarTitle to black. Then clicking on back the color should change back to a white title.
What I've did:
I did a custom NavigationViewController. There I was changing the func willShow viewController. In this I wrote the titleColor should change depending on which screen the navigationController changes to.
My code:
import UIKit
class SettingsNavigationViewController: UINavigationController {}
// MARK: - Controller Lifecycle
extension SettingsNavigationViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override var preferredStatusBarStyle: UIStatusBarStyle {
guard let child = self.childViewControllers.last else {
return .lightContent
}
return child is ViewController ? .lightContent : .default
}
}
// MARK: - NavigationController Delegate Implementation
extension SettingsNavigationViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let isSettingsContainer = viewController is ViewController
let backgroundColor = isSettingsContainer ? UIColor.cyan : UIColor.white
let titleColor = isSettingsContainer ? UIColor.white : UIColor.black
let image = isSettingsContainer ? UIImage() : nil
navigationController.navigationBar.shadowImage = image
navigationController.navigationBar.setBackgroundImage(image, for: .default)
navigationController.transitionCoordinator?.animate(alongsideTransition: { (context) in
navigationController.navigationBar.tintColor = titleColor
navigationController.navigationBar.barTintColor = backgroundColor
navigationController.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor : titleColor]
})
}
}
What happened instead:
If I change the screen to seconndVC the navBarTitleColor changes black. If I click on back it stays black. But it should change to white.
The complete project I also uploaded to github: https://github.com/Sonius94/stackNaviTitle
The possible solution could be, add a SecondViewController and in your SecondViewController you should implement the following :
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController: parent)
if parent == nil {
// Add your navigation bar appearance for FirstViewController
}
}
Related
I have a requirement of light content in status bar with black background, however some of the screen needs black status bar content with white background, hence I've kept View controller-based status bar appearance to YES in info.plist to adopt status bar style based on view controllers requirement.
My problem is whenever I present SFSafariViewController from any view controller it is taking black status bar content and white background by default i.e status bar style is .default everytime.
I tried overriding preferredStatusBarStyle in SFSafariViewController subclass and no look so far.
Below is my code
import UIKit
import SafariServices
extension SFSafariViewController {
override open var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
extension UINavigationController {
open override var preferredStatusBarStyle: UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle ?? .lightContent
}
}
class MyViewController: UIViewController, SFSafariViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.barTintColor = UIColor.lightGray
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
#IBAction func presentSafari(sender: AnyObject) {
let safari = SFSafariViewController(url: URL(string: "https://www.google.com/")!)
safari.delegate = self
present(safari, animated: true) {
}
}
// MARK: - SFSafariViewControllerDelegate
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated: true, completion: nil)
}
}
Set modalPresentationCapturesStatusBarAppearance to takes over control of status bar appearance from the presenting view controller.
#IBAction func presentSafari(sender: AnyObject) {
let safari = SFSafariViewController(url: URL(string: "https://www.google.com/")!)
safari.delegate = self
safari.modalPresentationCapturesStatusBarAppearance = true
if #available(iOS 10.0, *) {
safari.preferredBarTintColor = .yellow
} else {
// Fallback on earlier versions
safari.view.tintColor = .yellow
}
present(safari, animated: true) {
}
}
extension SFSafariViewController {
override open var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
When you present a view controller by calling the present(_:animated:completion:) method, status bar appearance control is transferred from the presenting to the presented view controller only if the presented controller's modalPresentationStyle value is UIModalPresentationStyle.fullScreen. By setting this property to true, you specify the presented view controller controls status bar appearance, even though presented non-fullscreen.
Output: Screenshot
iOS 10.0+
preferredBarTintColor
The color to tint the background of the navigation bar and the toolbar.
Ref: https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/2274394-preferredbartintcolor
Since your View controller-based status bar appearance is set to YES in Info.plist, you will need to apply the color information on preferredBarTintColor, like so:
let safari = SFSafariViewController(url: URL(string: "https://google.com")!)
//This:
safari.preferredBarTintColor = .black
present(safari, animated: true, completion: nil)
And... no need for the following:
extension SFSafariViewController {
open override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}
}
I added 2 tabBar items from storyboard and one UITabBarItem - Menu programmatically. I am successfully able to open the controllers corresponding to tabBarItems which I created using storyboard. However, when I click on "Menu" a blank black screen appears,
#objc public class MainScreenTabsController : UITabBarController {
public override func viewDidLoad() {
super.viewDidLoad()
let tabController = MyViewController()
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return true;
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
I followed couple of tutorials for adding tab bar item but all of them had the code I wrote. Am I missing out something very basic?
EDIT:
Class for Menu Controller
#objc public class MyViewController:UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
Your app is doing exactly what your code is telling it to do. You are creating an instance of MyViewController and adding it to the UITabBarController's array of View Controllers.
Your MyViewController class file simply defines a blank, black view.
I'm guessing you created a ViewController in your Storyboard that you want to use as MyViewController? If so, you need to instantiate that from the storyboard.
When you're editing your storyboard, assign the MyViewController class to the VC you want to use, and also give it a Storyboard ID - such as MyVC. Then, edit your viewDidLoad function to this:
public override func viewDidLoad() {
super.viewDidLoad()
// wrong way
// let tabController = MyViewController()
if let tabController = storyboard?.instantiateViewController(withIdentifier: "MyVC") as? MyViewController {
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
}
Since you're creating the ViewController programatically ie. without nib/storyboard, you are responsible for instantiating a UIView object and setting the view property of the view controller. to do that implement the loadView method and assign the view object to view property of the viewController. then you can add custom views to the view object, check the code below.
class MyViewController: UIViewController {
override func loadView() {
// super.loadView() // DO NOT CALL SUPER
//create view
view = UIView()
view.backgroundColor = UIColor.white
//Add a custom view with red color
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = UIColor.red
view.addSubview(customView)
NSLayoutConstraint.activate(
[customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
customView.topAnchor.constraint(equalTo: view.topAnchor),
customView.bottomAnchor.constraint(equalTo: view.bottomAnchor)]
)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
It would be good to use Storyboard/Nib for this purpose as you can easily configure custom views/controls using the autolayout in interface builder rather than doing it programmatically. :)
Edit:
if your'e using storyboard then instantiate view controller like given in below code
class MainScreenTabsController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tabController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainViewController") as! MainViewController //if using storyboard
let icon = UITabBarItem(title: "Menu", UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options")))
tabController.tabBarItem = icon
var controllers = self.viewControllers
controllers?.append(tabController)
self.setViewControllers(controllers!, animated: true)
}
}
I'm using 2 different bar tint colors at UINavigationBar in different views. I'n changing color with that method in both views:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.barTintColor = COLOR
}
When I tap on back button color is not changed smoothly (you can see blink on last second).
But everything is okay if just swipe view back instead of tapping on back button.
How to make smooth transition in both situations?
To achieve this kind of animation you should use UIViewControllerTransitionCoordinator as Apple documentation say it is :
An object that adopts the UIViewControllerTransitionCoordinator protocol provides support for animations associated with a view controller transition.(...)
So every UIViewController has own transitionController. To get this you should call in the UIViewControllerClass :
self.transitionCoordinator()
From documentation:
Returns the active transition coordinator object.
So to get the result that you want you should implement animateAlongsideTransition method in viewController transitionCoordinatior. Animation works when you click backButton and swipe to back.
Example :
First Controller :
class ViewControllerA: UIViewController {
override func loadView() {
super.loadView()
title = "A"
view.backgroundColor = .white
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "NEXT", style: .plain, target: self, action: #selector(self.showController))
setColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
private func animate() {
guard let coordinator = self.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: {
[weak self] context in
self?.setColors()
}, completion: nil)
}
private func setColors() {
navigationController?.navigationBar.tintColor = .black
navigationController?.navigationBar.barTintColor = .red
}
}
Second Controller:
class ViewControllerB : UIViewController {
override func loadView() {
super.loadView()
title = "B"
view.backgroundColor = .white
setColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
override func willMove(toParentViewController parent: UIViewController?) { // tricky part in iOS 10
navigationController?.navigationBar.barTintColor = .red //previous color
super.willMove(toParentViewController: parent)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.navigationBar.barTintColor = .blue
}
private func animate() {
guard let coordinator = self.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: {
[weak self] context in
self?.setColors()
}, completion: nil)
}
private func setColors(){
navigationController?.navigationBar.tintColor = .black
navigationController?.navigationBar.barTintColor = .blue
}
}
UPDATE iOS 10
In the iOS 10 the tricky part is to add the willMoveTo(parentViewController parent: UIViewController?) in the second ViewController. And set the navigationBar tintColor to the color value of previous controller. Also, in viewDidAppear method in second ViewControler set the navigationBar.tintColor to the color from second viewController.
Check out my example project on github
I've coded final solution that looks most comfortable to use (don't need to use a lot of overrides in own view controllers). It works perfectly at iOS 10 and easy adoptable for own purposes.
GitHub
You can check GitHub Gist for full class code and more detailed guide, I won't post full code here because Stackoverflow is not intended for storing a lot of code.
Usage
Download Swift file for GitHub. To make it work just use ColorableNavigationController instead of UINavigationController and adopt needed child view controllers to NavigationBarColorable protocol.
Example:
class ViewControllerA: UIViewController, NavigationBarColorable {
public var navigationBarTintColor: UIColor? { return UIColor.blue }
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Push", style: .plain, target: self, action: #selector(self.showController))
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
}
class ViewControllerB: UIViewController, NavigationBarColorable {
public var navigationBarTintColor: UIColor? { return UIColor.red }
}
let navigationController = ColorableNavigationController(rootViewController: ViewControllerA())
This worked for me:
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
navigationController?.navigationBar.barTintColor = previous view controller's navigation bar color
}
I am just wondering. For the same purpose I use UINavigationControllerDelegate. In navigationController(_:willShow:) I start the animation using transitionCoordinator?.animate(alongsideTransition:completion:). It works great when pushing new controllers, however pop doesn't.
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let dst = viewController as! ViewController
guard animated else {
navigationController.navigationBar.barTintColor = dst.navigationBarColor
navigationController.navigationBar.tintColor = dst.tintColor
navigationController.navigationBar.barStyle = dst.barStyle
return
}
navigationController.transitionCoordinator?.animate(alongsideTransition: { context in
navigationController.navigationBar.barTintColor = dst.navigationBarColor
navigationController.navigationBar.tintColor = dst.tintColor
navigationController.navigationBar.barStyle = dst.barStyle
}, completion: { context in
if context.isCancelled {
let source = context.viewController(forKey: UITransitionContextViewControllerKey.from) as! ViewController
navigationController.navigationBar.barTintColor = source.navigationBarColor
navigationController.navigationBar.tintColor = source.tintColor
navigationController.navigationBar.barStyle = source.barStyle
}
})
Do you see any reason why it should work with pushes but not pops?
I being searching this issue in stack overflow but couldn't get an exact answer to the issue, i being stuck in it for long time.
Mine issue is i'm trying to push a TestViewController through navigation controller. when i click the button the TestViewController is being load with navigation bar and the UIScreen is in black colour.
Code of TestViewController
import UIKit
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
navigationItem.title = "Test page"
}
}
Code of Button
#IBAction func secondButtonClicked(sender: AnyObject) {
buttonPressedNumber = "Two Clicked"
buttonTextColor = UIColor.magentaColor()
let a = TestViewController()
let b:UIViewController = a as UIViewController
navigationController?.pushViewController(b, animated: false)
}
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
self.navigationController?.pushViewController(resultViewController, animated: true)
You have not set the background color of your view.
The default color of the of UIWindow is Black. So if you have not set any other background colors in the stack they will all be transparent.
Not setting an appropriate background color for your UIViewController's view will also cause weird visuals during a transition.
let nextVC = self.storyboard?.instantiateViewControllerWithIdentifier("storyboardID") as! viewController
self.navigationController?.pushViewController(nextVC, animated: true)
not sure the background colour is set to white these days.
therefore, if view controller is created programmatically
in viewDidLoad/viewWillAppear for a white background colour
view.backgroundColor = UIColor.whiteColor()
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
I use this one to change status bar to light in all app. But now I need to change it in just one View Controller back to black. How can I do that?
Set View controller-based status bar appearance in your project.plist to NO
Use viewWillAppear and will viewWillDisappear to set and reset the statusBarStyle, while keeping a property with the previous statusBarStyle like this
let initialStatusBarStyle : UIStatusBarStyle
func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
initialStatusBarStyle = UIApplication.sharedApplication().statusBarStyle
UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: animated)
}
func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.sharedApplication().setStatusBarStyle(initialStatusBarStyle, animated: animated)
}
Xcode 8.1, Swift 3 Solution with #IBDesignable
This solution is a little bit different:
Subclass of UIViewController to centralize logic
No code for viewDidLoad or viewDidDisappear
Uses #IBDesignable so you can set your status bar color in the Attributes Inspector on the Storyboard
Step 1 - Setup Info.plist File
Step 2 - Subclass UIViewController
import UIKit
#IBDesignable
class DesignableViewController: UIViewController {
#IBInspectable var LightStatusBar: Bool = false
override var preferredStatusBarStyle: UIStatusBarStyle {
get {
if LightStatusBar {
return UIStatusBarStyle.lightContent
} else {
return UIStatusBarStyle.default
}
}
}
}
Step 3 - Inherit from DesignableViewController
Change the code for your ViewController(s) from:
class ViewController: UIViewController {
To:
class ViewController: DesignableViewController {
Step 4 - Set your preference on the Storyboard
Select the ViewControllers on the Storyboard and go to the Attributes Inspector:
Step 5 - Run project and test
In my project I setup a Tab Bar Controller with 2 View Controllers and switch back and forth between the two. Seems to work OK for me.
Solved:
Swift 3.1
Just using this code in View Controller:
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
Swift 3
Set View controller-based status bar appearance in your project.plist to NO
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.setStatusBarStyle(.default, animated: animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIApplication.shared.setStatusBarStyle(.lightContent, animated: animated)
}
An Objective-C answer:
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
}
let color = UIColor(red:0.00, green:0.60, blue:0.48,alpha:1.0)
UINavigationBar.appearance().tintColor = UIColor.blue
UINavigationBar.appearance().barTintColor = color
OR
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
In swit4 this working fine in my project based on navigation bar
let app = UIApplication.shared
let statusBarHeight: CGFloat = app.statusBarFrame.size.height
let statusbarView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: statusBarHeight))
statusbarView.backgroundColor = UIColor.red
view.addSubview(statusbarView)
You can set status bar color using below code and its working for me
self.navigationController?.SetStatusBar(StatusBarbackgroundColor: ThemeBackColor, StatusTextColor: .black)
Thanks
Happy Coding :)
Swift 5,iOS 14,UIKit
Step 1: Add Status bar style
Step 2: Change in Info.Plist
Add View controller-based status bar appearance key with NO value
Step 3: In your base view controller add this extension
extension UIViewController
{
func setStatusBarColor(){
if #available(iOS 13, *)
{
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let statusBarFrame = window?.windowScene?.statusBarManager?.statusBarFrame
let statusBar = UIView(frame: (statusBarFrame)!)
statusBar.backgroundColor = .orange
window?.addSubview(statusBar)
} else {
// ADD THE STATUS BAR AND SET A CUSTOM COLOR
let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as! UIView
if statusBar.responds(to:#selector(setter: UIView.backgroundColor)) {
statusBar.backgroundColor = .orange
}
UIApplication.shared.statusBarStyle = .lightContent
}
}
}
Step 4: In your view controller use this extension
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setStatusBarColor()
}
If you have derived your view controllers from a common view controller then, you can simply do it like this:
Step 1:
Add this key to your app's info.plist file.
Step 2:
override this in common view controller (or a ParentViewController).
override var preferredStatusBarStyle: UIStatusBarStyle {
if self is YourChildViewController {
return .lightContent
}
return .default
}
That's it! No more fancy things.