61

Is there a straightforward way of overriding the titleView of the current navigation bar item in a navigation bar within a navigation controller? I've tried creating a new UIView and replacing the titleView property of topView with my own UIVIew with no success.

Basically, I want a multi-line title for the navigation bar title. Any suggestions?

petert
  • 6,672
  • 3
  • 38
  • 46
benasher44
  • 799
  • 1
  • 6
  • 10

11 Answers11

85

Set the titleView property of the UINavigationItem. For example, in the view controller's viewDidLoad method you could do something like:

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 480, 44)];
label.backgroundColor = [UIColor clearColor];
label.numberOfLines = 2;
label.font = [UIFont boldSystemFontOfSize: 14.0f];
label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
label.text = @"This is a\nmultiline string";

self.navigationItem.titleView = label;

#if !__has_feature(objc_arc)
[label release];
#endif

It shows up like this:

multi-line titlebar label

Remember the titleView property is ignored if leftBarButtonItem is not nil.

Community
  • 1
  • 1
petert
  • 6,672
  • 3
  • 38
  • 46
  • 1
    @petert The statement "Remember the titleView property is ignored if leftBarButtonItem is not nil" does not appear to be true, at least in iOS6 using storyboards to create the titleView. – akaru Jan 08 '13 at 00:07
  • @akaru I'll take your word for it using Storyboards; I haven't had time to test it. The documentation still says "This property is ignored if leftBarButtonItem is not nil." – petert Jan 08 '13 at 09:54
  • @petert So what should we do if the leftBarButtonItem is not nil? – Sergey Grischyov Jan 23 '13 at 13:06
  • Behaviour change since my answer - see http://stackoverflow.com/questions/9690409/uinavigationitem-titleview-ignored-if-leftbarbuttonitem-is-set – petert Jan 23 '13 at 15:50
  • 1
    UITextAlignmentCenter is now deprecated use NSTextAlignmentCenter instead – primax79 Dec 15 '14 at 13:16
  • This is multi line, but not centered...see status bar. – Saqib Saud Apr 08 '15 at 07:43
30

for Swift:

let label = UILabel(frame: CGRectMake(0, 0, UIScreen.main.bounds.width, 44))
label.backgroundColor = UIColor.clearColor()
label.numberOfLines = 0
label.textAlignment = NSTextAlignment.Center
label.text = "multiline string"
self.navigationItem.titleView = label

for swift 4:

    let label = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: 44.0))
    label.backgroundColor = UIColor.clear
    label.numberOfLines = 0
    label.textAlignment = NSTextAlignment.center
    label.text = "first line\nsecond line"
    self.navigationItem.titleView = label   
sof98789
  • 1,051
  • 11
  • 17
25

Swift solution:

2 lines in NavigationBar:

private func setupTitleView() {
    let topText = NSLocalizedString("key", comment: "")
    let bottomText = NSLocalizedString("key", comment: "")

    let titleParameters = [NSForegroundColorAttributeName : UIColor.<Color>(),
                           NSFontAttributeName : UIFont.<Font>]
    let subtitleParameters = [NSForegroundColorAttributeName : UIColor.<Color>(),
                              NSFontAttributeName : UIFont.<Font>]

    let title:NSMutableAttributedString = NSMutableAttributedString(string: topText, attributes: titleParameters)
    let subtitle:NSAttributedString = NSAttributedString(string: bottomText, attributes: subtitleParameters)

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

    let size = title.size()

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

    let titleLabel = UILabel(frame: CGRectMake(0,0, width, height))
    titleLabel.attributedText = title
    titleLabel.numberOfLines = 0
    titleLabel.textAlignment = .Center

    navigationItem.titleView = titleLabel
}

2 line in BarButton

    let string = NSLocalizedString("key", comment: "")
    let attributes = [NSForegroundColorAttributeName : UIColor.<Color>,
                      NSFontAttributeName : UIFont.<Font>]
    let size = (string as NSString).sizeWithAttributes(attributes)
    guard let height = navigationController?.navigationBar.frame.size.height else {return}
    let button:UIButton = UIButton(frame: CGRectMake(0, 0, size.width, height))
    button.setAttributedTitle(NSAttributedString(string: string, attributes: attributes), forState: .Normal)
    button.addTarget(self, action: #selector(<SELECTOR>), forControlEvents: .TouchUpInside)
    button.titleLabel?.numberOfLines = 0
    button.titleLabel?.textAlignment = .Right
    let rightBarButton = UIBarButtonItem(customView: button)
    navigationItem.rightBarButtonItem = rightBarButton

result -

enter image description here

hbk
  • 10,908
  • 11
  • 91
  • 124
  • This is a way better solution. I tried to stuff it into an `UINavigationItem` extension method but the problem is getting the navigation bar height from inside there. I could hardcode it. But other than that, this is great. – Isuru Feb 05 '17 at 16:05
  • Spasibo, zemlyak :) – Eugene Gordin Mar 27 '17 at 17:41
7

After a lot of tweaking, I still couldn't get petert's solution to work for me in iOS 8. Here's a copy-paste-able solution for iOS 8/9. Credit goes to Matt Curtis's github post

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    if(!self.navigationItem.titleView){
        self.navigationItem.titleView = ({
            UILabel *titleView = [UILabel new];
            titleView.numberOfLines = 0;
            titleView.textAlignment = NSTextAlignmentCenter;
            titleView.attributedText = [[NSAttributedString alloc] initWithString:@"2\nLINES" attributes:
                self.navigationController.navigationBar.titleTextAttributes
            ];

            [titleView sizeToFit];
            // You'll need to set your frame otherwise if your line breaks aren't explcit.

            titleView;
        });
    }
}
jungledev
  • 4,195
  • 1
  • 37
  • 52
  • Works perfectly. One question... What is the "= ({ });" syntax? I've never seen that before. – Brainware Apr 24 '16 at 21:45
  • @Brainware I'm not sure actually- I tend to test code snippets I find before modifying them.. so as to see if it's worth modifying them. I rarely find snippets like this that work on first try, and this worked perfectly so I didn't bother to change it. Good catch. – jungledev Apr 25 '16 at 02:15
  • 2
    @Brainware I believe the `= ({ });` syntax you mentioned is a closure. These are more commonly seen in functions with callbacks, for example `MyClass.aFunction(x, completionHandler: { (y) -> Void in })`, but closures can actually be used to wrap any text, and allow the wrapped code to be passed like an unnamed variable or function. Jungledev's answer uses a closure to wrap the label creation and pass this 'block' of code to the titleView in a single step. For this reason, closures are called 'blocks' or 'anonymous functions' in other languages. See: https://www.weheartswift.com/closures/. – Natalia May 20 '16 at 10:20
  • @Brainware This is simply a block. In C, blocks can evaluate to their last line. This is mostly used in preprocessor macros when you need to have temporary variables, but can also be used outside of these macros (in this case, for `titleView`). – herzi Jun 22 '16 at 09:50
  • 1
    @NataliaChodelski Actually, this is not a closure. A closure would have to be called like this: `= ^{…; return titleView;}()`. It's a block just like the ones people use as the bodies of their if/for/while/etc.-statements. – herzi Jun 22 '16 at 09:53
  • It's not a block—it's a [GNU C extension called a "statement expression"](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) that got carried over to Clang. Statement expressions are useful for certain macro definitions, but in this case a small private method would be easier to read, document, and test. – Adrian Oct 12 '16 at 15:15
  • 1
    This only works if you hardcode the newline ('\n'), if a long title doesn't contain it, it will still be truncated instead of wrapped over two lines. – koen Feb 24 '17 at 20:34
5

What to do when label is not centered

If you encounter same issue as me - that label is not centered in navigationItem because of back button, embed your UILabel to UIView. UILabel is then not forced to grow with it's text, but stop growing when it's width raise view's width. More about this issue you can find here: Can't set titleView in the center of navigation bar because back button ( Darren's answer )

Not centered:

enter image description here

- (void)setTwoLineTitle:(NSString *)titleText color:(UIColor *)color font:(UIFont *)font {
    CGFloat titleLabelWidth = [UIScreen mainScreen].bounds.size.width/2;

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, titleLabelWidth, 44)];
    label.backgroundColor = [UIColor clearColor];
    label.numberOfLines = 2;
    label.font = font;
    label.adjustsFontSizeToFitWidth = YES;
    label.textAlignment = UITextAlignmentCenter;
    label.textColor = color;
    label.text = titleText;

    self.navigationItem.titleView = label;
}

Centered:

enter image description here

- (void)setTwoLineTitle:(NSString *)titleText color:(UIColor *)color font:(UIFont *)font {
    CGFloat titleLabelWidth = [UIScreen mainScreen].bounds.size.width/2;

    UIView *wrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, titleLabelWidth, 44)];

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, titleLabelWidth, 44)];
    label.backgroundColor = [UIColor clearColor];
    label.numberOfLines = 2;
    label.font = font;
    label.adjustsFontSizeToFitWidth = YES;
    label.textAlignment = UITextAlignmentCenter;
    label.textColor = color;
    label.text = titleText;

    [wrapperView addSubview:label];

    self.navigationItem.titleView = wrapperView;
}
Community
  • 1
  • 1
Vojta
  • 810
  • 10
  • 16
  • I tried this solution and it seems working fine on iOS 10 both portrait and landscape. But it doesn't work well on iOS 11 landscape. I believe it is due to hardcoded height. tested on iPod touch and iPhone 5s, 320x576pt screen. – John Pang Mar 27 '18 at 05:55
3

Here is a Swift 3 version of handling a multiline title:

override func viewDidLoad() {
    super.viewDidLoad()

    let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44))
    label.backgroundColor = .clear
    label.numberOfLines = 0
    label.textAlignment = .center
    label.font = UIFont.boldSystemFont(ofSize: 14.0)
    label.text = "This is a Multi-Line title of UINavigationBar"
    self.navigationItem.titleView = label
}
Community
  • 1
  • 1
Steve Sheets
  • 447
  • 2
  • 10
  • Use the following for dynamic width as per device screen width: let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44)) – Sayalee Pote Sep 12 '17 at 10:11
  • height cannot be hardcoded too. when device rotate to landscape, the height might not 44 anymore. – John Pang Mar 27 '18 at 05:53
3

Here's a Swift 4 way of doing it-

let upperTitle = NSMutableAttributedString(string: "\(text1)", attributes: [NSAttributedStringKey.font: UIFont(name: "SFProDisplay-Heavy", size: 17)!])
let lowerTitle = NSMutableAttributedString(string: "\n\((text2)!)", attributes: [NSAttributedStringKey.font: UIFont(name: "SFProText-Light", size: 11)! , NSAttributedStringKey.foregroundColor: UIColor(hex: "#607D8B")])

upperTitle.append(lowerTitle)

let label1 = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height:44))
label1.numberOfLines = 0
label1.textAlignment = .center
label1.attributedText = upperTitle  //assign it to attributedText instead of text
self.navigationItem.titleView = label1
Anjan Biswas
  • 7,746
  • 5
  • 47
  • 77
3

Swift 4

extension UINavigationItem {
    @objc func setTwoLineTitle(lineOne: String, lineTwo: String) {
        let titleParameters = [NSAttributedStringKey.foregroundColor : UIColor.white,
                               NSAttributedStringKey.font : UIFont.boldSystemFont(ofSize: 17)] as [NSAttributedStringKey : Any]
        let subtitleParameters = [NSAttributedStringKey.foregroundColor : UIColor.flatWhite(),
                                  NSAttributedStringKey.font : UIFont.systemFont(ofSize: 12)] as [NSAttributedStringKey : Any]

        let title:NSMutableAttributedString = NSMutableAttributedString(string: lineOne, attributes: titleParameters)
        let subtitle:NSAttributedString = NSAttributedString(string: lineTwo, attributes: subtitleParameters)

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

        let size = title.size()

        let width = size.width
        let height = CGFloat(44)

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

        titleView = titleLabel
    }
}

Font, color, and navigation bar height are hardcoded here.

Zia
  • 14,622
  • 7
  • 40
  • 59
3

In Swift 5,

    let wrapperView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width * 0.75, height: 44))
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width * 0.75, height: 44))
    label.backgroundColor = .clear
    label.numberOfLines = 2
    label.font = UIFont.boldSystemFont(ofSize: 16.0)
    label.textAlignment = .center
    label.textColor = .white
    label.text = "multi line text"
    wrapperView.addSubview(label)
    self.navigationItem.titleView = wrapperView
Shamshad
  • 129
  • 1
  • 6
1

Most of the solutions, except the one from @gbk, use hardcoded height 44pt for the UIView (wrapper view) and UILabel. All are created by codes. I overlooked @gbk solution which dynamically read height of navigation bar.

I ran into problem when orientation = landscape on iOS 11 (iPhone 5s). The label's height won't adjust and when I set one line of text for landscape, the text align to bottom of navigation bar.

Somehow I found that I can add the UILabel in Storyboard and create an IBOutlet for that. Isn't that nicer?

  1. Add an UIView to navigation bar in storyboard. When dragging it over navigation bar, it will appears as a blue box. If a vertical stroke appears, you are adding it to left/right bar button items array. Note: there can only be ONE UIView. If you add it correctly, it will appear under Navigation Item on the scene panel (on left).
  2. Drag a UILabel into this UIView. enter image description here
  3. Since UIView will have NO SIZE but centralized in navigation bar, you cannot add four zero's constraint. Just add two constraints to the UILabel so it position in the center of superview: Align Center X and Y to Superview.
  4. Configure UILabel as usual. For multiple line, I set number of lines to zero (0).
  5. Create an IBOutlet on your view controller and you can use it as usual. To have different size of text, use attribute string (lots of solutions above).

I tested on iPhone 5s with iOS 11.2.6 and the text just position in center with no problem, work fine on portrait and landscape.

John Pang
  • 2,403
  • 25
  • 25
0

Swift 5+ https://stackoverflow.com/a/68739808/6881070

very easy and smooth solution in one function link is mention

Shakeel Ahmed
  • 5,361
  • 1
  • 43
  • 34