quitar - Personalización de la altura de la barra de navegación de iOS 11
dock transparente en ios 11 (11)
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 la altura es 66.
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 guión gráfico
Establecer clase de barra de navegación personalizada
Agregar TestView + Establecer 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.
}
}
El amarillo es barbackgroundView. La opacidad negra es BarContentView.
Y eliminé el backgroundColor de BarContentView.
Eso es.
Ahora en iOS 11, el método
sizeThatFits
no se llama desde
UINavigationBar
subclases
UINavigationBar
.
Cambiar el marco de
UINavigationBar
provoca fallas e inserciones incorrectas.
Entonces, ¿alguna idea de cómo personalizar la altura de la barra de navegación ahora?
Agregado: el problema se resuelve en iOS 11 beta 6, por lo que el siguiente código no sirve de nada ^ _ ^
Respuesta original:
Resuelto con el siguiente código:
(Siempre quiero el navigationBar.height + statusBar.height == 64 si lo 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
Aunque está corregido 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 en el visor de jerarquía de vistas).
Una solución alternativa por ahora es anular
layoutSubviews
en su
UINavigationBar
personalizada 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 observa, el fondo de la barra, de hecho, tiene un desplazamiento de
-20
para que aparezca detrás de la barra de estado, por lo que el cálculo anterior agrega eso.
Estaba duplicando la altura de mi barra de navegación para poder agregar una fila de iconos 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. Ponga esto en su controlador de vista:
- (void)viewDidLoad {
[super viewDidLoad];
if (self.navigationController) {
self.navigationItem.prompt = @" "; // this adds empty space on top
}
}
Esto es lo que yo uso.
Funciona para contenido normal (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.
Use esto bajo su propio riesgo, ya que podría frenar en algún momento.
Esta es la barra de navegación con 90.0px de altura codificada, que funciona tanto en iOS 11 como en versiones anteriores.
Es posible que
UIBarButtonItem
agregar algunas inserciones al
UIBarButtonItem
para que iOS 11 anterior
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")
}
}
Esto funciona bien para la barra de navegación normal. Si está utilizando LargeTitle, esto no funcionará bien porque el tamaño titleView no tendrá una altura fija de 44 puntos. Pero para la vista regular esto debería ser suficiente.
Como @frangulyan apple sugirió agregar una vista debajo de la barra de navegación y ocultar la línea delgada (imagen de sombra). Esto es lo que se me ocurrió a continuación. Agregué una uiview a titleView del elemento de navegación y luego agregué una imageView dentro de esa uiview. Eliminé la línea delgada (imagen de sombra). La uiview que agregué es del mismo color exacto que la barra de navegación . Agregué un uiLabel dentro de esa vista y eso es todo.
Aquí está la imagen en 3D. La vista extendida se encuentra detrás de usernameLabel debajo de navBar. Es gris y tiene una delgada línea debajo. Simplemente ancle su collectionView o lo que sea debajo de la delgada línea de separación.
Los 9 pasos se explican arriba de cada línea de código:
class ExtendedNavController: UIViewController {
fileprivate let extendedView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
return view
}()
fileprivate let separatorLine: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .gray
return view
}()
fileprivate let usernameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 14)
label.text = "username goes here"
label.textAlignment = .center
label.lineBreakMode = .byTruncatingTail
label.numberOfLines = 1
return label
}()
fileprivate let myTitleView: UIView = {
let view = UIView()
view.backgroundColor = .white
return view
}()
fileprivate let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.clipsToBounds = true
imageView.backgroundColor = .darkGray
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// 1. the navBar''s titleView has a height of 44, set myTitleView height and width both to 44
myTitleView.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
// 2. set myTitleView to the nav bar''s titleView
navigationItem.titleView = myTitleView
// 3. get rid of the thin line (shadow Image) underneath the navigationBar
navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
navigationController?.navigationBar.layoutIfNeeded()
// 4. set the navigationBar''s tint color to the color you want
navigationController?.navigationBar.barTintColor = UIColor(red: 249.0/255.0, green: 249.0/255.0, blue: 249.0/255.0, alpha: 1.0)
// 5. set extendedView''s background color to the same exact color as the navBar''s background color
extendedView.backgroundColor = UIColor(red: 249.0/255.0, green: 249.0/255.0, blue: 249.0/255.0, alpha: 1.0)
// 6. set your imageView to get pinned inside the titleView
setProfileImageViewAnchorsInsideMyTitleView()
// 7. set the extendedView''s anchors directly underneath the navigation bar
setExtendedViewAndSeparatorLineAnchors()
// 8. set the usernameLabel''s anchors inside the extendedView
setNameLabelAnchorsInsideTheExtendedView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// 9. **Optional** If you want the shadow image to show on other view controllers when popping or pushing
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
navigationController?.navigationBar.setValue(false, forKey: "hidesShadow")
navigationController?.navigationBar.layoutIfNeeded()
}
func setExtendedViewAndSeparatorLineAnchors() {
view.addSubview(extendedView)
view.addSubview(separatorLine)
extendedView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
extendedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
extendedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
extendedView.heightAnchor.constraint(equalToConstant: 29.5).isActive = true
separatorLine.topAnchor.constraint(equalTo: extendedView.bottomAnchor).isActive = true
separatorLine.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
separatorLine.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
separatorLine.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
}
func setProfileImageViewAnchorsInsideMyTitleView() {
myTitleView.addSubview(profileImageView)
profileImageView.topAnchor.constraint(equalTo: myTitleView.topAnchor).isActive = true
profileImageView.centerXAnchor.constraint(equalTo: myTitleView.centerXAnchor).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 44).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 44).isActive = true
// round the profileImageView
profileImageView.layoutIfNeeded()
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
}
func setNameLabelAnchorsInsideTheExtendedView() {
extendedView.addSubview(usernameLabel)
usernameLabel.topAnchor.constraint(equalTo: extendedView.topAnchor).isActive = true
usernameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
usernameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Junto con la anulación de
-layoutSubviews
y
-setFrame:
debe verificar la propiedad
additionalSafereaInsets
adicional SafereaInsets de UIViewController (
Documentación de Apple
) si no desea que la barra de navegación redimensionada oculte su contenido.
Según los desarrolladores de Apple (mira here , here y here ), no se admite cambiar la altura de la barra de navegación en iOS 11. Here sugieren hacer soluciones alternativas como tener una vista debajo de la barra de navegación (pero fuera de ella) y luego eliminar el borde de la barra de navegación. Como resultado, tendrá esto en el guión gráfico:
se ve así en el dispositivo:
Ahora puede hacer una solución alternativa que se sugirió en las otras respuestas: cree una subclase personalizada de
UINavigationBar
, agregue su subvista grande personalizada, anule
sizeThatFits
y
layoutSubviews
, luego establezca
additionalSafeAreaInsets.top
para el controlador superior de la navegación a la diferencia
customHeight - 44px
, pero la vista de barra seguirá siendo la predeterminada de 44 píxeles, aunque visualmente todo se verá perfecto.
No intenté anular
setFrame
, tal vez funcione, sin embargo, como escribió el desarrollador de Apple en uno de los enlaces anteriores:
"... y ninguno está [soportado] cambiando el marco de una barra de navegación que es propiedad de un UINavigationController (el el controlador de navegación pisoteará felizmente los cambios en el marco cada vez que lo considere conveniente ".
En mi caso, la solución anterior hizo que las vistas se vean así (vista de depuración para mostrar bordes):
Como puede ver, la apariencia visual es bastante buena, los
additionalSafeAreaInsets
SafeAreaInsets empujaron correctamente el contenido hacia abajo, la gran barra de navegación es visible, sin embargo, tengo un botón personalizado en esta barra y solo el área que se encuentra debajo de la barra de navegación estándar de 44 píxeles clicable (área verde en la imagen).
Los toques por debajo de la altura de la barra de navegación estándar no alcanzan 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.
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 baja 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 funciona para mi:
- (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];
}
}
}