Personalización de la altura de la barra de navegación iOS 11

Ahora en iOS 11, el método sizeThatFits no se llama desde UINavigationBar subclases UINavigationBar . Cambiar el marco de UINavigationBar causa fallas e UINavigationBar incorrectos. Entonces, ¿alguna idea de cómo personalizar la altura de la barra de navegación ahora?

Actualizado 07 Ene 2018

Este código es compatible con XCode 9.2, iOS 11.2

Yo tuve el mismo problema. A continuación está mi solución. Supongo que el tamaño de altura es 66.

Por favor elige mi respuesta si te ayuda.

Crear 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() } } } } 

Establecer guiones gráficos

enter image description here

Establecer la clase NavigationBar

Establecer clase Custom NavigationBar

Agregar TestView

enter image description here

Agregar 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. } } 

enter image description here enter image description here

Amarillo es barbackgroundView. La opacidad negra es BarContentView.

Y eliminé el BackgroundColor de BarContentView.

enter image description here

Eso es.

Según los desarrolladores de Apple (mira aquí , aquí y aquí ), no se admite el cambio de la altura de la barra de navegación en iOS 11. Aquí sugieren hacer soluciones como tener una vista debajo de la barra de navegación (pero fuera de ella) y luego quitar el borde de la barra de navegación. Como resultado, tendrá esto en el guión gráfico:

enter image description here

mira esto en el dispositivo:

enter image description here

Ahora puede hacer una solución que se sugirió en las otras respuestas: crear una subclase personalizada de UINavigationBar , agregar su subvista grande personalizada, anular sizeThatFits y layoutSubviews , luego establecer additionalSafeAreaInsets.top para el controlador superior de navegación a la diferencia customHeight - 44px , pero la vista de barras seguirá siendo el valor predeterminado de 44 píxeles, aunque visualmente todo se verá perfecto. No intenté sobreescribir setFrame , quizás funcione, ya que el desarrollador de Apple escribió en uno de los enlaces de arriba: “… y ninguno de los dos está [compatible] cambiando el marco de una barra de navegación que es propiedad de un UINavigationController (el el controlador de navegación aplastará felizmente los cambios de su cuadro siempre que lo considere oportuno) “.

En mi caso, la solución anterior hacía que las vistas se vieran así (vista de depuración para mostrar los bordes):

enter image description here

Como puede ver, la apariencia visual es bastante buena, el additionalSafeAreaInsets empujó correctamente el contenido, la gran barra de navegación está visible, sin embargo, tengo un botón personalizado en esta barra y solo el área que está por debajo de la barra de navegación estándar de 44 píxeles clicable (área verde en la imagen). Toca debajo de la altura estándar de la barra de navegación no llega a mi subvista personalizada, por lo que necesito cambiar el tamaño de la barra de navegación, lo que los desarrolladores de Apple dicen que no es compatible.

Agregado: el problema se resuelve en iOS 11 beta 6, por lo que el código siguiente no sirve de nada ^ _ ^


Respuesta original:

Resuelto con el siguiente código:

(Siempre quiero el navigationBar.height + statusBar.height == 64 si el oculto de statusBar es verdadero o no)

  @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 

esto funciona para mí:

 - (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]; } } } 

Junto con la anulación de -layoutSubviews y -setFrame: debe verificar la propiedad additionalSafereaInsets añadida de SafereaInsets de UIViewController ( documentación de Apple ) si no desea que la barra de navegación de tamaño variable oculte su contenido.

Aunque está fijado en beta 4, parece que la imagen de fondo de la barra de navegación no se escala con la vista real (puede verificar esto mirando el visor de jerarquía de vistas). Una solución temporal por ahora es anular layoutSubviews en su UINavigationBar personalizado y luego usar este código:

 - (void)layoutSubviews { [super layoutSubviews]; for (UIView *subview in self.subviews) { if ([NSStringFromClass([subview class]) containsString:@"BarBackground"]) { CGRect subViewFrame = subview.frame; subViewFrame.origin.y = -20; subViewFrame.size.height = CUSTOM_FIXED_HEIGHT+20; [subview setFrame: subViewFrame]; } } } 

Si lo nota, el fondo de la barra tiene un desplazamiento de -20 para que aparezca detrás de la barra de estado, por lo que el cálculo anterior lo agrega.

Simplificado con 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 } } } } } 

en Xcode 9 Beta 6 todavía tengo el problema. La barra siempre tiene una altura de 44 píxeles y se coloca debajo de la barra de estado.

Para resolver eso hice una subclase con el código @strangetimes (en Swift)

 class NavigationBar: UINavigationBar { override func layoutSubviews() { super.layoutSubviews() for subview in self.subviews { var stringFromClass = NSStringFromClass(subview.classForCoder) print("--------- \(stringFromClass)") if stringFromClass.contains("BarBackground") { subview.frame.origin.y = -20 subview.frame.size.height = 64 } } } } 

y coloco la barra más abajo que la barra de estado

 let newNavigationBar = NavigationBar(frame: CGRect(origin: CGPoint(x: 0, y: 20), size: CGSize(width: view.frame.width, height: 64) ) ) 

Esto es lo que uso Funciona para contenido regular (44.0 px) si usa UISearchBar como título u otras vistas que modifican el tamaño del contenido de la barra, debe actualizar los valores en consecuencia. Úselo bajo su propio riesgo, ya que podría frenar en algún momento.

Esta es la barra de navegación con una altura de 90.0px codificada, que funciona tanto en iOS 11 como en versiones anteriores. Es posible que UIBarButtonItem agregar algunas inserciones al UIBarButtonItem para que el iOS anterior 11 UIBarButtonItem el mismo aspecto.

 class NavBar: UINavigationBar { override init(frame: CGRect) { super.init(frame: frame) if #available(iOS 11, *) { translatesAutoresizingMaskIntoConstraints = false } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func sizeThatFits(_ size: CGSize) -> CGSize { return CGSize(width: UIScreen.main.bounds.width, height: 70.0) } override func layoutSubviews() { super.layoutSubviews() guard #available(iOS 11, *) else { return } frame = CGRect(x: frame.origin.x, y: 0, width: frame.size.width, height: 90) if let parent = superview { parent.layoutIfNeeded() for view in parent.subviews { let stringFromClass = NSStringFromClass(view.classForCoder) if stringFromClass.contains("NavigationTransition") { view.frame = CGRect(x: view.frame.origin.x, y: frame.size.height - 64, width: view.frame.size.width, height: parent.bounds.size.height - frame.size.height + 4) } } } for subview in self.subviews { var stringFromClass = NSStringFromClass(subview.classForCoder) if stringFromClass.contains("BarBackground") { subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 90) subview.backgroundColor = .yellow } stringFromClass = NSStringFromClass(subview.classForCoder) if stringFromClass.contains("BarContent") { subview.frame = CGRect(x: subview.frame.origin.x, y: 40, width: subview.frame.width, height: subview.frame.height) } } } } 

Y lo agrega a una subclase UINavigationController como esta:

 class CustomBarNavigationViewController: UINavigationController { init() { super.init(navigationBarClass: NavBar.self, toolbarClass: nil) } override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } override init(rootViewController: UIViewController) { super.init(navigationBarClass: NavBar.self, toolbarClass: nil) self.viewControllers = [rootViewController] } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Estaba doblando la altura de mi barra de navegación para poder agregar una fila de íconos de estado sobre los controles de navegación predeterminados, subclasificando UINavigationBar y usando sizeThatFits para anular la altura. Afortunadamente, esto tiene el mismo efecto, y es más simple, con menos efectos secundarios. Lo probé con iOS 8 a 11. Pon esto en tu controlador de vista:

 - (void)viewDidLoad { [super viewDidLoad]; if (self.navigationController) { self.navigationItem.prompt = @" "; // this adds empty space on top } }