I'm having a problem with the titleView in navigation bar in iOS. I've created a custom view for the titleView, but every time I push another controller, the titleView immediately appear and disappear when I go back to the first view controller. Here's my code.
override func viewDidLoad() {
super.viewDidLoad()
let logo = UIImage(named: "img_appbar_logo")?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 180, height: 40))
logoContainer.backgroundColor = UIColor.clear
animatedLogo = UIImageView(frame: CGRect(x: 0, y: 0, width: logoContainer.frame.width, height: logoContainer.frame.height))
animatedLogo.contentMode = .scaleAspectFit
animatedLogo.clipsToBounds = true
animatedLogo.image = logo
logoContainer.addSubview(animatedLogo)
navigationItem.titleView = logoContainer
}
I've already fixed the issue related to titleView in Navigation bar. I found out that after I push another controller and pop back, the titleView will be replaced by the view of empty UILabel in the NavigationItem. That's why the titleView appear then disappear.
How I fixed the issue?
I added the custom view directly to the navigation bar. Here's the code
let logo = UIImage(named: "img_appbar_logo")?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 180, height: 40))
logoContainer.backgroundColor = UIColor.red
animatedLogo = UIImageView(frame: CGRect(x: 0, y: logoContainer.frame.origin.y, width: logoContainer.frame.width, height: logoContainer.frame.height))
animatedLogo.contentMode = .scaleAspectFit
animatedLogo.clipsToBounds = true
animatedLogo.image = logo
logoContainer.addSubview(animatedLogo)
navigationController?.navigationBar.addSubview(logoContainer) <--- new
//navigationItem.titleView = logoContainer <---- old
Then remove the view on viewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
logoContainer.removeFromSuperview()
}
Set the titleView in viewWillAppear since the titleView gets replaced by the title in the previous controller
Related
I am adding custom navigation item to my application. But every time I navigate the items of navigation item overlaps. My code is
func fixNavBar(){
self.navigationItem.setHidesBackButton(true, animated: true)
let nav = self.navigationController?.navigationBar
let navView = UIView(frame: CGRect(x: 0, y: 0, width: (nav?.frame.width)!, height: (nav?.frame.height)!))
let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: navView.frame.height, height: navView.frame.height))
backButton.imageView?.contentMode = .scaleToFill
backButton.setImage(UIImage(systemName: "chevron.left"), for: .normal)
backButton.setTitleColor(UIColor(rgb: 0x23C0FF), for: .normal)
backButton.tintColor = UIColor(rgb: 0x23C0FF)
nav?.addSubview(navView)
backButton.addTarget(self, action: #selector(goBack), for: .touchUpInside)
navView.backgroundColor = .clear
let imageView = UIImageView(frame: CGRect(x: backButton.frame.width+5, y: 0, width: navView.frame.height-2, height: navView.frame.height-2))
let profUrl = URL(string: "\(selectedUser.profilePictuer)")
imageView.kf.setImage(with: profUrl)
let userLbl = UILabel(frame: CGRect(x: backButton.frame.width + 15 + imageView.frame.width, y: 0, width: 150, height: navView.frame.height))
userLbl.font = UIFont(name: UIFont.fontNames(forFamilyName: "Ubuntu")[0], size: 17)
userLbl.text = selectedUser.fullName
userLbl.textAlignment = .left
userLbl.textColor = .black
navView.addSubview(imageView)
navView.addSubview(userLbl)
navView.addSubview(backButton)
imageView.layer.cornerRadius = imageView.frame.height/2
imageView.layer.masksToBounds = true
}
#objc func goBack(sender: UIButton!) {
self.navigationController!.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
fixNavBar()
}
This is root view controller when where I select any chat to open
When I select chat for first time I get something like that
And when select second chat it does not remove previous user's name but overlaps it
Your root and child view controller has the same navigationcontroller so when everytime you call fixNavBar , some views appending on the previous .
First option is adding nav?.subviews.forEach({$0.removeFromSuperview()}) on the top of your function like #Raja's comment.
Second is go storyboard and embed a new navigationController to your child viewcontroller(where yout function is)
So the original setup of the app that I inherited has the navigation bar set like so (this is in my AppDelegate):
private func configureNavigationController(_ navigationController: UINavigationController) {
navigationController.navigationBar.isTranslucent = false
self.window?.rootViewController = navigationController
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = UIViewContentMode.scaleAspectFit
let center = (navigationController.topViewController?.view.frame.width)! / 2.0 - 44
let titleView = UIView(frame: CGRect(x: center, y: 0, width: 88, height: 44))
imageView.frame = titleView.bounds
titleView.addSubview(imageView)
UINavigationBar.appearance().barTintColor = UIColor.navBackground
UINavigationBar.appearance().tintColor = UIColor.white
UINavigationBar.appearance().addSubview(titleView)
}
This creates the nav bar across every view controller correctly with the image in the center, however I have some new functionality that needs to be on top of everything, and this logo file - logo-white - is still showing up over top.
That's the real problem I want to solve - so if my attempted solution below is wrong, let me know and tell me the correct way.
Anyway, I tried commenting out the code above in my AppDelegate, and putting it in the specific viewcontrollers that I need it for
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = UIViewContentMode.scaleAspectFit
let center = (navigationController!.topViewController?.view.frame.width)!// / 2.0 - 44
let titleView = UIView(frame: CGRect(x: center, y: 0, width: 88, height: 44))
imageView.frame = titleView.bounds
titleView.addSubview(imageView)
navigationItem.titleView = imageView
However this doesn't work - I can either get the logo to show up on the left side of the screen, or slightly off of center, but never center.
I am guessing that this is because the bar has a back button and a little settings icon as well on either side.
So, how do I do this correctly?
Is there a way to make it so that something can cover the logo? Is the solution to move it into my individual view controllers?
Here's a picture of the overlap here. The logo, "Pinyada" should not be covering this up at all - this is a third party library that should be on top of everything.
You may try this :
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = .scaleAspectFit
imageView.frame = CGRect(x: 0, y: 0, width: 88, height: 44)
navigationItem.titleView = imageView
}
If you have navigationItem.titleView , another titleview is not necessary.
Sometimes, if you need a much more precise control of the titleView, you can add a customTitleView. Add the following codes in the viewController and you can get what you want.
let imageView = UIImageView(image: UIImage(named: "logo-white"))
private func addTitleView(){
let nbar = (navigationController?.navigationBar)!
let width = nbar.frame.width
imageView.contentMode = UIView.ContentMode.scaleAspectFit
imageView.frame = CGRect.init(x: (width - 88) / 2.0 , y: 0, width: 88, height: 44)
nbar.addSubview(imageView)
}
private func removeTitleView(){
imageView.removeFromSuperview()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addTitleView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeTitleView()
}
BTW, UINavigationBar.appearance().addSubview(titleView)
This method will result in all navigationBar with the same titleView, which is not what you want.
I figured it out.
One of the other modifications to my header was taking up too much space. I made it smaller and the image fits perfectly.
I moved all of the code generating the header out of the AppDelegate and into my navigation protocol, where I then popped it into each and every viewcontroller.
We're having a weird issue with using a UISearchController as an item in a navigation bar. It's set as the left bar button item and when a user taps to start searching, the search bar expands to the right off the side of the screen.
Below is the code for creating the UISearchController:
resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.hidesNavigationBarDuringPresentation = false
controller.searchBar.delegate = self
controller.delegate = self
controller.searchBar.frame = CGRect(x: 0, y: 0, width: 266, height: 44.0)
let searchBarView = UIView(frame: controller.searchBar.frame)
searchBarView.addSubview(controller.searchBar)
controller.searchBar.backgroundImage = UIImage(named: "searchBarBG")
controller.searchBar.barTintColor = .white
controller.searchBar.subviews[0].subviews.flatMap(){ $0 as? UITextField }.first?.tintColor = Constants.Colors.Red
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchBarView)
return controller
})()
When the screen first loads, it looks like this.
After tapping on the search bar, it changes to look like this.
I have no idea what's causing this. After lots of searching, I tried changing self.definesPresentationContext = false; and self.extendedLayoutIncludesOpaqueBars = true as well as adjusting similar checkboxes in the storyboard. Any tips?
Edit: This only seems to happen on iOS 11. In 10.3, the search bar actually shrinks a little to accommodate the Cancel button but ultimately takes up the same amount of space.
I ended up fixing this by overriding the didPresentSearchController and didDismissSearchController methods as part of the UISearchControllerDelegate.
extension ContactUsViewController: UISearchControllerDelegate {
func didPresentSearchController(_ searchController: UISearchController)
{
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: 266, height: 44.0)
}
func didDismissSearchController(_ searchController: UISearchController)
{
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: 266, height: 44.0)
}
}
If you don't need the added functionality of UISearchBarController you can directly use UISearchBar, which has a more predictable resizing behavor:
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 200, height: 20))
searchBar.delegate = self
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchBar)
Note that if you use the built-in cancel button or other similar features you'll have to manually handle them in the delegate methods.
I have created a custom navigation bar.Now i am adding this into viewwillappear method.Due to this when i return to my view controller it takes time to load.
func addViewToNav()
{
myView = UIView(frame: CGRect(x: 0, y: 20, width: (self.navigationController?.navigationBar.frame.size.width)!-40, height: (self.navigationController?.navigationBar.frame.size.height)!))
imageView.frame = CGRect(x: 5, y: (myView?.frame.size.height)!/2-14, width: 28, height: 28)
let image = UIImage(named: "download")
imageView.contentMode=UIViewContentMode.scaleAspectFill
imageView.image=image
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.cornerRadius = imageView.frame.size.width/2
imageView.clipsToBounds = true
imageView.backgroundColor=UIColor.yellow
label = UILabel(frame: CGRect(x: imageView.frame.size.width+15, y: 0, width: 150, height: (myView?.frame.size.height)!))
label?.text="Label"
label?.textColor=UIColor.white
myView?.addSubview(imageView)
myView?.addSubview(label!)
self.navigationController?.view.addSubview(myView!)
//add tap gesturte on imageview & label
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapBlurButton(_:)))
myView?.isUserInteractionEnabled=true
tapGesture.numberOfTapsRequired=1
myView?.addGestureRecognizer(tapGesture)
}
Please guide how can i make it fast because this is very slow.
It is taking time because every time the controller appears on the screen viewWillAppear will be called and each time the code for creating the custom view, adding it as the subview and then adding gesture recognizer will be executed.
There are 2 ways that might reduce the processing time:
If you want to do it programatically, the add the code to create and add custom navigation bar in viewDidLoad instead of viewWillAppear
Add the custom navigation bar in the storyboard itself and create an outlet connection for it in the controller.
I am trying to create a custom titleView for a navigation bar. I am able to set the titleView in the root view controller that is embedded in a navigation controller.
When I push the second view controller onto the stack and try to set the titleView for this view controller it does not work. The titleView quickly appears and disappears. When I go back to the previous view controller this titleView quickly appears and disappears now also.
Does anyone know why this is happening or how to set the titleView correctly without flashing and disappearing?
class FirstViewController: UIViewController {
var titleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addTitleView()
}
func addTitleView() {
titleView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 44))
let companyLabel = UILabel(frame: CGRect(x: 0, y: 3, width: 150, height: 11))
companyLabel.text = "CPS Dashboard"
companyLabel.textColor = UIColor.grayColor()
companyLabel.textAlignment = .Center
companyLabel.font = UIFont.systemFontOfSize(9)
titleView.addSubview(companyLabel)
let titleLabel = UILabel(frame: CGRect(x: 0, y: 16, width: 150, height: 18))
titleLabel.text = "Dashboard"
titleLabel.textColor = UIColor.blackColor()
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.systemFontOfSize(15)
titleView.addSubview(titleLabel)
navigationItem.titleView = titleView
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Show" {
let controller = segue.destinationViewController as! SecondViewController
controller.titleView = titleView
}
}
}
The second viewcontroller:
class SecondViewController: UIViewController {
var titleView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
if let titleView = titleView {
navigationItem.titleView = titleView
}
}
}
I found a solution. I copied addTitleView() method from FirstViewController into SecondViewController, and called both of them in viewDidLoad(). This worked exactly as I wanted it to. For some reason it was not working to pass the titleView forward as a property and assigning it to navigationItem.titleView.
class FirstViewController: UIViewController {
var titleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addTitleView()
}
func addTitleView() {
titleView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 44))
let companyLabel = UILabel(frame: CGRect(x: 0, y: 3, width: 150, height: 11))
companyLabel.text = "CPS Dashboard"
companyLabel.textColor = UIColor.grayColor()
companyLabel.textAlignment = .Center
companyLabel.font = UIFont.systemFontOfSize(9)
titleView.addSubview(companyLabel)
let titleLabel = UILabel(frame: CGRect(x: 0, y: 16, width: 150, height: 18))
titleLabel.text = "Dashboard"
titleLabel.textColor = UIColor.blackColor()
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.systemFontOfSize(15)
titleView.addSubview(titleLabel)
navigationItem.titleView = titleView
}
}
The second viewcontroller:
class SecondViewController: UIViewController {
var titleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addTitleView()
}
func addTitleView() {
titleView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 44))
let companyLabel = UILabel(frame: CGRect(x: 0, y: 3, width: 150, height: 11))
companyLabel.text = "CPS Dashboard"
companyLabel.textColor = UIColor.grayColor()
companyLabel.textAlignment = .Center
companyLabel.font = UIFont.systemFontOfSize(9)
titleView.addSubview(companyLabel)
let titleLabel = UILabel(frame: CGRect(x: 0, y: 16, width: 150, height: 18))
titleLabel.text = "Dashboard"
titleLabel.textColor = UIColor.blackColor()
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.systemFontOfSize(15)
titleView.addSubview(titleLabel)
navigationItem.titleView = titleView
}
}
My solution is simple, and it works:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let tv = navigationItem.titleView {
print("transform", tv.transform)) // is always identity
let bounds = tv.bounds
print("bounds", bounds) // its origin may not be zero.
tv.bounds = CGRect(origin: .zero, size: bounds.size)
print("new bounds", tv.bounds)
}
}
Using Xcode's view debugger, you will find that titleView.bounds.origin is not zero.
How to let it happen again, two steps:
1. UIViewController A and B; A has custom navigationItem.titleView, B hides navigationBar in its viewWillAppear(); when B poped, A.viewWillAppear() setNavigationBar(hidden: false, animated: true)
2. user-driven popViewController is canceled by lifting your hand.
Then you will found, A's navigationBar is blank.
I was having this same issue, but none of the above solutions fixed it for me. My issue was that I was setting translatesAutoresizingMaskIntoConstraints to false. I imagine this caused the appearing/disappearing because it needs to be set to true in order to constrain the view internally to the navigation bar.