UINavigationBar TitleView with subtitle
Asked Answered
B

9

14

I want a titleView inside my UINavigationBar which has two lines of text instead of only one

My current implementiation works well when I have a "Back"-Button and an "Edit" Button because it looks nearly centered. The problem is when I only have one of the Buttons. It looks really weird and wrong because the view centers itself on the available space.

UINavigationBar with left and right Button

UINavigationBar with only one Button

This is my current implementation:

CGRect frame = self.navigationController.navigationBar.frame;

UIView *twoLineTitleView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth(frame), 0, CGRectGetWidth(frame), CGRectGetHeight(frame))];

UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 8, CGRectGetWidth(frame), 14)];
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
titleLabel.textAlignment = UITextAlignmentCenter;
titleLabel.text = self.title;
[twoLineTitleView addSubview:titleLabel];

UILabel *subTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 23, CGRectGetWidth(frame), 14)];
subTitleLabel.backgroundColor = [UIColor clearColor];
subTitleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
subTitleLabel.textAlignment = UITextAlignmentCenter;
subTitleLabel.text = self.subTitle;
[twoLineTitleView addSubview:subTitleLabel];

self.navigationItem.titleView = twoLineTitleView;
Bromal answered 16/10, 2012 at 11:46 Comment(1)
It will help you all existing scenario #37409760Disused
R
29

Here is how I would do it:

  • Use sizeToFit for each of your two labels.
  • Have the container view's width set to the max width of your two labels.
  • Center the smaller of the two labels on the view.

This code should work for you:

    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
    titleLabel.backgroundColor = [UIColor clearColor];
    titleLabel.textColor = [UIColor whiteColor];
    titleLabel.font = [UIFont boldSystemFontOfSize:20];
    titleLabel.text = @"Your Title";
    [titleLabel sizeToFit];

    UILabel *subTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 22, 0, 0)];
    subTitleLabel.backgroundColor = [UIColor clearColor];
    subTitleLabel.textColor = [UIColor whiteColor];
    subTitleLabel.font = [UIFont systemFontOfSize:12];
    subTitleLabel.text = @"Your subtitle";
    [subTitleLabel sizeToFit];

    UIView *twoLineTitleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, MAX(subTitleLabel.frame.size.width, titleLabel.frame.size.width), 30)];
    [twoLineTitleView addSubview:titleLabel];
    [twoLineTitleView addSubview:subTitleLabel];

    float widthDiff = subTitleLabel.frame.size.width - titleLabel.frame.size.width;

    if (widthDiff > 0) {
        CGRect frame = titleLabel.frame;
        frame.origin.x = widthDiff / 2;
        titleLabel.frame = CGRectIntegral(frame);
    }else{
        CGRect frame = subTitleLabel.frame;
        frame.origin.x = abs(widthDiff) / 2;
        subTitleLabel.frame = CGRectIntegral(frame);
    }

    self.navigationItem.titleView = twoLineTitleView;

Swift 3 variant:

    let titleLabel = UILabel.init(frame: CGRect.zero)
    titleLabel.backgroundColor = UIColor.clear
    titleLabel.textColor = UIColor.schBlack
    titleLabel.font = UIFont.schTextStyle2Font()
    titleLabel.text = titleString;
    titleLabel.sizeToFit()

    let subTitleLabel = UILabel.init(frame: CGRect.init(x: 0, y: 22, width: 0, height: 0))
    subTitleLabel.backgroundColor = UIColor.clear
    subTitleLabel.textColor = UIColor.schBrownishGrey
    subTitleLabel.font = UIFont.schTextStyle3Font()
    subTitleLabel.text = subTitleString;
    subTitleLabel.sizeToFit()

    let twoLineTitleView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.view.frame.width-40, height: 30))
    twoLineTitleView.addSubview(titleLabel)
    twoLineTitleView.addSubview(subTitleLabel)

    /*

    float widthDiff = subTitleLabel.frame.size.width - titleLabel.frame.size.width;
    if (widthDiff > 0) {
        CGRect frame = titleLabel.frame;
        frame.origin.x = widthDiff / 2;
        titleLabel.frame = CGRectIntegral(frame);
    }else{
        CGRect frame = subTitleLabel.frame;
        frame.origin.x = abs(widthDiff) / 2;
        subTitleLabel.frame = CGRectIntegral(frame);
    }
    */

    self.navigationItem.titleView = twoLineTitleView;
Revegetate answered 21/4, 2014 at 12:17 Comment(2)
Is it possible to use Auto Layout for these two labels?Gump
@Gump (for people googling this) You can for the labels, although a stack view is probably your best bet for a wrapper, you can set the alignment and distribution to .fill and set height constraints on the labels. The stackView will be resized by the navbar.Accelerando
S
11

An alternative approach may be to use a prompt.

Shonda answered 16/10, 2012 at 12:40 Comment(3)
Yes. Using the prompt string is a good solution for many basic use cases. Totally free and just works.Adjournment
@lostintranslation I assume you mean above the navigation title...yes, that is what prompt does. If you're looking for something else then a prompt is not the right solution for you.Shonda
@Shonda correct and agree. ser asked for subtitle, which is not what apple intends when using promptPoirer
R
9

A few additions to Brian van den hovel's answer to better centralise title and subtitle.

func setTitle(title:String, subtitle:String) -> UIView {
    //Create a label programmatically and give it its property's
    let titleLabel = UILabel(frame: CGRectMake(0, 0, 0, 0)) //x, y, width, height where y is to offset from the view center
    titleLabel.backgroundColor = UIColor.clearColor()
    titleLabel.textColor = UIColor.blackColor()
    titleLabel.font = UIFont.boldSystemFontOfSize(17)
    titleLabel.text = title
    titleLabel.sizeToFit()

    //Create a label for the Subtitle
    let subtitleLabel = UILabel(frame: CGRectMake(0, 18, 0, 0))
    subtitleLabel.backgroundColor = UIColor.clearColor()
    subtitleLabel.textColor = UIColor.lightGrayColor()
    subtitleLabel.font = UIFont.systemFontOfSize(12)
    subtitleLabel.text = subtitle
    subtitleLabel.sizeToFit()

    // Create a view and add titleLabel and subtitleLabel as subviews setting
    let titleView = UIView(frame: CGRectMake(0, 0, max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), 30))

    // Center title or subtitle on screen (depending on which is larger)
    if titleLabel.frame.width >= subtitleLabel.frame.width {
        var adjustment = subtitleLabel.frame
        adjustment.origin.x = titleView.frame.origin.x + (titleView.frame.width/2) - (subtitleLabel.frame.width/2)
        subtitleLabel.frame = adjustment
    } else {
        var adjustment = titleLabel.frame
        adjustment.origin.x = titleView.frame.origin.x + (titleView.frame.width/2) - (titleLabel.frame.width/2)
        titleLabel.frame = adjustment
    }

    titleView.addSubview(titleLabel)
    titleView.addSubview(subtitleLabel)

    return titleView
}
Rhetoric answered 9/12, 2015 at 5:29 Comment(4)
I have found this solution works best. Other solutions here will fail when the titleLabel is longer than a certain amount of characters and will drop over the subtitleLabel for some reason. This solution, although not perfect (long titleLabels will run off the screen) is the best I have seen so far.Identic
When i call this function inside a closure, it shows title and subtitle at very left then it moves them to center in a second, which looks bad, how to fix it?Autogiro
I would love a solution that addresses the two issues mentioned in the commentsHylotheism
Works perfect to me !! Thanks!Telpherage
S
9

You can also use an attributed string to achieve the same result:

let subtitleAttribute = [NSForegroundColorAttributeName: UIColor.grayColor() , NSFontAttributeName: UIFont.systemFontOfSize(12.0)]
let titleString = NSMutableAttributedString(string: "title" + "\n", attributes: [NSFontAttributeName: UIFont.boldSystemFontOfSize(17.0)])
let subtitleString = NSAttributedString(string: "subtitle", attributes: subtitleAttribute)
titleString.appendAttributedString(subtitleString)

let label = UILabel(frame: CGRectMake(0, 0, titleString.size().width, 44))
label.numberOfLines = 0
label.textAlignment = NSTextAlignment.Center
label.attributedText = titleString
self.navigationItem.titleView = label

Swift 3

let subtitleAttribute = [NSForegroundColorAttributeName: UIColor.gray , NSFontAttributeName: UIFont.systemFont(ofSize: 12.0)]
let titleString = NSMutableAttributedString(string: "title" + "\n", attributes: [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 17.0)])
let subtitleString = NSAttributedString(string: "subtitle", attributes: subtitleAttribute)
titleString.append(subtitleString)

let label = UILabel(frame: CGRect(x: 0, y: 0, width: titleString.size().width, height: 44))
label.numberOfLines = 0
label.textAlignment = NSTextAlignment.center
label.attributedText = titleString
self.navigationItem.titleView = label
Sisile answered 25/8, 2016 at 15:43 Comment(0)
A
3

In addition to the github! here.

I found that when my subtitle was smaller than my Title it would not format it correctly here is the right code.

//Usage:
self.navigationItem.titleView = setTitle(varDec.currentDate(), subtitle: varDec.currentDay())

 func setTitle(title:String, subtitle:String) -> UIView {
    //Create a label programmatically and give it its property's
    let titleLabel = UILabel(frame: CGRectMake(0, -5, 0, 0)) //x, y, width, height where y is to offset from the view center
    titleLabel.backgroundColor = UIColor.clearColor()
    titleLabel.textColor = UIColor.blackColor()
    titleLabel.font = UIFont.boldSystemFontOfSize(17)
    titleLabel.text = title
    titleLabel.sizeToFit()

    //Create a label for the Subtitle
    // x, y, width, height where x is set to be half the size of the title (100/4 = 25%) as it starts all the way left.
    let subtitleLabel = UILabel(frame: CGRectMake(titleLabel.frame.size.width / 4, 18, 0, 0)) 
    subtitleLabel.backgroundColor = UIColor.clearColor()
    subtitleLabel.textColor = UIColor.lightGrayColor()
    subtitleLabel.font = UIFont.systemFontOfSize(12)
    subtitleLabel.text = subtitle
    subtitleLabel.sizeToFit()

    /*Create a view and add titleLabel and subtitleLabel as subviews setting 
     * its width to the bigger of both
     * this will crash the program if subtitle is bigger then title
     */
    let titleView = UIView(frame: CGRectMake(0, 0, max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), 30))
    titleView.addSubview(titleLabel)
    titleView.addSubview(subtitleLabel)


    return titleView
}
Alice answered 24/9, 2015 at 9:55 Comment(0)
G
2

https://www.reddit.com/r/swift/comments/3izq7b/style_guidelines_for_navigation_bar_prompts/

extension UINavigationItem {

    //Make the title 2 lines with a title and a subtitle
    func addTitleAndSubtitleToNavigationBar (title: String, subtitle: String) {
        var label = UILabel(frame: CGRectMake(10.0, 0.0, 50.0, 40.0))
        label.font = UIFont.boldSystemFontOfSize(14.0)
        label.numberOfLines = 2
        label.text = "\(title)\n\(subtitle)"
        label.textColor = UIColor.blackColor()
        label.sizeToFit()
        label.textAlignment = NSTextAlignment.Center
        self.titleView = label
    }
}
Guthrun answered 29/6, 2016 at 16:58 Comment(0)
C
1

I did this by completely replacing the title view. The end result looks like this:

enter image description here

It can be achieved with the following code in the viewDidLoad method.

UIView * containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 220, 40)];

UILabel * titleLabel = [[UILabel alloc] init];
titleLabel.text = @"Ttitle";
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.font = [UIFont boldSystemFontOfSize:titleLabel.font.pointSize];

[containerView addSubview:titleLabel];
titleLabel.keepInsets.equal = 0;
titleLabel.keepBottomInset.equal = 10;

UILabel * subtitleLabel = [[UILabel alloc] init];
subtitleLabel.textAlignment = NSTextAlignmentCenter;
subtitleLabel.font = [UIFont italicSystemFontOfSize:12.0];
subtitleLabel.textColor = [UIColor lightGrayColor];

[containerView addSubview:subtitleLabel];

subtitleLabel.keepHeight.equal = 15;
subtitleLabel.keepWidth.equal = 200;
subtitleLabel.keepBottomInset.equal = 0;
subtitleLabel.keepHorizontalCenter.equal = 0.5;

subtitleLabel.text = @"Subtitle";

[self.navigationItem setTitleView:containerView];

To do this I used the excellent KeepLayout library. It yields a result like this:

Contentious answered 12/9, 2016 at 10:17 Comment(0)
H
0

I was using Rodrigo Pedroso's answer, but the fact that it doesn't truncate long titles became a problem. I made a version that measures how much space there is for the title between the bar button items, and then manually truncates the title to fit in that space. The truncation code was adapted from this answer, and is a bit of a mess - maybe someone can help figure out a way to use UILabel's built-in truncation methods (i.e. .ByTruncatingTail). I've already spent too much time on this though.

Here's what I've got:

extension UIViewController {

    private var availableWidthForTitle: CGFloat {
        get {
            var total = UIScreen.mainScreen().bounds.width
            if let navigationBar = navigationController?.navigationBar {
                var maxYLeft:  CGFloat = 0
                var minYRight: CGFloat = 0
                for subview in navigationBar.subviews.dropFirst()
                    where !subview.hidden {
                    if subview.frame.origin.x < total / 2 {
                        let rightEdge = subview.frame.origin.x + subview.frame.size.width
                        if rightEdge < total / 2 {
                            maxYLeft = max(maxYLeft, rightEdge)
                        }
                    } else {
                        let leftEdge = subview.frame.origin.x
                        if leftEdge > total / 2 {
                            minYRight = min(minYRight, total - leftEdge)
                        }
                    }
                }
                total = total - maxYLeft - minYRight
            }

            return total
        }
    }

    func setTitle(title:String, subtitle:String?) {
        if (subtitle == nil) {
            self.title = title.uppercaseString
        } else {
            let titleLabel = UILabel(frame: CGRectMake(0, -2, 0, 0))
            titleLabel.backgroundColor = UIColor.clearColor()
            titleLabel.textColor = MaterialColor.white
            titleLabel.font = RobotoFont.boldWithSize(17)
            titleLabel.textAlignment = .Center
            let availableWidth = availableWidthForTitle
            let titleUppercase = NSString(string: title.uppercaseString)
            if titleUppercase.boundingRectWithSize(
                // if title needs to be truncated
                CGSize(width: CGFloat.max, height: CGFloat.max),
                options: NSStringDrawingOptions.UsesLineFragmentOrigin,
                attributes: [NSFontAttributeName : titleLabel.font],
                context: nil).width > availableWidth {
                let truncTitle: NSMutableString = ""
                for nextChar in title.uppercaseString.characters {
                    if truncTitle.boundingRectWithSize(
                        CGSize(width: CGFloat.max, height: CGFloat.max),
                        options: NSStringDrawingOptions.UsesLineFragmentOrigin,
                        attributes: [NSFontAttributeName : titleLabel.font],
                        context: nil).width > availableWidth - 16 { // for ellipsis + some margin
                        truncTitle.appendString("...")
                        break
                    } else {
                        truncTitle.appendString(String(nextChar))
                    }
                }
                titleLabel.text = truncTitle as String
            } else {
                // use title as-is
                titleLabel.text = titleUppercase as String
            }
            titleLabel.sizeToFit()
            let subtitleLabel = UILabel(frame: CGRectMake(0, 18, 0, 0))
            subtitleLabel.backgroundColor = UIColor.clearColor()
            subtitleLabel.textColor = MaterialColor.white
            subtitleLabel.font = MaterialFont.italicSystemFontWithSize(10)
            subtitleLabel.text = subtitle
            subtitleLabel.sizeToFit()

            let titleView = UIView(frame: CGRectMake(0, 0, max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), 30))
            // Center title or subtitle on screen (depending on which is larger)
            if titleLabel.frame.width >= subtitleLabel.frame.width {
                var adjustment = subtitleLabel.frame
                adjustment.origin.x = titleView.frame.origin.x + (titleView.frame.width/2) - (subtitleLabel.frame.width/2)
                subtitleLabel.frame = adjustment
            } else {
                var adjustment = titleLabel.frame
                adjustment.origin.x = titleView.frame.origin.x + (titleView.frame.width/2) - (titleLabel.frame.width/2)
                titleLabel.frame = adjustment
            }

            titleView.addSubview(titleLabel)
            titleView.addSubview(subtitleLabel)
            self.navigationItem.titleView = titleView
        }
    }
}
Hylotheism answered 2/8, 2016 at 10:2 Comment(0)
N
0

Inspired by @Ben Smiley, I added a masonry implementation.

CGFloat width = 200;
CGFloat titleHeight = 25;
CGFloat containerHeight = 40;

UIView * containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, containerHeight)];
UILabel * titleLabel = [[UILabel alloc] init];
titleLabel.text = @"Title";
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.font = [UIFont boldSystemFontOfSize:titleLabel.font.pointSize];

[containerView addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(0);
    make.left.mas_equalTo(0);
    make.right.mas_equalTo(0);
    make.width.mas_equalTo(width);
    make.height.mas_equalTo(titleHeight);

}];

UILabel * subtitleLabel = [[UILabel alloc] init];
subtitleLabel.textAlignment = NSTextAlignmentCenter;
subtitleLabel.font = [UIFont systemFontOfSize:12];

[containerView addSubview:subtitleLabel];

[subtitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.mas_equalTo(0);
    make.right.mas_equalTo(0);
    make.top.mas_equalTo(titleLabel.mas_bottom);
    make.width.mas_equalTo(width);
    make.height.mas_equalTo(containerHeight - titleHeight);

}];

subtitleLabel.text = @"Subtitle";

[self.navigationItem setTitleView:containerView];
Natika answered 10/4, 2017 at 8:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.