18

I have a custom UINavigationBar title and a custom back button. My problem is that the title is not centered on the iPhone. It is as if my back button is pushing the title over to the right. Any Idea how I can center it?

int height = self.navigationController.navigationBar.frame.size.height;
int width = self.navigationController.navigationBar.frame.size.width;


UILabel *navLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width + 300, 20)];
navLabel.backgroundColor = [UIColor whiteColor];
navLabel.textColor = [UIColor redColor];
navLabel.font = [UIFont fontWithName:@"Helvetica" size:30];
navLabel.textAlignment = UITextAlignmentCenter;
self.navigationItem.titleView = navLabel;
[navLabel release];
((UILabel *)self.navigationItem.titleView).text = self.title;

Thanks!

edit I removed superfluous code and added this picture:

Picture of title off ceter

Notice how the title is pushed over to accomodate the button....

itgiawa
  • 1,616
  • 4
  • 16
  • 28
  • Seems to be a diff problem. My title is vertically centered correctly. Its not horizontally centered correctly (the button moves it over). If I remove the button everything is centered perfectly... – itgiawa Mar 29 '12 at 19:27

7 Answers7

42

iOS is doing this because the frame you initialize is alway 300+width pixels. It is trying to center the full frame, the frame is larger than the space it wants to fit it in (because of the button) and therefore your label gets pushed to the right. What you need to do is give the Frame of the navLabel the minimum size it needs.

So if your text is only 100px wide, but the frame is 400px, then iOS is trying to center the 400px inside the Navigation header, and doesn't have enough space. When you set the size to the actual 100px that is needed, iOS will center your header correctly, because there is plenty of space to center 100px.

The code snippet below should help you to detect the minimum size your frame needs, depending on the font and the text you try to put in. Make sure the frame of the label is as small as possible, but does not exceed the max width. (the width of the navigation bar).

UIFont* titleFont = [UIFont fontWithName:@"Helvetica" size:30];
CGSize requestedTitleSize = [titleText sizeWithAttributes:@{NSFontAttributeName: titleFont}]; 
CGFloat titleWidth = MIN(maxTitleWidth, requestedTitleSize.width);

UILabel *navLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, titleWidth, 20)];
navLabel.backgroundColor = [UIColor whiteColor];
navLabel.textColor = [UIColor redColor];
navLabel.font = [UIFont fontWithName:@"Helvetica" size:30];
navLabel.textAlignment = NSTextAlignmentCenter;
navLabel.text = titleText;
self.navigationItem.titleView = navLabel;

Swift 5.0

    let titleFont = UIFont.systemFont(ofSize: 17.0)
    let title = "My Title"
    let titleSize = title.size(withAttributes: [.font: titleFont])

    let frame = CGRect(x: 0, y: 0, width: titleSize.width, height: 20.0)
    let titleLabel = UILabel(frame: frame)
    titleLabel.font = titleFont
    titleLabel.textColor = .red
    titleLabel.textAlignment = .center
    titleLabel.text = title
    navigationItem.titleView = titleLabel
Bocaxica
  • 3,911
  • 3
  • 37
  • 54
  • 1
    Note in iOS 6 and above UITextAlignmentCenter has been deprecated. You can use NSTextAlignmentCenter instead. – mohlman3 Aug 31 '14 at 19:35
  • self.navigationItem.titleView - perfect! – hbk Sep 26 '14 at 14:09
  • But if there is enough text such that width == maxTitleWidth then we would get the problem again, right? Wouldn't the real solution be to subtract the width of the back button (*2)from frame's width. – User Feb 06 '15 at 15:31
  • @lxx: I don't believe so. iOS SDK figures out for us how to center it's navigationItem.titleView, so we do not need to worry about frames and button sizes. – Bocaxica Jul 12 '15 at 16:03
5

I had the same problem before. I had a UINavigationbar with right- and left-button. I want to center an image on the UINavigationbar. So I had to put the image into an UIView with width 0. The image self get a x-value which is half of own width.

UINavigationItem *item = navigationController.topViewController.navigationItem;
UIView *backView =[[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 30)];
[img_logo setFrame:CGRectMake(-45, 5, 90, 20)];
[backView addSubview:img_logo];
item.titleView = backView;
BHuelse
  • 2,889
  • 3
  • 30
  • 41
  • Surprised this works, but it worked nicely for me. Interesting trick to know to be able to "hide" a view inside a zero width view. – anorskdev May 22 '17 at 22:47
3

Nice answer! However sizeWithFont is now deprecated. You would want to do something like this now.

NSDictionary *textTitleOptions = [NSDictionary dictionaryWithObjectsAndKeys:  [UIFont fontWithName:@"Helvetica" size:30], NSFontAttributeName, nil];

CGSize requestedTitleSize = [[NSString stringWithFormat:@"Your String"] sizeWithAttributes:textTitleOptions];

CGFloat titleWidth = MIN(self.view.frame.size.width, requestedTitleSize.width);

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, titleWidth, 44)];
Luke
  • 612
  • 1
  • 6
  • 19
2

I used autolayout constraints to solve this issue:

  1. Add UILabel into a UIView.
  2. Set constraints of centerX for the UILabel:

    self.titleLabelCenterXConstraint = [NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f];

  3. In the UIView layoutSubViews to adjust the centerX offset value:

    - (void)layoutSubviews { [super layoutSubviews]; CGFloat screenCenter = CGRectGetMidX([UIScreen mainScreen].bounds); CGFloat diffCenter = screenCenter - CGRectGetMidX(self.frame); self.titleLabelCenterXConstraint.constant = diffCenter; }

  4. set UIView to navigationItem titleView

Yang Young
  • 602
  • 5
  • 6
  • Tried that. Did not work.Title was not moving. Offset was calculated correctly, as it seems. – osxdirk Sep 06 '16 at 13:14
  • 1
    Works like a charm! – odm Mar 24 '17 at 18:34
  • Updated for swift 5 ``` override func layoutSubviews() { super.layoutSubviews() let screenCenter = UIScreen.main.bounds.midX let diffCenter = screenCenter - self.frame.midX self.centerConstraint.constant = -diffCenter } ``` – J T Apr 27 '20 at 23:26
1

Swift 2.3:

    let tlabel = UILabel(frame: CGRectMake(0, 0, 200, 40))
    tlabel.text = self.title
    tlabel.textColor = UIColor.whiteColor()
    tlabel.font = UIFont.boldSystemFontOfSize(17) //UIFont(name: "Helvetica", size: 17.0)
    tlabel.backgroundColor = UIColor.clearColor()
    tlabel.adjustsFontSizeToFitWidth = true
    tlabel.textAlignment = .Center
    self.navigationItem.titleView = tlabel
Unit Testing
  • 261
  • 3
  • 5
1

Swift 3 for @BHuelse's answer:

let image = UIImage(named: "logo")
let logoView = UIView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 30))
let imageView = UIImageView(frame: CGRect(x: -45, y: 5, width: 90, height: 20))
imageView.image = image
imageView.contentMode = .scaleAspectFit
logoView.addSubview(imageView)
self.navigationItem.titleView = logoView
Guillermo Aguirre
  • 743
  • 1
  • 8
  • 16
0

The title is not centered in the navbar itself. It is centered between the left buttons and the right buttons of the navbar. This means that if you have a big button on the left, the title will be shifted to the right.

You can change the center by adding a centered constraint to your title and then modifying it so it's really the center of the navbar :

// Position the title in the middle of the navbar and not in the middle of buttons
fileprivate func centerTitle () {
    if let navigation = self.navigationController {
        let titleMiddle = navigation.navigationBar.convert(titleViewLabel.frame, from: titleViewLabel.superview).midX
        let diff = navigation.navigationBar.center.x - titleMiddle
        constraintTitleViewCentered.constant += diff
    }
}
CedricSoubrie
  • 6,657
  • 2
  • 39
  • 44