iOS 11 navigation bar height customizing
Solution 1
According to Apple developers (look here, here and here), changing navigation bar height in iOS 11 is not supported. Here they suggest to do workarounds like having a view under the navigation bar (but outside of it) and then remove the nav bar border. As a result, you will have this in storyboard:
look like this on the device:
Now you can do a workaround that was suggested in the other answers: create a custom subclass of UINavigationBar
, add your custom large subview to it, override sizeThatFits
and layoutSubviews
, then set additionalSafeAreaInsets.top
for the navigation's top controller to the difference customHeight - 44px
, but the bar view will still be the default 44px, even though visually everything will look perfect. I didn't try overriding setFrame
, maybe it works, however, as Apple developer wrote in one of the links above: "...and neither is [supported] changing the frame of a navigation bar that is owned by a UINavigationController (the navigation controller will happily stomp on your frame changes whenever it deems fit to do so)."
In my case the above workaround made views to look like this (debug view to show borders):
As you can see, the visual appearance is quite good, the additionalSafeAreaInsets
correctly pushed the content down, the big navigation bar is visible, however I have a custom button in this bar and only the area that goes under the standard 44 pixel nav bar is clickable (green area in the image). Touches below the standard navigation bar height doesn't reach my custom subview, so I need the navigation bar itself to be resized, which the Apple developers say is not supported.
Solution 2
Updated 07 Jan 2018
This code is support XCode 9.2, iOS 11.2
I had the same problem. Below is my solution. I assume that height size is 66.
Please choose my answer if it helps you.
Create CINavgationBar.swift
import UIKit
@IBDesignable
class CINavigationBar: UINavigationBar {
//set NavigationBar's height
@IBInspectable var customHeight : CGFloat = 66
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: customHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
print("It called")
self.tintColor = .black
self.backgroundColor = .red
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("UIBarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)
subview.backgroundColor = .green
subview.sizeToFit()
}
stringFromClass = NSStringFromClass(subview.classForCoder)
//Can't set height of the UINavigationBarContentView
if stringFromClass.contains("UINavigationBarContentView") {
//Set Center Y
let centerY = (customHeight - subview.frame.height) / 2.0
subview.frame = CGRect(x: 0, y: centerY, width: self.frame.width, height: subview.frame.height)
subview.backgroundColor = .yellow
subview.sizeToFit()
}
}
}
}
Set Storyboard
Set Custom NavigationBar class
Add TestView + Set SafeArea
ViewController.swift
import UIKit
class ViewController: UIViewController {
var navbar : UINavigationBar!
@IBOutlet weak var testView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//update NavigationBar's frame
self.navigationController?.navigationBar.sizeToFit()
print("NavigationBar Frame : \(String(describing: self.navigationController!.navigationBar.frame))")
}
//Hide Statusbar
override var prefersStatusBarHidden: Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
//Important!
if #available(iOS 11.0, *) {
//Default NavigationBar Height is 44. Custom NavigationBar Height is 66. So We should set additionalSafeAreaInsets to 66-44 = 22
self.additionalSafeAreaInsets.top = 22
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create BackButton
var backButton: UIBarButtonItem!
let backImage = imageFromText("Back", font: UIFont.systemFont(ofSize: 16), maxWidth: 1000, color:UIColor.white)
backButton = UIBarButtonItem(image: backImage, style: UIBarButtonItemStyle.plain, target: self, action: #selector(SecondViewController.back(_:)))
self.navigationItem.leftBarButtonItem = backButton
self.navigationItem.leftBarButtonItem?.setBackgroundVerticalPositionAdjustment(-10, for: UIBarMetrics.default)
}
override var prefersStatusBarHidden: Bool {
return true
}
@objc func back(_ sender: UITabBarItem){
self.navigationController?.popViewController(animated: true)
}
//Helper Function : Get String CGSize
func sizeOfAttributeString(_ str: NSAttributedString, maxWidth: CGFloat) -> CGSize {
let size = str.boundingRect(with: CGSize(width: maxWidth, height: 1000), options:(NSStringDrawingOptions.usesLineFragmentOrigin), context:nil).size
return size
}
//Helper Function : Convert String to UIImage
func imageFromText(_ text:NSString, font:UIFont, maxWidth:CGFloat, color:UIColor) -> UIImage
{
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = NSLineBreakMode.byWordWrapping
paragraph.alignment = .center // potentially this can be an input param too, but i guess in most use cases we want center align
let attributedString = NSAttributedString(string: text as String, attributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.paragraphStyle:paragraph])
let size = sizeOfAttributeString(attributedString, maxWidth: maxWidth)
UIGraphicsBeginImageContextWithOptions(size, false , 0.0)
attributedString.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Yellow is barbackgroundView. Black opacity is BarContentView.
And I removed BarContentView's backgroundColor.
That's It.
Solution 3
this works for me :
- (CGSize)sizeThatFits:(CGSize)size {
CGSize sizeThatFit = [super sizeThatFits:size];
if ([UIApplication sharedApplication].isStatusBarHidden) {
if (sizeThatFit.height < 64.f) {
sizeThatFit.height = 64.f;
}
}
return sizeThatFit;
}
- (void)setFrame:(CGRect)frame {
if ([UIApplication sharedApplication].isStatusBarHidden) {
frame.size.height = 64;
}
[super setFrame:frame];
}
- (void)layoutSubviews
{
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"BarBackground"]) {
CGRect subViewFrame = subview.frame;
subViewFrame.origin.y = 0;
subViewFrame.size.height = 64;
[subview setFrame: subViewFrame];
}
if ([NSStringFromClass([subview class]) containsString:@"BarContentView"]) {
CGRect subViewFrame = subview.frame;
subViewFrame.origin.y = 20;
subViewFrame.size.height = 44;
[subview setFrame: subViewFrame];
}
}
}
Solution 4
Added: The problem is solved in iOS 11 beta 6 ,so the code below is of no use ^_^
Original answer:
Solved with code below :
(I always want the navigationBar.height + statusBar.height == 64 whether the hidden of statusBar is true or not)
@implementation P1AlwaysBigNavigationBar
- (CGSize)sizeThatFits:(CGSize)size {
CGSize sizeThatFit = [super sizeThatFits:size];
if ([UIApplication sharedApplication].isStatusBarHidden) {
if (sizeThatFit.height < 64.f) {
sizeThatFit.height = 64.f;
}
}
return sizeThatFit;
}
- (void)setFrame:(CGRect)frame {
if ([UIApplication sharedApplication].isStatusBarHidden) {
frame.size.height = 64;
}
[super setFrame:frame];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (![UIApplication sharedApplication].isStatusBarHidden) {
return;
}
for (UIView *subview in self.subviews) {
NSString* subViewClassName = NSStringFromClass([subview class]);
if ([subViewClassName containsString:@"UIBarBackground"]) {
subview.frame = self.bounds;
}else if ([subViewClassName containsString:@"UINavigationBarContentView"]) {
if (subview.height < 64) {
subview.y = 64 - subview.height;
}else {
subview.y = 0;
}
}
}
}
@end
Solution 5
Simplified with Swift 4.
class CustomNavigationBar : UINavigationBar {
private let hiddenStatusBar: Bool
// MARK: Init
init(hiddenStatusBar: Bool = false) {
self.hiddenStatusBar = hiddenStatusBar
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Overrides
override func layoutSubviews() {
super.layoutSubviews()
if #available(iOS 11.0, *) {
for subview in self.subviews {
let stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarBackground") {
subview.frame = self.bounds
} else if stringFromClass.contains("BarContentView") {
let statusBarHeight = self.hiddenStatusBar ? 0 : UIApplication.shared.statusBarFrame.height
subview.frame.origin.y = statusBarHeight
subview.frame.size.height = self.bounds.height - statusBarHeight
}
}
}
}
}
Максим Котляр
Updated on July 05, 2022Comments
-
Максим Котляр almost 2 years
Now in iOS 11, the
sizeThatFits
method is not called fromUINavigationBar
subclasses. Changing the frame ofUINavigationBar
causes glitches and wrong insets. So, any ideas how to customize navbar height now? -
sudoExclaimationExclaimation almost 7 yearsIn the for look your
subview
is a UIView. How are you doingsubview.height
later on?? -
CharlieSu almost 7 yearsI wrote a helper category for UIView.
-
Marco Pappalardo almost 7 yearsyou need to declare/instanciate subviewFrame? or edit directly the frame of the subview?
-
strangetimes almost 7 years@MarcoPappalardo fixed typo, needs to be a local variable
-
Steffen Ruppel almost 7 yearsStill having this issue with iOS 11 beta 9. Using this workaround solves the problem. But hopefully they will fix it. Thanks @CharlieSu
-
Husein Behboudi Rad almost 7 yearshow can I set this class as the navigation bar of my uinavigationcontroller?
-
JoGoFo almost 7 yearsThis is important, simply updating your navbar background height will case it to overlap content in your view controllers. What I'm not able to work out is how to properly use
addionalSafeAreaInsets
and in particular how to allow for iOS 10 and below which don't support this property -
JoGoFo almost 7 yearsThis is important, simply updating your navbar background height will case it to overlap content in your view controllers. What I'm not able to work out is how to properly use
addionalSafeAreaInsets
and in particular how to allow for iOS 10 and below which don't support this property -
Shawn Baek almost 7 yearsI got an error -> fatal error: init(coder:) has not been implemented:
-
Jelly over 6 yearsJust implement init with coder if you are using that
-
Shawn Baek over 6 yearsThanks for the reply. But Safe Area's top is not updated. Safe Area's top is still 44px. How to update Safe Area's top after set navigation bar height.
-
Jelly over 6 yearsYou could try using
safeAreaInsets
property on UIView to update your safe area. -
KarenAnne over 6 yearsThanks! The
setAdditionalSafeAreaInsets:
helped me! :) So the navigation bar in iOS 11 is shorter in terms of height to the one in iOS 10? -
andromedainiative over 6 yearsIs there a swift example of this? I reckon I use a subclassed UINavigationBar?
-
Michael over 6 yearsThis solution seems to be invalid in iOS 11.2 because the navigation bar calls
layoutSubviews()
numerous times, making the app freeze. -
Krishna Kumar Thakur over 6 yearsI am also facing same problem @Michael
-
Shawn Baek over 6 years@KrishnaKumar Updated my answer
-
Shawn Baek over 6 years@Michael Updated my anwer
-
anemo over 6 yearsThese kind of workarounds are totally hackish and guaranteed to break in near future!
-
krishnanunni over 6 yearsself.subviews is empty for me
-
MarkII over 6 yearsto resolve the issue with clickable area, try to add to your custom UINavigationBar next override method
code override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return subviews.reduce(super.hitTest(point, with: event)) { (result, subview) in return result ?? subview.hitTest(convert(point, to: subview), with: event) } }
sorry for formatting -
Jordan H about 6 yearsOn iPhone X, the nav bar height changes but no longer extends up underneath the status bar area. Any updates to make it work on iPhone X?
-
Weizhi about 6 yearsThe latest project provided by Apple does not include the extended navigation bar.
-
InkGolem over 5 yearsSearching for subviews by class name is extremely brittle. Anyone looking for a robust solution should avoid this.
-
Grubas over 5 years@Weizhi you can download old version from github: github.com/robovm/apple-ios-samples/tree/master/…
-
Gang Fang about 5 years@Weizhi - they still have the code included but the storyboard scene was removed..
-
Don Miguel about 5 yearsAs the sample now remaining from Apple is incomplete, could you @frangulan provide some code on how you actually implemented this?
-
frangulyan about 5 years@DonMiguel I just stopped messing with the navigation bar, changed my app's design to use the default bar. Otherwise, I believe, I had to implement my own navigation bar, it was not worth it.
-
MEnnabah over 4 yearsThis causes the navigation bar to flicker on iOS 13.2 on all devices, I don't know about other versions.
-
Admin about 4 yearsthis code gives me a fatal error fatalError("init(coder:) has not been implemented")
-
Itai Spector over 3 yearsis
navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
considired private, and rejected by apple's review? -
Lance Samaria over 3 yearsI use it in one of my live apps and I never had any problems
-
Lance Samaria over 3 years@ItaiSpector you have nothing to worry about