77

Seeing a behavior on iOS11 with a navigationItem.titleView where the width of the titleView is not the full width of the screen.

I have a custom view that I set as the titleView. Previous to iOS11 the view would fill the navigation bar area. But iOS 11 it is not resizing to fill the width of the screen.

I've tried setting the frame of the view before setting titleView but no luck. I've tried to force the titleViews superview to layout constraints as well but no luck.

Screenshots attached:

iOS10:

enter image description here

iOS11:

enter image description here

Anyone else experience this?

gngrwzrd
  • 5,902
  • 4
  • 43
  • 56

15 Answers15

143

I figured it out. I had to override the intrinsicContentSize getter for the view, and the text field.

I set the width to CGFloat.greatestFiniteMagnitude so it'll always be as wide as the screen.

Update:

Since I've spent couple of hours on this issue, hope that some else will catch up faster by having all things tight up together

I've created a custom sub class of TitleView, called CustomTitleView, here's the code:

import UIKit

class CustomTitleView: UIView {

  override var intrinsicContentSize: CGSize {
    return UIView.layoutFittingExpandedSize
  }
}

and the most important part which I missed from the start was this:

enter image description here

Kqtr
  • 5,824
  • 3
  • 25
  • 32
gngrwzrd
  • 5,902
  • 4
  • 43
  • 56
  • 5
    As an explanation: the titleView is now laid out with Auto Layout. Since it looks for the intrinsicContentSize, this is what worked. – mangerlahn Aug 18 '17 at 13:04
  • Can be the title view showed below the large title view? I assigned a segmentedControl for title view, and it always showed upon the large title. – Arco Aug 25 '17 at 06:51
  • @Arco: Have you solved your issue? I have the same issue with `UISegmentedControl` placed inside `TitleView` and the touch doesn't responds, re-attached & subclassed the `UIView` class of the `TitleView` which overrides the `intrinsicContentSize ` property as `João Nunes` mentioned in below answer. – el.severo Oct 19 '17 at 12:33
  • @el.severo I don't solve my question because I think there is no public api for us to assign a custom view on large title view place. Maybe you can see the navigation bar subviews structure and try to use Objective-C runtime to solve your problem. – Arco Oct 19 '17 at 13:55
  • @Arco: Thanks but I've managed to solve it, I just edited this answer and provided extra info, hope some else will benefit from it. – el.severo Oct 19 '17 at 14:12
  • 2
    The problem is that it also pushes away from the screen left and right nav buttons – RealNmae Dec 20 '17 at 12:41
  • How/where did you set the width? By resetting the frame? – Michael McKenna May 20 '19 at 00:11
  • Interesting. It works! I was trying to include a "spacer" view as the last arranged subview that would have a width constraint to CGFloat.greatestFiniteMagnitude and the stackview distribution as .fill in order to fill up any remaining space. In my case it stopped working when there were more than 1 right bar button items. – iOS Blacksmith Oct 21 '20 at 15:06
  • Man you saved my life!!!. This solution works perfectly on iOS 14 as well. – Kawe Oct 29 '20 at 16:58
47

Using @falkon's answer here's the code:

Add this code to the view that is used as titleView

override var intrinsicContentSize: CGSize {
    return UILayoutFittingExpandedSize
} 
João Nunes
  • 3,751
  • 31
  • 32
16

Fixed it by creating a subclass of UIView and assigned it to a title view of UINavigationController

Objective-C:

#import "FLWCustomTitleView.h"

@implementation FLWCustomTitleView

- (CGSize )intrinsicContentSize {
  return UILayoutFittingExpandedSize;
}

@end

enter image description here enter image description here

Chirag Kothiya
  • 955
  • 5
  • 12
  • 28
Alex Kosyakov
  • 2,095
  • 1
  • 17
  • 25
12

setting intrinsicContentSize to UILayoutFittingExpandedSize works fine as well

konradowy
  • 1,572
  • 17
  • 27
7

I had to fit an UIImageView as navigationItem.titleView. The aspect ratio did fit but the intrinsicContentSize made it to large. Scaling the image led to poor image quality. Setting layout anchors worked for me:

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 30)];
[imageView setImage:image];
[imageView.widthAnchor constraintEqualToConstant:80].active = YES;
[imageView.heightAnchor constraintEqualToConstant:30].active = YES;
[imageView setContentMode:UIViewContentModeScaleAspectFit];
self.navigationItem.titleView = imageView;
Yedy
  • 2,107
  • 1
  • 25
  • 30
5

Addition to the existing answers:

If your custom title view is a view that already has an intrinsic content size by default (other than .zero), for example a UILabel, a UITextView or a UIButton, you can simply set

yourCustomTitleView.translatesAutoresizingMaskIntoConstraints = false

and it will automatically adjust to just enclose its contents, but never overlap with the left and right item views.


For example, you can drag a button into the title view area of a navigation bar in Interface Builder, create an outlet titleButton for it in your view controller and then do

override func viewDidLoad() {
    super.viewDidLoad()
    titleButton.translatesAutoresizingMaskIntoConstraints = false
}
Mischa
  • 15,816
  • 8
  • 59
  • 117
4

Swift 4.2 Version of Yedy's answer

let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 80, height: 30))
imageView.image = image
imageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 30).isActive = true
imageView.contentMode = .scaleAspectFit
navigationItem.titleView = imageView

Converted with the help of Swiftify.

Md. Ibrahim Hassan
  • 5,359
  • 1
  • 25
  • 45
2

The most important is that you need overwrite customTitleView as your titleView:

self.navigationItem.titleView = [self titleView];

#pragma mark - getter

- (UIView *)titleView {
    UIView *navTitleView = [HFCalenderTitleView new];
    navTitleView.frame = CGRectMake(0.0, 0.0, HorizontalFrom750(200.0), 44.0);

    [navTitleView addSubview:self.titleLabel];
    [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(navTitleView);
    }];

    CGFloat btnWidth = 30.0;
    [navTitleView addSubview:self.previousButton];
    self.previousButton.imageEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, 15.0);
    [self.previousButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(navTitleView);
        make.top.bottom.equalTo(navTitleView);
        make.width.equalTo(@(btnWidth));
    }];
    [navTitleView addSubview:self.nextBtn];
    self.nextBtn.imageEdgeInsets = UIEdgeInsetsMake(0.0, 15.0, 0.0, 0.0);
    [self.nextBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(navTitleView);
        make.top.bottom.equalTo(navTitleView);
        make.width.equalTo(@(btnWidth));
    }];

    return navTitleView;
}
#pragma mark - customTitleView

#import "HFCalenderTitleView.h"
@implementation HFCalenderTitleView
- (CGSize)intrinsicContentSize{
    return CGSizeMake(HorizontalFrom750(200.0), 40); // the target size
}

enter image description here

enter image description here

Qun Li
  • 1,256
  • 13
  • 13
2

When you have a UIView as subview inside CustomTitleView, intrinsicContentSize solution does not work, for me in XCODE 9 in iOS 11 only. so I did like below, works fine for me, might this help someone.

@interface CustomTitleView : UIView
@property (weak, nonatomic) IBOutlet UIView *doubleTitleView;
@end

@implementation CustomTitleView
- (void)awakeFromNib {
    [super awakeFromNib];
    int width = _doubleTitleView.frame.size.width;
    int height = _doubleTitleView.frame.size.height;
    if (width != 0 && height != 0) {

        NSLayoutConstraint *widthConstraint =  [_doubleTitleView.widthAnchor constraintEqualToConstant:width];
        NSLayoutConstraint *heightConstraint = [_doubleTitleView.heightAnchor constraintEqualToConstant:height];

        [_doubleTitleView addConstraint:heightConstraint];
        [_doubleTitleView addConstraint:widthConstraint];
        [heightConstraint setActive:TRUE];
        [widthConstraint setActive:TRUE];
    }
}
2

This class will do the trick. Make sure you set your custom view's class to this one:

import UIKit

class TitleView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        translatesAutoresizingMaskIntoConstraints = false
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        translatesAutoresizingMaskIntoConstraints = false
    }

    override var intrinsicContentSize: CGSize {
        CGSize(width: UIView.layoutFittingExpandedSize.width, height: self.bounds.height)
    }

}
Foti Dim
  • 1,303
  • 13
  • 19
1

You can also use constraints if you don't want to override intrinsicContentSize. Here's a demo of SnapKit

self.navigationItem.titleView = titleView
if #available(iOS 11, *) {
    titleView.snp.makeConstraints({ (make) in
        make.width.equalTo(250) // fixed width
        make.height.equalTo(30) // less than 44(height of naviagtion bar)
    })
}else {
    titleView.frame = ...
}

But if there are more than one navigationbaritems on any side(left or right) navigationbar, you should use intrinsicContentSize;

hstdt
  • 5,652
  • 2
  • 34
  • 34
0

I had the same issue but with setting an UIImage as the navigationItem titleView

What i did is i scaled the image to the needed size by using the below:

-(UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {

    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

And call it as follows:

-(void)setHeaderImage{
    UIImage * image = [self imageWithImage:[UIImage imageNamed:@"headerImage"] scaledToSize:CGSizeMake(150, 27)];
    UIImageView *  imageView = [[UIImageView alloc]initWithImage:image];
    imageView.frame = CGRectMake(0, 0, 150, 27);
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    self.navigationItem.titleView = imageView;
}
Mutawe
  • 6,464
  • 3
  • 47
  • 90
0

return UILayoutFittingExpandedSize not helped me, because view was added vertically few more times to fill layout.

The solution was to override intrinsicContentSize in custom view setting width to max screen width:

 - (CGSize)intrinsicContentSize {
    //fills empty space. View will be resized to be smaller, but if it is too small - then it stays too small
    CGRect frame = self.frame;
    frame.size.width = MAX(SCREEN_WIDTH, SCREEN_HEIGHT);
    return frame.size;
}
Eddwhis
  • 1,124
  • 2
  • 16
  • 33
0

Try using the Standard UISearchBar / UISearchController

Actually what you need to do - if you can use a standard UISearchBar / a UISearchController is to display the search bar the following way which respects the safe area and thus looks perfect on iPhone X and in each device orientation:

func presentSearchController() {
    let searchController = UISearchController(searchResultsController: nil)
    searchController.searchResultsUpdater = self
    searchController.obscuresBackgroundDuringPresentation = false
    searchController.searchBar.text = "any text"

    if #available(iOS 11.0, *) {
        self.navigationItem.searchController = searchController
        searchController.isActive = true
    } else {
        present(searchController, animated: true, completion: nil)
    }
}

References

https://developer.apple.com/videos/play/fall2017/201/ https://medium.com/@PavelGnatyuk/large-title-and-search-in-ios-11-514d5e020cee

blackjacx
  • 9,011
  • 7
  • 45
  • 56
0

on setup -ing your navigation controller

all you have to do is setting translatesAutoresizingMaskIntoConstraints to false check apple doc

Note that the autoresizing mask constraints fully specify the view’s size and position; therefore, you cannot add additional constraints to modify this size or position without introducing conflicts. If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false

then assign it to the width you wish

navigationItem.titleView?.translatesAutoresizingMaskIntoConstraints = false

lazy var titleWidth =  view.frame.size.width / 2

navigationItem.titleView?.constrainWidth(constant: titleWidth)
Aalaa
  • 55
  • 6