52

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!

pgSystemTester
  • 8,979
  • 2
  • 23
  • 49
AdrianGutierrez
  • 679
  • 1
  • 5
  • 11

11 Answers11

82

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
    }
}
aheze
  • 24,434
  • 8
  • 68
  • 125
user2325031
  • 924
  • 6
  • 3
  • 3
    title and subtitle slide to centre from left when pushing from navigation controller any workarounds – Nitish Makhija May 12 '17 at 09:40
  • 11
    To me, this is the cleaner answer, it doesn't have magic numbers as the first answer. I will only add `stackView.alignment = .center` after you set the axis, otherwise it will not appear totally centered if the subtitle is bigger. Also I would remove the last two calls to `sizeToFit()`, since you called them on the labels. – GerardoMR May 23 '17 at 23:17
  • does default title also has constant font value 17? – user155 Mar 26 '19 at 21:27
63

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

John Kahn
  • 308
  • 1
  • 11
Rajan Maheshwari
  • 14,465
  • 6
  • 64
  • 98
  • 1
    Its a whole lot more code then i was expecting but it does work. I really hope Apple adds an easier way. Subtitle doesn't seem like anything that would be so far fetched.. lol – AdrianGutierrez Jul 29 '16 at 01:34
  • Thanks a ton by the way! :) @rajan – AdrianGutierrez Jul 29 '16 at 01:34
  • I need something like this, If title is blank then title should in center. I have to implement in chat app. If user starts typing, then subtitle show. For more see whatsApp. – Ashu Jan 25 '17 at 11:58
  • 3
    title and subtitle slide to centre from left when pushing from navigation controller any workarounds @naveed – Zoom May 30 '17 at 12:06
  • above solution work for centering the both text, in my case its working – Naveed Ahmad May 30 '17 at 19:37
  • Having the same issue as @Zoom, from other posts it looks like it may be related to when in the view lifecycle the title is updated? – Yasir Jul 03 '17 at 16:19
  • @Yasir adding the code for title and subtitle in viewDidAppear worked for me. – Zoom Jul 04 '17 at 05:59
  • My issue was fixed, keeping the initial update coming async from viewDidLoad. It was setting the titleView to an attributed label multiple times as the content changed during transition in. Got it working by only setting the view once, then only updating its contents if already present. – Yasir Jul 04 '17 at 07:42
  • how can i change alignment of both above to be left only – Dilip Tiwari Feb 06 '19 at 06:45
  • i tried titleLabel.textColor = UIColor.white but not working for me – Dilip Tiwari Feb 06 '19 at 06:45
  • I want to text alignment at left, is it possible ? – PK Chahar Sep 21 '22 at 07:10
16

@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
  }
}
Dan
  • 169
  • 1
  • 5
11

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!

AdrianGutierrez
  • 679
  • 1
  • 5
  • 11
5

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;
Nikhil Manapure
  • 3,748
  • 2
  • 30
  • 55
Mubeen Qazi
  • 167
  • 1
  • 8
5

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
    }

}
iosjillian
  • 2,118
  • 1
  • 14
  • 8
2

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

Nick Kirsten
  • 1,187
  • 11
  • 27
2

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

Florian Pfisterer
  • 1,205
  • 12
  • 21
2

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

Mohammad Razipour
  • 3,643
  • 3
  • 29
  • 49
2

Easy fix for iOS 16. Following the @iosjillian's / @Dan's approx, calling layoutSubviews() on stack view does the trick.

extension UINavigationItem {

    func setTitle(_ title: String, subtitle: String) {
        
        let textColor = getTextColor()
        
        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
        stackView.layoutSubviews()
               
        self.titleView = stackView
    }
}
Bienve
  • 21
  • 1
0

Working iOS 16 solution. Swift 5.7

With SnapKit library. If you are not using SnapKit lib, just make both views (titleLabel and subtitleLabel) translatesAutoresizingMaskIntoConstraints = false and replace SnapKit Constraints with native constraints.

func setTitle(title: String, subtitle: String, view: UIView) -> UIView {
    let appearance = UINavigationBar.appearance()
    let titleColor = appearance.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor ?? .black

    let titleLabel = UILabel()

    titleLabel.backgroundColor = UIColor.clear
    titleLabel.textColor = titleColor
    titleLabel.adjustsFontSizeToFitWidth = false
    titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
    titleLabel.lineBreakMode = .byTruncatingTail
    titleLabel.textAlignment = .center
    titleLabel.text = title
    
    let subtitleLabel = UILabel()
    subtitleLabel.backgroundColor = UIColor.clear
    subtitleLabel.textColor = UIColor.init(hexString: "#808890")
    subtitleLabel.adjustsFontSizeToFitWidth = false
    subtitleLabel.lineBreakMode = .byTruncatingTail
    subtitleLabel.textAlignment = .center
    subtitleLabel.font = UIFont.systemFont(ofSize: 11)
    subtitleLabel.text = subtitle
    
    let titleView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
    titleView.addSubview(titleLabel)
    titleView.addSubview(subtitleLabel)
    
    titleLabel.snp.makeConstraints { make in
        make.horizontalEdges.equalToSuperview()
        make.top.equalToSuperview().offset(-20)
        make.height.equalTo(20)
    }

    subtitleLabel.snp.makeConstraints { make in
        make.horizontalEdges.equalToSuperview()
        make.top.equalTo(titleLabel.snp.bottom).offset(4)
        make.height.equalTo(10)
    }
    
    
    
    return titleView
}
Pete Streem
  • 360
  • 5
  • 14