Customize navigation bar by adding two labels instead of title in Swift - ios

I am trying to add two labels in the place where the title is shown in navigation bar, but I am struggling to do so. It would be very nice if I could achieve this with storyboard but as I can see I cannot do it.
As I have seen I need to use navigationItem but I do not know how exactly to do that. If anyone have any example or if anyone could explain me more specifically how to do so would be wonderful.
And I need to mention that I am completely unfamiliar with Obj-C, so any help would need to be in Swift.

I am not sure if you can do it from the storyboard, but if you want to add two title labels, you can do the following in the viewDidLoad() method of the view controller for which you want the two titles:
if let navigationBar = self.navigationController?.navigationBar {
let firstFrame = CGRect(x: 0, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
let secondFrame = CGRect(x: navigationBar.frame.width/2, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
let firstLabel = UILabel(frame: firstFrame)
firstLabel.text = "First"
let secondLabel = UILabel(frame: secondFrame)
secondLabel.text = "Second"
navigationBar.addSubview(firstLabel)
navigationBar.addSubview(secondLabel)
}
In this way you can add as many subviews as you want to the navigation bar

Here's an implementation that uses a stack view instead, which also gives you some versatility with layout of the labels:
class ViewController: UIViewController {
lazy var titleStackView: UIStackView = {
let titleLabel = UILabel()
titleLabel.textAlignment = .center
titleLabel.text = "Title"
let subtitleLabel = UILabel()
subtitleLabel.textAlignment = .center
subtitleLabel.text = "Subtitle"
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.axis = .vertical
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = titleStackView
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if view.traitCollection.horizontalSizeClass == .compact {
titleStackView.axis = .vertical
titleStackView.spacing = UIStackView.spacingUseDefault
} else {
titleStackView.axis = .horizontal
titleStackView.spacing = 20.0
}
}
}

Related

I cannot stretch the height of the titleView in the UINavigationBar

This is my code:
class UINavigationControllerCustom : UINavigationController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews();
navigationBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 120);
}
}
class PreferenceInput: UIViewController {
override func viewDidLoad() {
let l1 = UILabel()
l1.backgroundColor = .clear
l1.numberOfLines = 1
l1.font = UIFont.boldSystemFont(ofSize: 30.0)
l1.textAlignment = .left
l1.textColor = .black
l1.text = "Bold title"
l1.sizeToFit();
let l2 = UILabel()
l2.backgroundColor = .clear
l2.numberOfLines = 2
l2.font = UIFont.boldSystemFont(ofSize: 16.0)
l2.textAlignment = .left
l2.textColor = .darkGray;
l2.text = "This is a\nmultiline string for the navBar"
l2.sizeToFit();
let tView = UIStackView(arrangedSubviews: [l1,l2]);
tView.axis = .vertical;
let spacer = UIView()
let constraint = spacer.widthAnchor.constraint(greaterThanOrEqualToConstant: CGFloat.greatestFiniteMagnitude)
constraint.isActive = true
constraint.priority = .defaultLow
let stack = UIStackView(arrangedSubviews: [tView, spacer])
stack.axis = .horizontal
navigationItem.titleView = stack
navigationItem.titleView.sizeToFit();
}
}
And this is what I get:
I can set the height of the navigation bar but how and where should I set the constraint to stretch the height in respect of the given 120px of the custom navigation controller?
I want to provide an individual height of the navigation bar.

How to reduce the left and right gaps in custom view for Navigation bar in iOS

I recently added a side menu which is having a UITableviewController class as the Menu . In this ViewController i added a custom view to the Navbar through this code. But when i run in device i get extra space on left and right side of the view . How to remove this?
import UIKit
class SettingsTableController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView()
newView.frame = CGRect(x:UIApplication.shared.statusBarFrame.origin.x,y:UIApplication.shared.statusBarFrame.height, width: view.frame.size.width + 10, height: self.navigationController!.navigationBar.frame.height)
//#710193
newView.backgroundColor = UIColor.red
let lblTitle = UILabel()
lblTitle.text = " Animals"
lblTitle.textColor = UIColor.white
lblTitle.font = UIFont(name: "HelveticaNeue-Bold", size: 18.0)
lblTitle.frame = newView.bounds
newView.addSubview(lblTitle)
navigationItem.titleView = newView
}
}
so it produces the output as below. I want to remove the pointed out gaps which is shown through red arrows .
You are setting frame but also need to autolayout. So after newView.addSubview(lblTitle) add below codes.
newView.translatesAutoresizingMaskIntoConstraints = false
newView.layoutIfNeeded()
newView.sizeToFit()
newView.translatesAutoresizingMaskIntoConstraints = true
navigationItem.titleView = newView
I have changed some line of code in your code and it is working fine for me
let newView = UIView()
newView.frame = CGRect(x:UIApplication.shared.statusBarFrame.origin.x,y:UIApplication.shared.statusBarFrame.height, width: view.frame.size.width, height: self.navigationController!.navigationBar.frame.height)
//#710193
newView.backgroundColor = UIColor.red
let lblTitle = UILabel()
lblTitle.text = " Animals"
lblTitle.textColor = UIColor.white
lblTitle.font = UIFont(name: "HelveticaNeue-Bold", size: 18.0)
lblTitle.frame = newView.bounds
newView.addSubview(lblTitle)
You are adding newView to titleView of navigationItem instead of adding that to navigationBar of navigationController
self.navigationController?.navigationBar.addSubview(newView)
Try to use auto layout constraint programatically in swift. The link is provided below
Auto Layout in Swift: Writing constraints programmatically

Custom Navigation Title in iOS 12

I am trying to implement a custom Navigation Title on an iOS app.
The StoryBoard looks like this:
The place that I want to have the custom Navigation Title is the last view ( the message view ), and because I use an image and text this means that I need to have custom width and height. By needing this if I do in viewDidLoad:
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
......
titleView?.addSubview(imageView)
......
titleView?.addSubview(label)
navigationItem.titleView = titleView
The height of the title is blocked to 44pt.
But how I managed to do it is adding the subViews to the navigation bar:
var navigationBar: MessagesNavigationBar? {
guard let navigationBar = navigationController?.navigationBar as? MessagesNavigationBar else {
return nil
}
return navigationBar
}
And in viewDidLoad
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
......
titleView?.addSubview(imageView)
......
titleView?.addSubview(label)
navigationBar?.addSubview(titleView!)
But the problem is that I have to remove the subviews when I leave the view, otherwise whatever I add there will be present in the table view as well.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if navigationBar != nil {
titleView?.removeFromSuperview()
}
}
Which kinda makes me feel that I'm not doing the right thing and I find difficult to add a fade out animation to those subViews when I leave the conversation. (i.e. native messages app on iOS).
So what is the right way of creating a custom Title Navigation Bar in iOS 12?
Scenes
Creating your custom titleView and assigning it to navigationItem.titleView is what you want. On older systems (pre iOS 11) you just might need to call sizeToFit() on the titleView.
This way you can create this titleView
Swift
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 20),
imageView.widthAnchor.constraint(equalToConstant: 20)
])
imageView.backgroundColor = .red
let titleLabel = UILabel()
titleLabel.text = "Custom title"
let hStack = UIStackView(arrangedSubviews: [imageView, titleLabel])
hStack.spacing = 5
hStack.alignment = .center
navigationItem.titleView = hStack
}
Obj-C
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] init];
[NSLayoutConstraint activateConstraints:#[
[imageView.heightAnchor constraintEqualToConstant:20],
[imageView.widthAnchor constraintEqualToConstant:20]
]];
imageView.backgroundColor = [UIColor redColor];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = #"Custom title";
UIStackView *hStack = [[UIStackView alloc] initWithArrangedSubviews:#[imageView, titleLabel]];
hStack.spacing = 5;
hStack.alignment = UIStackViewAlignmentCenter;
self.navigationItem.titleView = hStack;
}
You might also need to have the right set of autolayout constraints or use UIStackView.
These lines have no effect on the size of a title view:
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
Instead (or in addition) give your title view a width constraint and a height constraint. That is how the runtime knows what size you want.

Change label text in the navigation bar

I added a label to my NavigationBar with this code:
let navigationBar = self.navigationController?.navigationBar
let moneyFrame = CGRect(x: 330, y: 0, width: (navigationBar?.frame.width)!/2, height: (navigationBar?.frame.height)!)
let moneyLabel = UILabel(frame: moneyFrame)
moneyLabel.text = "\(money)"
navigationBar?.addSubview(moneyLabel)
The problem is: when I want to change the value of the variable "money", I always came with the solution to add another label. I just want to change the text of the label in the NavigationBar.
In the class scope define a label like this:
var moneyLabel: UILabel?
then in your function or wherever the code you posted sits in the class, do this:
func myFunctionThatSetupNavigationLabel() {
let navigationBar = self.navigationController?.navigationBar
let moneyFrame = CGRect(x: 330, y: 0, width:
(navigationBar?.frame.width)!/2, height: (navigationBar?.frame.height)!)
moneyLabel = UILabel(frame: moneyFrame)
moneyLabel.text = "\(money)"
navigationBar?.addSubview(moneyLabel)
}
Now just add this function to edit the title label:
func updateTitle(title: String) {
if let myTitleView = self.moneyLabel {
myTitleView.text = title
}
}
You could add the label to the navigation bar as a custom view.
In the class scope define a label:
var label = UILabel()
then in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
let textlabel = UIBarButtonItem(customView: label)
navigationItem.rightBarButtonItem = textlabel
label.text = "money"
}
then whenever you want to change the text label you could simply just write the next code:
label.text = "whatever you want"

How to make iOS navigationItem.titleView align to the left?

navigationItem.hidesBackButton = true
navigationItem.leftBarButtonItem = nil
head = UIView()
head.frame = CGRectMake(0, 0, 200, 44)
head.frame.origin.x = CGFloat(0)
navigationItem.titleView = head
I attempt to align the titleView to the left, but it still remains in the middle.
Try this:
let title = UILabel()
title.text = "TITLE"
let spacer = UIView()
let constraint = spacer.widthAnchor.constraint(greaterThanOrEqualToConstant: CGFloat.greatestFiniteMagnitude)
constraint.isActive = true
constraint.priority = .defaultLow
let stack = UIStackView(arrangedSubviews: [title, spacer])
stack.axis = .horizontal
navigationItem.titleView = stack
The idea is that main view (in this case title) will take all the space it needs and spacer view will take all the free space left.
I figured it out.
I just need to set my custom UIView as the leftBarButtonItem.
I had a similar requirement of adding the Title along with the subtitle to the left of the navbar. I couldn't achieve it with a TitleView since it cannot be aligned left.
So I took #TIMEZ and #Wain's answers, along with responses from the thread here and added a complete answer, in case it helps anyone :
let titleLabel = UILabel()
titleLabel.text = "Pillars"
titleLabel.textAlignment = .center
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
let subtitleLabel = UILabel()
subtitleLabel.text = "How did you do today?"
subtitleLabel.textAlignment = .center
subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalSpacing
stackView.alignment = .leading
stackView.axis = .vertical
let customTitles = UIBarButtonItem.init(customView: stackView)
self.navigationItem.leftBarButtonItems = [customTitles]
You can't align a title view to the left. You can create a title view and add a subview positioned to its left. If you're looking to display in place of the back button then you should be using a bar button item instead of title view.
I don't think Apple wants you to do that. Navigation bars have a pretty specific purpose that often involves having something else in the top left corner like a Back button. You might be better off making a custom UIView or UIToolbar that looks like the navigation bar.
If it's a custom UIView, override intrinsicContentSize and return
CGSize(width: .greatestFiniteMagnitude, height: UIView.noIntrinsicMetric)
This will stretch the view to the entire width between left and right bar button items.
You can constraint the titleView to the navigationBars leftAnchor.
private func setupNavigationBarTitleView() {
let titleView = YourCustomTitleView()
navigationBarTitleView = titleView
navigationBarTitleView?.translatesAutoresizingMaskIntoConstraints = false
navigationItem.titleView = navigationBarTitleView
if let navigationBar = navigationController?.navigationBar {
NSLayoutConstraint.activate([
titleView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor, constant: 16),
titleView.heightAnchor.constraint(equalToConstant: 36)
])
}
}
The simplest solution is to add low priority constraint for title width.
let titleLabel = UILabel()
titleLabel.textAlignment = .left
...
let c = titleLabe.widthAnchor.constraint(equalToConstant: 10000)
c.priority = .required - 1
c.isActive = true
navigationItem.titleView = titleLabel
Hey guys after trying most of the solutions above. I found that most of em still did not meet my prod requirements. Here is a solution I came up with after trying out different solutions.
func setLeftAlignTitleView(font: UIFont, text: String, textColor: UIColor) {
guard let navFrame = navigationController?.navigationBar.frame else{
return
}
let parentView = UIView(frame: CGRect(x: 0, y: 0, width: navFrame.width*3, height: navFrame.height))
self.navigationItem.titleView = parentView
let label = UILabel(frame: .init(x: parentView.frame.minX, y: parentView.frame.minY, width: parentView.frame.width, height: parentView.frame.height))
label.backgroundColor = .clear
label.numberOfLines = 2
label.font = font
label.textAlignment = .left
label.textColor = textColor
label.text = text
parentView.addSubview(label)
}
let navLabel = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width - 32, height: view.frame.height))
navLabel.text = "Hi, \(CurrentUser.firstName)"
navLabel.textColor = UIColor.white
navLabel.font = UIFont.systemFont(ofSize: 20)
navigationItem.titleView = navLabel

Resources