30
votes

I am in process of adding large title in navigation bar in one of the application. The issue is title is little long so I will require to add two lines in large title. How can I add large title with two lines in navigation bar?

This is not about default navigation bar title! This is about large title which is introduced in iOS 11. So make sure you add suggestions by considering large title. Thanks

Title text truncated with 3 dots in the navigation bar

7
Put a label in a UIView set number of lines as 0 and then add the view as Navigation bar's TitleView. Problem solvedSandeep Bhandari
@SandeepBhandari Does this behave properly (like the original title label) with varying navigation bar items: Length of back button label, left/right bar items?meaning-matters
@meaning-matters : You can always specify the frame for titleView if it overlaps with left/right bar button items :)Sandeep Bhandari
Try that in large title ( new feature of iOS 11 if you are now aware!)Jigar Thakkar
@Richie Rich this is not duplicate , try to read description one more time :)Jigar Thakkar

7 Answers

15
votes

Based in @krunal answer, this is working for me:

extension UIViewController {

func setupNavigationMultilineTitle() {
    guard let navigationBar = self.navigationController?.navigationBar else { return }
    for sview in navigationBar.subviews {
        for ssview in sview.subviews {
            guard let label = ssview as? UILabel else { break }
            if label.text == self.title {
                label.numberOfLines = 0
                label.lineBreakMode = .byWordWrapping
                label.sizeToFit()
                UIView.animate(withDuration: 0.3, animations: {
                    navigationBar.frame.size.height = 57 + label.frame.height
                })
            }
        }
    }
}

In the UIViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    self.title = "This is a multiline title"
    setupNavigationMultilineTitle()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    setupNavigationMultilineTitle()
}

And for setting font and color on the large title:

navigation.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: .red, NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 30)]
14
votes

Get a navigation item subviews and locate UILabel from it.

Try this and see:

self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .automatic

self.title = "This is multiline title for navigation bar"
self.navigationController?.navigationBar.largeTitleTextAttributes = [                     
                                NSAttributedStringKey.foregroundColor: UIColor.black,
                                NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: .largeTitle)
                                ]

for navItem in(self.navigationController?.navigationBar.subviews)! {
     for itemSubView in navItem.subviews { 
         if let largeLabel = itemSubView as? UILabel {
             largeLabel.text = self.title
             largeLabel.numberOfLines = 0
             largeLabel.lineBreakMode = .byWordWrapping
         }
     }
}

Here is result:

enter image description here

3
votes

The linebreak solution seems to be problematic when there's a back button. So instead of breaking lines, I made the label auto adjust font.

func setupLargeTitleAutoAdjustFont() {
    guard let navigationBar = navigationController?.navigationBar else {
        return
    }
    // recursively find the label
    func findLabel(in view: UIView) -> UILabel? {
        if view.subviews.count > 0 {
            for subview in view.subviews {
                if let label = findLabel(in: subview) {
                    return label
                }
            }
        }
        return view as? UILabel
    }

    if let label = findLabel(in: navigationBar) {
        if label.text == self.title {
            label.adjustsFontSizeToFitWidth = true
            label.minimumScaleFactor = 0.7
        }
    }
}

Then it needs to be called in viewDidLayoutSubviews() to make sure the label can be found, and we only need to call it once:

private lazy var setupLargeTitleLabelOnce: Void = {[unowned self] in
    if #available(iOS 11.0, *) {
        self.setupLargeTitleAutoAdjustFont()
    }
}()

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let _ = setupLargeTitleLabelOnce
}

If there's any navigationController pop event back to this controller, we need to call it again in viewDidAppear(). I haven't found a better solution for this - there's a small glitch of label font changing when coming back from a pop event:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if #available(iOS 11.0, *) {
        setupLargeTitleAutoAdjustFont()
    }
}
0
votes

Swift 4 : Multi line even though the sentence is only short

title = "You're \nWelcome"

for navItem in(self.navigationController?.navigationBar.subviews)! {
     for itemSubView in navItem.subviews { 
         if let largeLabel = itemSubView as? UILabel {
             largeLabel.text = self.title
             largeLabel.numberOfLines = 0
             largeLabel.lineBreakMode = .byWordWrapping
         }
     }
}

Proof

0
votes

If anyone looking for Title Lable Not Large Title, then below code is working.

Swift 5.X

func setMultilineNavigationBar(topText:  String, bottomText : String) {
     let topTxt = NSLocalizedString(topText, comment: "")
     let bottomTxt = NSLocalizedString(bottomText, comment: "")

     let titleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
                               NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .semibold)]
     let subtitleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
                                  NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13, weight: .regular)]

     let title:NSMutableAttributedString = NSMutableAttributedString(string: topTxt, attributes: titleParameters)
     let subtitle:NSAttributedString = NSAttributedString(string: bottomTxt, attributes: subtitleParameters)

     title.append(NSAttributedString(string: "\n"))
     title.append(subtitle)

     let size = title.size()

     let width = size.width
     guard let height = navigationController?.navigationBar.frame.size.height else {return}

      let titleLabel = UILabel(frame: CGRect.init(x: 0, y: 0, width: width, height: height))
      titleLabel.attributedText = title
      titleLabel.numberOfLines = 0
      titleLabel.textAlignment = .center
      self.navigationItem.titleView = titleLabel 
    }

This is working fine for me. Hope this function will help you.

Happy Coding :)

0
votes

(Edit 7/13: I notice that this solution is not support scrollView, so now I'm in research)

I found a perfect solution on Swift5

but sorry for my poor English because I'm Japanese🇯🇵Student.

In case of 2 lines In case of 3 lines

At first, set navigation settings for largeTitle normally in viewDidLoad

//Set largeTitle
navigationItem.largeTitleDisplayMode = .automatic
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]//ex) fontSize=26, margin=5, numberOfLines=2
        
//Set title
title = "multiple large\ntitle is working!"

It is most important point of this solution that font-size at largeTitleTextAttributes equals actual font-size(+margin) multiplied by number of lines.

Description image

Because, default specification of navigationBar attributes may be able to display only 1 line largeTitle.

Although, somehow, I did notice that in case of label-settings(the label which subview of subview of navigationBar) on direct, it can display any number of lines in 1 line of in case of navigationBar attributes.

So, we should do set big font in navigationbar attributes, and set small font in the label(subview of subview of navigationBar), and take into consideration the margins.

Do label settings direct in viewDidAppear like this:

//Find label
navigationController?.navigationBar.subviews.forEach({ subview in
        subview.subviews.forEach { subsubview in
        guard let label: UILabel = subsubview as? UILabel else { return }
        //Label settings on direct.
        label.text = title
        label.font = UIFont.systemFont(ofSize: fontSize)
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.sizeToFit()
    }
})

Therefore, in short, the solution at minimum code is given like this:

import UIKit

class ViewController: UIViewController {
    
    private let fontSize: CGFloat = 26, margin: CGFloat = 5
    private let numberOfLines: CGFloat = 2

    override func viewDidLoad() {
        super.viewDidLoad()

        setUpNavigation()
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        setMultipleLargeTitle()
    }
    private func setUpNavigation() {
        //Set largeTitle
        navigationItem.largeTitleDisplayMode = .automatic
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]
        
        //Set title
        title = "multiple large\ntitle is working!"
    }
    private func setMultipleLargeTitle() {
        //Find label
        navigationController?.navigationBar.subviews.forEach({ subview in
            subview.subviews.forEach { subsubview in
                guard let label: UILabel = subsubview as? UILabel else { return }
                //Label settings on direct.
                label.text = title
                label.font = UIFont.systemFont(ofSize: fontSize)
                label.numberOfLines = 0
                label.lineBreakMode = .byWordWrapping
                label.sizeToFit()
            }
        })
    }
}

thank you for reading :)

-2
votes

Here you can add a multiline UILabel in the NavigationTitle, You can do it by some kind of customisation in your code and put the UILabel on self.navigationItem.titleView

    let label = UILabel()
    label.backgroundColor = .clear
    label.numberOfLines = 2
    label.font = UIFont(name: "Montserrat-Regular", size: 16.0)!
    label.textAlignment = .center
    label.textColor = .white
    label.text = "FIFA" + " \n " + "Europe 2018-2019"  
    self.navigationItem.titleView = label

Cheers have a good day.

enter image description here