custom - navigation bar ios 11
UISearchBar aumenta la altura de la barra de navegaciĆ³n en iOS 11 (18)
Tengo mi
UISearchBar
como parte de la barra de navegación como:
let searchBar = UISearchBar()
//some more configuration to the search bar
.....
navigationItem.titleView = searchBar
Después de actualizar a
iOS 11
algo extraño le sucedió a la barra de búsqueda en mi aplicación.
En
iOS 10
y anteriores tenía mi barra de navegación como:
Ahora con
iOS 11
tengo:
Como puede ver, hay una diferencia en el redondeo de las dos barras de búsqueda que no me molesta. El problema es que la barra de búsqueda aumenta la altura de la barra de navegación. Entonces, cuando voy a otro controlador, también se ve raro:
De hecho, la altura de la línea negra extraña más la altura de la barra de navegación actual es igual a la altura de la barra de navegación que se muestra en la segunda imagen ...
¿Alguna idea de cómo deshacerse de la línea negra y tener una altura de barra de navegación constante en todos los controladores de vista?
¡Gracias a todos! Finalmente encontré una solución.
Agregar el siguiente código a ViewController con UISearchBar.
-
Primer paso:
viewDidLoad
let barFrame = searchController.searchBar.frame
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: barFrame.width, height: 44)
navigationItem.searchController = searchController
-
Segundo paso:
viewWillDisappear
UIBarButtonItem *cancelButton;
if (@available(iOS 11.0, *)) {
// For iOS11 creating custom button to accomadate the change of navbar + search bar being 56 points
self.navBarCustomButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.navBarCustomButton setTitle:@"Cancel"];
[self.navBarCustomButton addTarget:self action:@selector(cancelButtonTapped) forControlEvents:UIControlEventTouchUpInside];
cancelButton = [[UIBarButtonItem alloc] initWithCustomView:self.navBarCustomButton];
} else {
cancelButton = [[UIBarButtonItem alloc] initWithTitle:MagicLocalizedString(@"button.cancel", @"Cancel")
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancelButtonTapped)];
}
if (@available(iOS 11.0, *)) {
UIView *buttonsStackView = [navigationController.navigationBar subviewOfClass:[UIStackView class]];
if (buttonsStackView ) {
[buttonsStackView.centerYAnchor constraintEqualToAnchor:navigationController.navigationBar.centerYAnchor].active = YES;
[self.navBarCustomButton.heightAnchor constraintEqualToAnchor:buttonsStackView.heightAnchor];
}
}
Creo que en iOS 11 UISearchBar ahora tiene una altura igual a 56, y UINavigationBar usa la distribución automática para ajustar sus subvistas, por lo tanto, aumenta la altura. Si aún desea tener UISearchBar como titleView como en versiones anteriores a iOS 11, descubrí que la mejor manera de hacerlo es incrustar UISearchBar en una vista personalizada, establecer la altura de esta vista en 44 y asignarla a navigationItem.titleView
class SearchBarContainerView: UIView {
let searchBar: UISearchBar
init(customSearchBar: UISearchBar) {
searchBar = customSearchBar
super.init(frame: CGRect.zero)
addSubview(searchBar)
}
override convenience init(frame: CGRect) {
self.init(customSearchBar: UISearchBar())
self.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
searchBar.frame = bounds
}
}
class MyViewController: UIViewController {
func setupNavigationBar() {
let searchBar = UISearchBar()
let searchBarContainer = SearchBarContainerView(customSearchBar: searchBar)
searchBarContainer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44)
navigationItem.titleView = searchBarContainer
}
}
EDITAR: La respuesta @zgjie es una mejor solución para este problema: https://.com/a/46356265/1713123
Parece que esto sucede porque en iOS 11 el valor de altura predeterminado de SearchBar se cambió a 56, en lugar de 44 en versiones anteriores de iOS.
Por ahora, he aplicado esta solución, estableciendo la altura de la barra de búsqueda de nuevo a 44:
- (__kindof UIView *)subviewOfClass:(Class)targetClass {
// base case
if ([self isKindOfClass:targetClass]) {
return self;
}
// recursive
for (UIView *subview in self.subviews) {
UIView *dfsResult = [subview subviewOfClass:targetClass];
if (dfsResult) {
return dfsResult;
}
}
return nil;
}
Otra solución podría ser usar la nueva propiedad searchController en navigationItem en iOS 11 :
@implementation CJSearchBar
-(CGSize)intrinsicContentSize{
CGSize s = [super intrinsicContentSize];
s.height = 44;
return s;
}
@end
Pero de esta manera da searchBar aparece debajo del título de navegación.
En mi caso, la altura más grande de UINavigationBar no fue un problema para mí. Solo necesitaba realinear los elementos del botón de la barra izquierda y derecha. Esa es la solución que se me ocurrió:
- (void)iOS11FixNavigationItemsVerticalAlignment
{
[self.navigationController.navigationBar layoutIfNeeded];
NSString * currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer compare:@"11" options:NSNumericSearch] != NSOrderedAscending)
{
UIView * navigationBarContentView;
for (UIView * subview in [self.navigationController.navigationBar subviews])
{
if ([subview isKindOfClass:NSClassFromString(@"_UINavigationBarContentView")])
{
navigationBarContentView = subview;
break;
}
}
if (navigationBarContentView)
{
for (UIView * subview in [navigationBarContentView subviews])
{
if (![subview isKindOfClass:NSClassFromString(@"_UIButtonBarStackView")]) continue;
NSLayoutConstraint * topSpaceConstraint;
NSLayoutConstraint * bottomSpaceConstraint;
CGFloat topConstraintMultiplier = 1.0f;
CGFloat bottomConstraintMultiplier = 1.0f;
for (NSLayoutConstraint * constraint in navigationBarContentView.constraints)
{
if (constraint.firstItem == subview && constraint.firstAttribute == NSLayoutAttributeTop)
{
topSpaceConstraint = constraint;
break;
}
if (constraint.secondItem == subview && constraint.secondAttribute == NSLayoutAttributeTop)
{
topConstraintMultiplier = -1.0f;
topSpaceConstraint = constraint;
break;
}
}
for (NSLayoutConstraint * constraint in navigationBarContentView.constraints)
{
if (constraint.firstItem == subview && constraint.firstAttribute == NSLayoutAttributeBottom)
{
bottomSpaceConstraint = constraint;
break;
}
if (constraint.secondItem == subview && constraint.secondAttribute == NSLayoutAttributeBottom)
{
bottomConstraintMultiplier = -1.0f;
bottomSpaceConstraint = constraint;
break;
}
}
CGFloat contentViewHeight = navigationBarContentView.frame.size.height;
CGFloat subviewHeight = subview.frame.size.height;
topSpaceConstraint.constant = topConstraintMultiplier * (contentViewHeight - subviewHeight) / 2.0f;
bottomSpaceConstraint.constant = bottomConstraintMultiplier * (contentViewHeight - subviewHeight) / 2.0f;
}
}
}
}
Básicamente, buscamos vistas de pila que contengan elementos de botón de barra y luego cambiamos sus valores de restricciones superior e inferior. Sí, es un truco sucio, y no recomendaré usarlo si puede solucionar su problema de otra manera.
En mi caso, tengo que disminuir la altura de textField 36pt -> 28pt.
Así que intenté cambiar la altura del marco, la altura de la capa. Pero las formas no funcionaron.
Finalmente, encontré una solución que es la máscara. Creo que no es una buena manera, pero funciona.
let textField = searchBar.value(forKey: "searchField") as? UITextField
textField?.font = UIFont.systemFont(ofSize: 14.0, weight: .regular)
textField?.textColor = #colorLiteral(red: 0.1960784314, green: 0.1960784314, blue: 0.1960784314, alpha: 1)
textField?.textAlignment = .left
if #available(iOS 11, *) {
let radius: CGFloat = 5.0
let magnifyIconWidth: CGFloat = 16.0
let inset = UIEdgeInsets(top: 4.0, left: 0, bottom: 4.0, right: 0)
let path = CGMutablePath()
path.addArc(center: CGPoint(x: searchBar.bounds.size.width - radius - inset.right - magnifyIconWidth, y: inset.top + radius), radius: radius, startAngle: .pi * 3.0/2.0, endAngle: .pi*2.0, clockwise: false) // Right top
path.addArc(center: CGPoint(x: searchBar.bounds.size.width - radius - inset.right - magnifyIconWidth, y: searchBar.bounds.size.height - radius - inset.bottom), radius: radius, startAngle: 0, endAngle: .pi/2.0, clockwise: false) // Right Bottom
path.addArc(center: CGPoint(x: inset.left + radius, y: searchBar.bounds.size.height - radius - inset.bottom), radius: radius, startAngle: .pi/2.0, endAngle: .pi, clockwise: false) // Left Bottom
path.addArc(center: CGPoint(x: inset.left + radius, y: inset.top + radius), radius: radius, startAngle: .pi, endAngle: .pi * 3.0/2.0, clockwise: false) // Left top
let maskLayer = CAShapeLayer()
maskLayer.path = path
maskLayer.fillRule = kCAFillRuleEvenOdd
textField?.layer.mask = maskLayer
}
Puede cambiar las inserciones, si desea cambiar el marco del campo de texto.
Encontré que la solución de Mai Mai es la única que es realmente útil.
Sin embargo, todavía no es perfecto:
Al girar el dispositivo, la barra de búsqueda no cambia de tamaño correctamente y permanece en la dimensión más pequeña.
He encontrado una solución para eso. Aquí está mi código en el Objetivo C con las partes relevantes anotadas:
// improvements in the search bar wrapper
@interface SearchBarWrapper : UIView
@property (nonatomic, strong) UISearchBar *searchBar;
- (instancetype)initWithSearchBar:(UISearchBar *)searchBar;
@end
@implementation SearchBarWrapper
- (instancetype)initWithSearchBar:(UISearchBar *)searchBar {
// setting width to a large value fixes stretch-on-rotation
self = [super initWithFrame:CGRectMake(0, 0, 4000, 44)];
if (self) {
self.searchBar = searchBar;
[self addSubview:searchBar];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.searchBar.frame = self.bounds;
}
// fixes width some cases of resizing while search is active
- (CGSize)sizeThatFits:(CGSize)size {
return size;
}
@end
// then use it in your VC
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.titleView = [[SearchBarWrapper alloc] initWithSearchBar:self.searchController.searchBar];
}
@end
Ahora todavía queda un caso que aún no he descubierto.
Para reproducir haga lo siguiente:
- comenzar en retrato
- activar el campo de búsqueda
- rotar al paisaje
- error: la barra no cambia de tamaño
Hola a las personas que usan
UISearchController
y luego adjuntan su
UISearchBar
a
navigationItem.titleView
.
He pasado unas locas 4-5 horas de mi día para resolver esto.
Siguiendo el enfoque recomendado para iOS 11+, que pone el
searchController
en
navigation.searchController
no es el adecuado para mi caso.
La pantalla que tiene este searchController / searchBar tiene un backButton, uno personalizado.
He probado esto en iOS 10, iOS 11 y 12. En diferentes dispositivos. Solo tenía que hacerlo. No puedo ir a casa sin resolver este demonio. Esto es lo más perfecto que podría hacer por hoy, dada mi apretada fecha límite.
Así que solo quiero compartir este arduo trabajo que hice, depende de usted poner todo en donde quiera (por ejemplo, variables en su modelo de vista). Aquí va:
En mi primera pantalla (digamos pantalla de inicio, que no tiene este controlador de búsqueda), tengo esto en mi
viewDidLoad()
.
self.extendedLayoutIncludesOpaqueBars = true
En mi segunda pantalla, la que tiene el SearchController, tengo esto en mi
viewDidAppear
.
anular func viewDidAppear (_ animated: Bool) {super.viewDidAppear (animado)
let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
if systemMajorVersion < 12 {
// Place the search bar in the navigation item''s title view.
self.navigationItem.titleView = self.searchController.searchBar
}
if systemMajorVersion >= 11 {
self.extendedLayoutIncludesOpaqueBars = true
UIView.animate(withDuration: 0.3) {
self.navigationController?.navigationBar.setNeedsLayout()
self.navigationController?.navigationBar.layoutIfNeeded()
}
self.tableView.contentInset = UIEdgeInsets(top: -40, left: 0, bottom: 0, right: 0)
if self.viewHadAppeared {
self.tableView.contentInset = .zero
}
}
self.viewHadAppeared = true // this is set to false by default.
}
y aquí está mi declaración de SearchController:
lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.textField?.backgroundColor = .lalaDarkWhiteColor
searchController.searchBar.textField?.tintColor = .lalaDarkGray
searchController.searchBar.backgroundColor = .white
return searchController
}()
Así que espero que esto ayude a alguien algún día.
Intenté varias cosas para que el tamaño volviera al 44 original, pero luego la barra de búsqueda siempre se ve y se comporta de forma extraña, como estar demasiado estirado, con desplazamiento en y y similar.
Encontré una buena solución aquí (a través de alguna otra publicación de ): https://github.com/DreamTravelingLight/searchBarDemo
Simplemente obtenga su viewcontroller del SearchViewController e incluya en su proyecto las clases SearchViewController y WMSearchbar. Salió de la caja para mí sin ningún tipo de feo si (iOS11) más ... fealdad.
Lo arreglé agregando la restricción para verDidAppear en el controlador de vista del mapa donde está incrustada la barra de búsqueda
public override func viewDidAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
resultSearchController?.searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
// searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
}
No pude usar la solución de mantener la barra de navegación en 44. Así que me llevó un día, pero finalmente, encontré una solución que no cambia la altura de la barra y coloca el botón en el medio de la barra. El problema es que los botones se colocan en una vista de pila que está configurada como vista de pila horizontal y, por lo tanto, no se ajusta al cambio de altura.
Esto se hace en init:
-(void)viewDidLoad
{
[super viewDidLoad];
self.extendedLayoutIncludesOpaqueBars = YES;
...
}
on viewWillApear (o en cualquier momento después de que la vista se haya agregado a la pila de navegación)
override func viewDidLoad() {
super.viewDidLoad()
self.extendedLayoutIncludesOpaqueBars = true
}
Y subviewOfClass es una categoría en UIView:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// force update layout
[self.navigationController.view setNeedsLayout];
// to fix height of the navigation bar
[self.navigationController.view layoutIfNeeded];
}
No puedo comentar, pero quería compartir algunos problemas adicionales con los que me encontré al pasar muchas horas tratando de llegar al fondo de este problema, incluso después de usar una de las otras soluciones.
Parece que la mejor solución para mí fue la respuesta de Andrew :
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout() // force update layout
navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}
Sin embargo, al menos
en iOS 12.1
, si su
UINavigationBar
:
-
tiene
isTranslucent
establecido enfalse
, el controlador de vista con la barra de búsqueda parece no volver a ajustar el diseño de su vista cuando se descarta de forma interactiva (el descarte normal a través del botón de retroceso parece funcionar). -
tiene su imagen de fondo establecida usando
setBackgroundImage(UIImage(), for: .default)
, la animación de transición no funciona correctamente y volverá a su posición después de terminar.
Sin embargo, estas propiedades particulares se configuraron para que la barra de navegación apareciera de cierta manera, por lo que necesito hacer algunos ajustes para recuperarla o tolerar el comportamiento extraño. Intentaré recordar actualizar lo anterior si me encuentro con algo más o si encuentro otras soluciones o diferencias en otras versiones del sistema operativo.
Obtuve una línea negra debajo de NavigationBar con SearchBar en iOS 11 en dos casos:
-
cuando empujé otro ViewControllers desde ViewController con UISearchBar
-
cuando descarté ViewController con UISearchBar con "arrastrar hacia la derecha para descartar"
Mi solución fue: agregar este código a mi ViewController con UISearchBar:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.navigationController.view setNeedsLayout]; // force update layout
[self.navigationController.view layoutIfNeeded]; // to fix height of the navigation bar
}
Actualización de Swift 4
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout() // force update layout
navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}
Puede agregar una restricción de altura 44 a la barra de búsqueda para iOS 11.
if #available(iOS 11.0, *) {
searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
// C objetivo
if (@available(iOS 11.0, *)) {
[searchBar.heightAnchor constraintEqualToConstant:44].active = YES;
}
Toda la solución no funcionó para mí, así que antes de presionar el controlador de vista lo hice:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationItem.titleView = UIView()
}
Y para que la barra de búsqueda esté presente cuando regrese:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.titleView = UISearchBar()
}
Todo lo que tiene que hacer es subclasificar UISearchBar y anular "intrinsicContentSize":
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout() // force update layout
navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}
pruebe este código en el controlador de vista "AGRADECIMIENTOS" en viewDidLoad
self.extendedLayoutIncludesOpaqueBars = true
En el objetivo-C
if (@available(iOS 11.0, *)) {
[self.searchBar.heightAnchor constraintLessThanOrEqualToConstant: 44].active = YES;
}
//
// Created by Sang Nguyen on 10/23/17.
// Copyright © 2017 Sang. All rights reserved.
//
import Foundation
import UIKit
class CustomSearchBarView: UISearchBar {
final let SearchBarHeight: CGFloat = 44
final let SearchBarPaddingTop: CGFloat = 8
override open func awakeFromNib() {
super.awakeFromNib()
self.setupUI()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// fatalError("init(coder:) has not been implemented")
}
func findTextfield()-> UITextField?{
for view in self.subviews {
if view is UITextField {
return view as? UITextField
} else {
for textfield in view.subviews {
if textfield is UITextField {
return textfield as? UITextField
}
}
}
}
return nil;
}
func setupUI(){
if #available(iOS 11.0, *) {
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(equalToConstant: SearchBarHeight).isActive = true
}
}
override func layoutSubviews() {
super.layoutSubviews()
if #available(iOS 11.0, *) {
if let textfield = self.findTextfield() {
textfield.frame = CGRect(x: textfield.frame.origin.x, y: SearchBarPaddingTop, width: textfield.frame.width, height: SearchBarHeight - SearchBarPaddingTop * 2)`enter code here`
return
}
}
}
}