49
votes

So I'm wanting to add a "subtitle" under the title in the navigation bar in navigation controller.

Mostly everything I look up so far wants me to use CGRect. I don't know a whole lot what that is and it sounds like its wanting me to create an entire new view which is not what I am wanting to do.

My question is, is there a dot method to adding a subtitle view easily?

The closest thing I found was posted on stack overflow and here is the link:

Create a subtitle in navigationbar

Apparently last year this worked but now I am getting errors and it's in my viewDidLoad...

I tried this:

self.navigationController?.navigationItem.prompt = "Subtitle Here"

It's the only thing that won't show any errors but still doesn't work. It literally does nothing. At least nothing visible at run time.

On a side note, swift is preferred. Thanks!

9
Have you tried self.navigationItem.prompt = "Subtitle"? - tktsubota
@tktsubota That will make the subtitle above title and not below title - Rajan Maheshwari
@RajanMaheshwari Yeah I did a test and found that out. - tktsubota
@tktsubota are you joking? It's not subtitle, it's a new separate bar above navigation bar with its own title, this way you have two bars, useless! - user155
It will help you all existing scenario stackoverflow.com/questions/37409260/… - Shrikant Tanwade

9 Answers

76
votes

Here is my version using a stack view on an extension.

extension UINavigationItem {
    func setTitle(title:String, subtitle:String) {
        
        let one = UILabel()
        one.text = title
        one.font = UIFont.systemFont(ofSize: 17)
        one.sizeToFit()
        
        let two = UILabel()
        two.text = subtitle
        two.font = UIFont.systemFont(ofSize: 12)
        two.textAlignment = .center
        two.sizeToFit()
        
        let stackView = UIStackView(arrangedSubviews: [one, two])
        stackView.distribution = .equalCentering
        stackView.axis = .vertical
        stackView.alignment = .center
        
        let width = max(one.frame.size.width, two.frame.size.width)
        stackView.frame = CGRect(x: 0, y: 0, width: width, height: 35)
        
        one.sizeToFit()
        two.sizeToFit()
        
        self.titleView = stackView
    }
}
60
votes

Though there is a solution but it has some known issues

Solution is writing a function like this

func setTitle(title:String, subtitle:String) -> UIView {
    let titleLabel = UILabel(frame: CGRectMake(0, -2, 0, 0))

    titleLabel.backgroundColor = UIColor.clearColor()
    titleLabel.textColor = UIColor.grayColor()
    titleLabel.font = UIFont.boldSystemFontOfSize(17)
    titleLabel.text = title
    titleLabel.sizeToFit()

    let subtitleLabel = UILabel(frame: CGRectMake(0, 18, 0, 0))
    subtitleLabel.backgroundColor = UIColor.clearColor()
    subtitleLabel.textColor = UIColor.blackColor()
    subtitleLabel.font = UIFont.systemFontOfSize(12)
    subtitleLabel.text = subtitle
    subtitleLabel.sizeToFit()

    let titleView = UIView(frame: CGRectMake(0, 0, max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), 30))
    titleView.addSubview(titleLabel)
    titleView.addSubview(subtitleLabel)

    let widthDiff = subtitleLabel.frame.size.width - titleLabel.frame.size.width

    if widthDiff < 0 {
        let newX = widthDiff / 2
        subtitleLabel.frame.origin.x = abs(newX)
    } else {
        let newX = widthDiff / 2
        titleLabel.frame.origin.x = newX
    }

    return titleView
}

Using this function for custom navigation title view in viewDidLoad

self.navigationItem.titleView = setTitle("Title", subtitle: "SubTitle")

Only known issue is that if subtitle becomes very large than the misplacement occurs.

Final Outcome enter image description here

Source: https://gist.github.com/nazywamsiepawel/0166e8a71d74e96c7898

16
votes

@iosjillian's Swift 4 extension works great, adding a bit more to honor the bar's appearance and user font preferences:

import UIKit

extension UINavigationItem {

  func setTitle(_ title: String, subtitle: String) {
    let appearance = UINavigationBar.appearance()
    let textColor = appearance.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor ?? .black

    let titleLabel = UILabel()
    titleLabel.text = title
    titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
    titleLabel.textColor = textColor

    let subtitleLabel = UILabel()
    subtitleLabel.text = subtitle
    subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
    subtitleLabel.textColor = textColor.withAlphaComponent(0.75)

    let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
    stackView.distribution = .equalCentering
    stackView.alignment = .center
    stackView.axis = .vertical

    self.titleView = stackView
  }
}
11
votes

Thanks a lot for your answer! @RajanMaheshwari

Your coding worked perfectly except the if statement you made with the widthDiff..

I adjusted it a little bit and everything worked smoothly.

if widthDiff < 0 {
        let newX = widthDiff / 2
        subtitleLabel.frame.origin.x = abs(newX)
    } else {
        let newX = widthDiff / 2
        titleLabel.frame.origin.x = newX
    }

Thanks again for your response!

4
votes

I really liked @user2325031's answer, but found that sizing the labels to fit and setting the frame wasn't needed. I also set the stackView's alignment to .center per @GerardoMR's suggestion.

extension UINavigationItem {

    func setTitle(_ title: String, subtitle: String) {
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 17.0)
        titleLabel.textColor = .black

        let subtitleLabel = UILabel()
        subtitleLabel.text = subtitle
        subtitleLabel.font = .systemFont(ofSize: 12.0)
        subtitleLabel.textColor = .gray

        let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
        stackView.distribution = .equalCentering
        stackView.alignment = .center
        stackView.axis = .vertical

        self.titleView = stackView
    }

}
4
votes

In case anyone looking for Objective-C code of the above mentioned solution:

   UILabel *title = [[UILabel alloc]init];
   UILabel *subtitle = [[UILabel alloc]init];

   [title setFont:[UIFont systemFontOfSize:12]];
   [title setTextColor:[UIColor whiteColor]];
   [title setFont:[UIFont systemFontOfSize:17]];
   [title sizeToFit];
   title.text = @"Title";

   [subtitle setTextColor:[UIColor whiteColor]];
   [subtitle setFont:[UIFont systemFontOfSize:12]];
   [subtitle setTextAlignment:NSTextAlignmentCenter];
   [subtitle sizeToFit];
   subtitle.text = @"Subtitle Title";

   UIStackView *stackVw = [[UIStackView alloc]initWithArrangedSubviews:@[title,subtitle]];
   stackVw.distribution = UIStackViewDistributionEqualCentering;
   stackVw.axis = UILayoutConstraintAxisVertical;
   stackVw.alignment =UIStackViewAlignmentCenter;


   [stackVw setFrame:CGRectMake(0, 0, MAX(title.frame.size.width, subtitle.frame.size.width), 35)];
   self.navigationItem.titleView = stackVw;
2
votes

Thanks for the answer @RajanMaheshwari

If anyone is having the issue where the title becomes misaligned when the subtitle text is longer than the title text, I added the following code to the Rajan's answer above just below where the subtitleLabel is instantiated:

// Fix incorrect width bug
if (subtitleLabel.frame.size.width > titleLabel.frame.size.width) {
    var titleFrame = titleLabel.frame
    titleFrame.size.width = subtitleLabel.frame.size.width
    titleLabel.frame = titleFrame
    titleLabel.textAlignment = .center
}

Hope this helps someone who encountered the same issue as me

2
votes

Another solution, using only one label and NSAttributedString to differentiate between title and subtitle (with different font sizes, weights, colors, etc.) instead. Removes the problem of different label alignment.

extension UIViewController {
    func setTitle(_ title: String, subtitle: String) {
        let rect = CGRect(x: 0, y: 0, width: 400, height: 50)
        let titleSize: CGFloat = 20     // adjust as needed
        let subtitleSize: CGFloat = 15

        let label = UILabel(frame: rect)
        label.backgroundColor = .clear
        label.numberOfLines = 2
        label.textAlignment = .center
        label.textColor = .black

        let text = NSMutableAttributedString()
        text.append(NSAttributedString(string: title, attributes: [.font : UIFont.boldSystemFont(ofSize: titleSize)]))
        text.append(NSAttributedString(string: "\n\(subtitle)", attributes: [.font : UIFont.systemFont(ofSize: subtitleSize)]))
        label.attributedText = text
        self.navigationItem.titleView = label
    }
}

Custom titleView based in part on https://stackoverflow.com/a/34298491/3918865

2
votes

Swift 4:

import UIKit

class NavigationTitleView: UIView {

    private var contentStackView = UIStackView()
    private var titleLabel = UILabel()
    private var subTitleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        viewConfig()
        addViewsConfig()
        layoutViewsConfig()

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    func set(title: String, subTitle: String){

        self.titleLabel.text = title
        self.subTitleLabel.text = subTitle

    }

    private func viewConfig() {

        contentStackView.axis = .vertical
        contentStackView.alignment = .center
        contentStackView.distribution  = .fill
        contentStackView.spacing = 5


        self.backgroundColor = .clear
        self.titleLabel.textColor = .white
        self.self.subTitleLabel.textColor = .white

    }

    private func addViewsConfig() {

        contentStackView.addArrangedSubview(subTitleLabel)
        contentStackView.addArrangedSubview(titleLabel)
        self.addSubview(contentStackView)

    }

    private func layoutViewsConfig(){

        contentStackView.translatesAutoresizingMaskIntoConstraints = false
        contentStackView.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0.0).isActive = true
        contentStackView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true

    }

}

Use:

import UIKit

class  ViewController: UIViewController {

    private var navigationTitleView = NavigationTitleView()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationItem.titleView = navigationTitleView
        navigationTitleView.set(title: "title", subTitle: "subTitle")

    }

}

enter image description here