How to adjust sublayer on UINavigationController? - ios

In my Swift project, I added a sublayer to UINavigationController.
But after adding this GAGradientLayer, I can't see the navigation title text or back button.
The weird thing is that in the view hierarchy, the CAGradientLayer(which was added as sublayer) is behind the title and button.
I tried to reload navigationController layer with LayoutIfNeeded, setNeedsLayout or setNeedsDisplay but nothing worked.
And I also just tried to change the navigation title but it doesn't work.
(Actually The text of navigation title is loaded on the view controller behind, so I don't want to change this on this VC.)
So, How can I show my navigation title text and button with CAGradientlayer above?
Here's the screenshot
Here's the codes needed
import UIKit
import SnapKit
class BulletinBoardViewController: UIViewController {
// ...
var backgroundGradientLayer: CAGradientLayer?
let bulletinBoardView = BulletinBoardView()
// MARK: - Lifecycles
override func viewDidLoad() {
super.viewDidLoad()
setBulletinBoardView()
setCells()
}
override func viewWillAppear(_ animated: Bool) {
setupBackgroundLayer()
}
override func viewWillDisappear(_ animated: Bool) {
self.backgroundGradientLayer?.removeFromSuperlayer()
}
// MARK: - Helpers
func setupBackgroundLayer() {
DispatchQueue.main.async {
if let backgroundGradientLayer = self.backgroundGradientLayer {
backgroundGradientLayer.frame = CGRect(x: 0, y: -59, width: 500, height: 103)
self.navigationController?.navigationBar.layer.addSublayer(backgroundGradientLayer)
}
}
}
func setBulletinBoardView() {
self.view.addSubview(bulletinBoardView)
bulletinBoardView.snp.makeConstraints { make in
make.right.left.top.equalTo(self.view.safeAreaLayoutGuide)
make.bottom.equalTo(self.view)
}
}
// ...
}
The origin navigation controller setting is below
class MainPageViewController: UIViewController {
// ...
func setupNav() {
navigationController?.navigationBar.tintColor = .black
navigationItem.rightBarButtonItem = listButton
navigationItem.leftBarButtonItem = settingButton
let backBarButtonItem = UIBarButtonItem(title: "",
style: .plain,
target: self,
action: nil)
self.navigationItem.backBarButtonItem = backBarButtonItem
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .systemGray3
appearance.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: AppFontName.bold, size: 20)!]
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance =
navigationController?.navigationBar.standardAppearance
}
// ...
}

Related

How to change prompt color in Swift 5 iOS16

I am trying to change the color of the prompt in my navigation controller so that it is white not black for iOS16.
The following code changes the title but not the prompt. My code is:
import UIKit
class ParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.blue //UIColor.lincsNavBarBlueColor()
appearance.titleTextAttributes[NSAttributedString.Key.foregroundColor] = UIColor.white
navigationItem.standardAppearance = appearance
navigationItem.scrollEdgeAppearance = appearance
navigationItem.title = "Hello there"
navigationItem.prompt = "This is the prompt"
}
}
What do I need to add to change the prompt color? Thanks.
This seems like a bug and I doubt Apple will fix it.
I've worked around it by subclassing the UINavigationController and diving for the label.
#objc
final class NavigationControllerWithPrompt: UINavigationController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
changePromptColor()
}
private func changePromptColor() {
let promptView = navigationBar.subviews.first { view in
return String(describing: type(of: view)) == "_UINavigationBarModernPromptView"
}
let promptLabel = promptView?.subviews.compactMap{ $0 as? UILabel }.first
promptLabel?.textColor = UIColor.white
}
}

Navigation bar title truncated after relaunching application

Recently I getting e wired problem
When I run my application from XCode navigation title text is showing perfect.
But when I close the application and reluanch again text cuts off with
...
I tried the following questions but no luck.
Here is my BaseViewController
import UIKit
import SnapKit
open class BaseViewController: UIViewController {
public lazy var navigationShadowView: UIView = {
let view = UIView()
DispatchQueue.main.async {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.Blue10.cgColor, UIColor.Blue0.withAlphaComponent(0.0).cgColor]
gradientLayer.frame = view.bounds
view.layer.addSublayer(gradientLayer)
}
return view
}()
public override func viewDidLoad() {
loadDefaults()
setupUI()
}
}
extension BaseViewController {
private func loadDefaults() {
view.backgroundColor = .white
tabBarController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
// MARK: Navigation bar bottom shadow
view.addSubview(navigationShadowView)
navigationShadowView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.equalTo(view.snp.leading)
make.trailing.equalTo(view.snp.trailing)
make.height.equalTo(10)
}
}
#objc open func setupUI() {
}
}
I populate the view in viewcontroller like below
import UIKit
import CoreModule
import SnapKit
import Combine
class CustomerProjectListVC: BaseViewController {
lazy var refreshControl: UIRefreshControl = {
let refresh = UIRefreshControl()
refresh.addTarget(self, action: #selector(refreshProjects(_:)), for: .valueChanged)
return refresh
}()
lazy var jobsTableView: UITableView = {
let tableView = UITableView()
tableView.showsHorizontalScrollIndicator = false
tableView.showsVerticalScrollIndicator = false
tableView.separatorStyle = .none
tableView.rowHeight = 220
tableView.backgroundColor = .Blue0
tableView.addSubview(refreshControl)
tableView.register(ProjectListTableViewCell.self, forCellReuseIdentifier: ProjectListTableViewCell.identifier)
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
private let viewModel = CustomerProjectListViewModel()
private var subscription = Set<AnyCancellable>()
override func viewDidAppear(_ animated: Bool) {
tabBarController?.navigationItem.title = "Project List"
tabBarController?.navigationItem.rightBarButtonItem = nil
}
override func setupUI() {
view.addSubview(jobsTableView)
jobsTableView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.bottom.equalToSuperview()
}
populateData()
}
}
Here is the CustomeTabBarController
class CustomerTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
tabBar.backgroundColor = .white
viewControllers = [
createNavController(for: sideMenuController(), title: "Home", image: UIImage(named: .TabBarHome)!),
createNavController(for: ProfileVC(), title: "Profile", image: UIImage(named: .TabBarProfile)!),
createNavController(for: NewPostVC(), title: "Post", image: UIImage(named: .TabBarPost)!),
createNavController(for: CustomerProjectListVC(), title: "Chatbox", image: UIImage(named: .TabBarChatBox)!),
createNavController(for: HomeVC(), title: "Notification", image: UIImage(named: .TabBarNotification)!)
]
}
}
extension CustomerTabBarController {
fileprivate func createNavController(for rootViewController: UIViewController,
title: String,
image: UIImage) -> UIViewController {
rootViewController.tabBarItem.title = title
rootViewController.tabBarItem.image = image
return rootViewController
}
}
extension CustomerTabBarController {
private func configureSideMenu() {
SideMenuController.preferences.basic.menuWidth = UIScreen.main.bounds.width - 80
SideMenuController.preferences.basic.position = .above
SideMenuController.preferences.basic.direction = .right
}
private func sideMenuController() -> SideMenuController {
configureSideMenu()
return SideMenuController(contentViewController: HomeVC(), menuViewController: SideMenuVC())
}
}
And I am initiating the viewcontroller like following
let viewController = CustomerTabBarController()
let navigationViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navigationViewController
window?.makeKeyAndVisible()
Navigation Bar Shrinking after relaunching app
Navigation Bar Title truncated
Here I attached some screenshots.
Following one is launching from XCode
https://pasteboard.co/HHl1vFJYbzeX.png
2nd one is after relaunch
https://pasteboard.co/kw3zRZqic9q7.png
My question is why this does not happen when runs from XCode but when the app relaunchs.
I have tried many ways like setupui from viewdidappear methods and others. But no luck.
Please help me out.
It seems like you're setting the title of a wrong view controller and it's also worth checking if you have the correct navigation hierarchy. My best be would that this is causing the problems.
The typical hierarchy of tab-based apps is the following: UITabBarController (root) → UINavigationController (one per each tab) → YourViewController.
Now, I see that you're setting the navigation title as follows:
tabBarController?.navigationItem.title = "Project List"
This is strange and unusual. The view controller is supposed to set its own title like this:
navigationItem.title = "Project List"
Then its parent UINavigationController will be able use this title.
It's also worth setting the title in viewDidLoad, not it viewDidAppear so that the title is visible before the transition animation and not after it.
So check the hierarchy of the view controllers in the app and make sure each view controller only sets its own navigation title.
If that doesn't help, I'll be happy to retract my answer to avoid confusion.

custom back button in navigation in iOS

I know this question has been asked before but nothing worked for me and I had to ask it again.
I want an image as my back button in navigation bar, just want to change the appearance of the back button. I don't want to add a button and add selectors for it.
I tried the following code:
let backImage = UIImage(named: "Back_button")
let backAppearance = UIBarButtonItem.appearance()
backAppearance.setBackButtonBackgroundImage(backImage, for: .normal, barMetrics: .default)
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
I also tried setting the back image and back mask using storyboard but both these approaches place a black circle on my back image.
I tried setting another image as back mask by setting its alpha content equal to zero using the code but it didn't work either.
please help.
let backButton = UIBarButtonItem()
backButton.title = "Back"
backButton.image = UIImage(named: "Back_button")
self.navigationController?.navigationBar.topItem?.backBarButtonItem = backButton
You can do this to customize your Back button. And you don't have to worry about adding selectors.
This code works with Swift 5.
let backButton: UIButton = UIButton()
backButton.setImage(UIImage(named: "back"), for: UIControl.State())
backButton.addTarget(self, action:#selector(SearchResultsViewController.onBack), for: UIControl.Event.touchUpInside)
let leftBarButtonItem = UIBarButtonItem(customView: backButton)
navigationItem.leftBarButtonItem = leftBarButtonItem
I used this code to customize the back button on only one of my views:
self.navigationController?.navigationBar.topItem?.backButtonTitle = ""
let backButton = UIBarButtonItem(image: UIImage(named: "back"), style: .plain, target: self, action: #selector(goBack))
navigationItem.leftBarButtonItem = backButton
#objc func goBack() {
self.navigationController?.popViewController(animated: true)
}
Create a custom class for define navigation bar traits
Create an extension to UINavigationController for configure it
import UIKit
private final class MyNavigationBarTraits {
public var backIndicatorImage: UIImage?
public var backIndicatorTransitionMaskImage: UIImage?
public func apply(to navigationBar: UINavigationBar) {
navigationBar.backIndicatorImage = backIndicatorImage
navigationBar.backIndicatorTransitionMaskImage = backIndicatorTransitionMaskImage
}
public init(navigationBar: UINavigationBar) {
backIndicatorImage = navigationBar.backIndicatorImage
backIndicatorTransitionMaskImage = navigationBar.backIndicatorTransitionMaskImage
}
}
public typealias Callback<T> = (_: T) -> Void
public extension UINavigationController {
private struct AssociationKeys {
static var navigationBarTraits = "ws_nc_navigationBarTraits"
}
private var navigationBarTraits: MyNavigationBarTraits? {
get {
return objc_getAssociatedObject(self, &AssociationKeys.navigationBarTraits) as? MyNavigationBarTraits
}
set {
objc_setAssociatedObject(self, &AssociationKeys.navigationBarTraits, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func configureBar(block: Callback<UINavigationBar>) {
navigationBarTraits = MyNavigationBarTraits(navigationBar: navigationBar)
block(navigationBar)
}
func resetBar() {
navigationBarTraits?.apply(to: navigationBar)
navigationBarTraits = .none
}
}
And then you can configure your navigation bar in your ViewController's viewWillAppear (for example tintColor)
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.configureBar { navigationBar in
// You can customize your navigation bar in here!
navigationBar.tintColor = .red
}
}
If you want to use this customization just in one View Controller you should reset bar in your View Controller's viewWillDisappear
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.resetBar()
}
Simply Add Below Methods in Your ViewController :
func setLeftBarBackItem() {
let leftBarBackItem = UIBarButtonItem(image: #imageLiteral(resourceName: "imgBack"), style: .plain, target: self, action: #selector(self.clickToBtnBackItem(_:)))
self.navigationItem.leftBarButtonItem = leftBarBackItem
}
func clickToBtnBackItem(_ sender: UIBarButtonItem) {
view.endEditing(true)
_ = navigationController?.popViewController(animated: true)
}
func setTranspertNavigation()
{
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.view.backgroundColor = .clear
}
Inside Your ViewController's ViewDidLoad Method, Set backButton As :
self.navigationController?.isNavigationBarHidden = false
AppDelegate.shared().setupNavigationBar()
setLeftBarBackItem()
setTranspertNavigation()
self.title = "Title Here"

NavigationBar title text won't change with titleTextAttributes

I have a strange issue which I don't really understand.
I have two views, one should have a black title and the other should have a white title.
The issue that I am experiencing is that I can set the color ONCE and not change it back.
So when I go to the view that has the white title from the view with the black title and then go back, the title does not change back to black.
code for white title in viewWillAppear:
self.navigationController?.navigationBar.barStyle = .black
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
code for black title in viewWillAppear:
self.navigationController?.navigationBar.isHidden = false
self.navigationController?.navigationBar.barStyle = .default
self.navigationController?.navigationBar.tintColor = UIColor.blue
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
Why does it not change back, when I am clearly setting a new color?
EDIT: adding the complete code
Black title view:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.isHidden = false
self.navigationController?.navigationBar.barStyle = .default
self.navigationController?.navigationBar.tintColor = hexStringToUIColor(hex: "4CAF50")
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
self.navigationItem.title = listData.name
clearStatusBarColor()
let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(tapToEdit))
let sortImg = UIImage(named: "sort")
sortingButton = UIBarButtonItem(image: sortImg, style: .plain, target: self, action: #selector(tapToSort))
self.navigationItem.rightBarButtonItems = [sortingButton!, editButton]
self.navigationController?.navigationBar.setBackgroundImage(nil, for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = nil
// get updated Data
if User.active.hasListUpdated {
// return with new gameEntries -> Update
listData = User.active.allLists![listDataIndex] // To keep upToDate data!
listEntries = listData.list_entries!
gameEntries = listEntries.compactMap({ (entry: ListEntry) -> GameEntry in
return GameEntry(game: entry.game, platform: nil, platform_id: entry.platform, rating: Int(entry.rating ?? 0), review: nil, notes: entry.description)
})
listTable.reloadData()
}
// Sorting
if hasSortChanged {
hasSortChanged = false
sortList(sort: sortingOption, order: sortingOrder)
}
}
White title view:
override func viewWillAppear(_ animated: Bool) {
if !isPreviewing {
self.navigationController?.navigationBar.isHidden = false
self.navigationController?.navigationBar.barStyle = .black
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
// MARK: Clear StatusBar
clearStatusBarColor()
if transparentNav {
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for:UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.title = nil
} else {
self.navigationController?.navigationBar.setBackgroundImage(nil, for: UIBarMetrics.default)
self.navigationController?.navigationBar.shadowImage = nil
self.title = game.name!
}
}
// MARK: NavigationBar
let button = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(showOptions))
self.navigationItem.rightBarButtonItem = button
// Check if game should have new review or rating
if User.active.hasMainScreenUpdated {
// Update rating
updateUserRating()
// update review
updateUserReviewStatus()
}
// Update lists!
if User.active.hasListUpdated {
updateListsStatus()
}
}
If you are changing the nav bar colors in different view controllers, I recommend you to have a subclass of UIViewController and handle the navbar changes through that. Here's an example for your case.
class CustomUIViewController: UIViewController {
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
if parent == nil {
if SettingsManager.LastBarColor == .default {
self.setLightBars()
}
else {
self.setDarkBars()
}
}
}
func setDarkBars() {
SettingsManager.LastBarColor = .lightContent
UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent
tabBarController?.tabBar.tintColor = UIColor.white
navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
}
func setLightBars() {
SettingsManager.LastBarColor = .default
UIApplication.shared.statusBarStyle = UIStatusBarStyle.default
tabBarController?.tabBar.tintColor = UIColor.Black
navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.Black]
navigationController?.navigationBar.barTintColor = UIColor.white
navigationItem.titleView?.tintColor = UIColor.Black
}
}
class SettingsManager {
class var LastBarColor: UIStatusBarStyle = .default
}
And in your view controller use CustomUIViewController, call setDarkBars() or setLightBars() in your viewWillAppear() function.
You can use a custom UINavigationController class then override pushViewController function to set what you need on the navigationBar.
The viewWillAppear method has a lot of code here.
class MyNavigationViewController: UINavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
self.updateForVC(viewController: viewController)
}
func updateForVC(viewController: UIViewController) {
//DO WHATEVER YOU WHANT HERE, title, img, etc
var color = UIColor.black
if viewController.isKind(of: MyClass.self) {
color = UIColor.white
}
self.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: color]
}
}
Try pushViewController to navigate,It is working for me
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController {
if let navigator = self.navigationController {
navigator.pushViewController(viewController, animated: true)
viewController.title = ""
}
}

UIBarButtonItems wont show in navigationBar

I've created a navigationBar in my view which is suppose to hold 2 buttons left and right. The problem is whatever i do it wont show the buttons/views. How am i suppose to do this? here is my code so far.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont(name: "Aerovias Brasil NF", size: 30)!, NSForegroundColorAttributeName: UIColor.whiteColor()]
navTitleView = UIView(frame: CGRectMake(0, 0, 100, 44))
titleLabel.text = "SHOPOP"
navTitleView?.addSubview(titleLabel)
self.navigationItem.titleView?.addSubview(navTitleView!)
navigationHeight = UIApplication.sharedApplication().statusBarFrame.height+self.navigationController!.navigationBar.bounds.height
searchBar = UISearchBar(frame: CGRectZero)
searchBarWrapper = UINavigationBar(frame: CGRectMake(0, 100, self.navigationController!.navigationBar.bounds.size.width, self.navigationHeight!))
var buttonSearchBar:UIBarButtonItem = UIBarButtonItem(customView: searchBar!)
var cancelButton:UIBarButtonItem = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
searchBarWrapper?.topItem?.leftBarButtonItem = buttonSearchBar
searchBarWrapper?.topItem?.rightBarButtonItem = cancelButton
self.navigationController?.view.addSubview(searchBarWrapper!)
}
#IBAction func showSearchBar(sender: UIBarButtonItem) {
searchBar?.becomeFirstResponder()
UIView.animateWithDuration(0.25, animations: {
// Optional chaining may return nil
self.searchBarWrapper!.center = CGPointMake(self.navigationController!.view.center.x, self.navigationHeight!/2)
// return
}, completion: {
finished in
println("Basket doors opened!")
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func hideSearchBar(sender: UIBarButtonItem) {
UIView.animateWithDuration(0.25, animations: {
// Optional chaining may return nil
self.searchBarWrapper!.center = CGPointMake(self.navigationController!.view.center.x, -self.navigationHeight!/2)
self.searchBar?.resignFirstResponder()
// return
}, completion: {
finished in
println("Basket doors opened!")
})
Based on the changes to your question you should look into the UISearchController class. A cursory glance at the documentation leads me to believe that after you configure it properly you can push it onto the view controller stack and it will behave like you want.
Old answer:
While I'm not sure about wrapping a UISearchBar inside of a UIBarButtonItem I can say with certainty that this is the wrong way to go about adding a UINavigationBar to a UINavigationController:
self.navigationController?.view.addSubview(searchBarWrapper!)
The right way to go about providing items for the navigation bar is to override UIViewController's navigationItem property and return a customized UINavigationItem instance. This way when you push and pop view controllers off of the UINavigationController's stack the navigation bar will be updated automatically.
This is an example from a UIViewController subclass in one of my projects.
private var customNavigationItem = UINavigationItem(title:nil);
override var navigationItem:UINavigationItem
{
get
{
customNavigationItem.title = self.title;
customNavigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.Cancel,
target:self, action:"dismissForm:");
customNavigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem:.Save,
target:self, action:"saveForm:");
return customNavigationItem;
}
}
Doing this will ensure that all of the "free" functionality that UINavigationController provides works as intended. Messing with UINavigationController's view is generally a bad idea.

Resources