web-dev-qa-db-fra.com

UISearchBar augmente la hauteur de la barre de navigation dans iOS 11

Ma UISearchBar fait partie de la barre de navigation comme:

 let searchBar = UISearchBar()
 //some more configuration to the search bar
 .....
 navigationItem.titleView = searchBar

Après la mise à jour vers iOS 11, il s'est passé quelque chose de bizarre dans la barre de recherche de mon application. Sur iOS 10 et avant, ma barre de navigation ressemblait à ceci:

 enter image description here

Maintenant avec iOS 11 j'ai: 

 enter image description here

Comme vous pouvez le constater, l’arrondi des deux barres de recherche présente une différence qui ne me gêne pas. Le problème est que la barre de recherche augmente la hauteur de la barre de navigation. Donc, quand je vais sur un autre contrôleur, ça a l'air bizarre aussi:

 enter image description here

En fait, la hauteur de cette ligne noire étrange, plus la hauteur de la barre de navigation actuelle, est égale à la hauteur de la barre de navigation indiquée dans la deuxième image ... 

Des idées pour se débarrasser de la ligne noire et obtenir une hauteur de barre de navigation cohérente sur tous les contrôleurs de vue? 

69
radioaktiv

Vous pouvez ajouter une contrainte de hauteur 44 à la barre de recherche pour iOS 11.

if #available(iOS 11.0, *) {
    searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
}

// Objectif c 

   if (@available(iOS 11.0, *)) {
      [searchBar.heightAnchor constraintEqualToConstant:44].active = YES;
    }
63
zgjie

J'ai une ligne noire sous NavigationBar avec SearchBar dans iOS 11 dans deux cas:

  • quand j'ai poussé un autre ViewControllers de ViewController avec UISearchBar  enter image description here

  • quand j'ai rejeté ViewController avec UISearchBar avec "glisser à droite pour rejeter"  enter image description here

Ma solution était la suivante: ajouter ce code à mon ViewController avec 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
}

Mise à jour 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
}
40
Andrew

Je crois que dans iOS 11, UISearchBar a maintenant une hauteur égale à 56, et UINavigationBar utilise autolayout pour s’adapter à ses sous-vues, ce qui augmente sa hauteur. Si vous souhaitez toujours utiliser UISearchBar comme titleView comme dans les versions antérieures à iOS 11, la meilleure façon de le faire consiste à incorporer UISearchBar dans une vue personnalisée et à définir la hauteur de cette vue sur 44, puis de l'attribuer à 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  
    }  
} 
32
Mai Mai

essayez ce code sur le contrôleur de vue "REMERCIEMENTS" in viewDidLoad

self.extendedLayoutIncludesOpaqueBars = true
11
Silverwind

En Objective-C

if (@available(iOS 11.0, *)) {
        [self.searchBar.heightAnchor constraintLessThanOrEqualToConstant: 44].active = YES;
}              
4
Hassy

Je ne pouvais pas utiliser la solution consistant à maintenir la barre de navigation à 44… .. Il m'a donc fallu un jour, mais j'ai finalement trouvé une solution qui ne modifiait pas la hauteur de la barre et ne plaçait pas le bouton au milieu de la barre. Le problème est que les boutons sont placés dans une vue de pile configurée en tant que vue de pile horizontale et ne s'adaptent donc pas au changement de hauteur. 

Ceci est fait sur init:

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

sur viewWillApear (ou à tout moment après que la vue a été ajoutée à la pile de navigation)

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

Et subviewOfClass est une catégorie sur UIView:

- (__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;
}
1
Zeev Vax

Merci à tous! J'ai finalement trouvé une solution.

Ajout du code suivant à ViewController avec UISearchBar.

  1. Première étape: viewDidLoad
-(void)viewDidLoad
{
    [super viewDidLoad];
    self.extendedLayoutIncludesOpaqueBars = YES;
    ...
}
override func viewDidLoad() {
    super.viewDidLoad()
    self.extendedLayoutIncludesOpaqueBars = true
}
  1. Deuxième étape: viewWillDisappear
-(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];  
}
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.view.setNeedsLayout() // force update layout
        navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
    }
1
BlessNeo

Tout ce que vous avez à faire est de sous-classer UISearchBar et de remplacer "intrinsicContentSize":

@implementation CJSearchBar
-(CGSize)intrinsicContentSize{
    CGSize s = [super intrinsicContentSize];
    s.height = 44;
    return s;
}
@end

 enter image description here

0
Jagie

J'ai trouvé que la solution de Mai Mai était la seule réellement utilisable.
Cependant ce n'est pas encore parfait:
Lorsque vous faites pivoter l'appareil, la barre de recherche n'est pas correctement redimensionnée et reste dans la plus petite dimension. 

J'ai trouvé un correctif pour cela. Voici mon code en Objective C avec les parties pertinentes annotées:

// 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

Maintenant, il reste encore un cas que je n'ai pas encore compris. Pour reproduire, procédez comme suit:
- commence en portrait
- activer le champ de recherche
- rotation en paysage
erreur -: la barre ne redimensionne pas 

0
de.

Dans mon cas, la taille plus grande de UINavigationBar ne me posait aucun problème. J'avais juste besoin de réaligner les éléments des boutons gauche et droit. C'est la solution que j'ai trouvée:

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

Fondamentalement, nous recherchons des vues de pile contenant des éléments de bouton à barres, puis en modifiant les valeurs de leurs contraintes supérieure et inférieure. Oui, c'est un sale bricolage, et je ne recommanderai pas de l'utiliser si vous pouvez résoudre votre problème d'une autre manière.

0
Roman Serga

Toutes les solutions ne fonctionnaient pas pour moi, alors avant d’appuyer sur le contrôleur de vue, je le faisais:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    self.navigationItem.titleView = UIView()
}

Et pour rendre la barre de recherche présente lors du retour: 

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    self.navigationItem.titleView = UISearchBar()
}
0

J'ai corrigé cela en ajoutant la contrainte de viewDidAppear sur le contrôleur de vue cartographique où la barre de recherche est intégrée

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
    }
}
0
Akshay

Je suis incapable de commenter, mais je voulais partager quelques problèmes supplémentaires que j'ai rencontrés alors que je passais de nombreuses heures à essayer de faire la lumière sur ce problème, même après avoir utilisé l'une des autres solutions. 

Il semble que la meilleure solution pour moi était la réponse d'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
}

Cependant, à tout le moins sous iOS 12.1, si votre UINavigationBar:

  • si isTranslucent est défini sur false, le contrôleur de la vue avec la barre de recherche ne semble pas avoir la disposition de sa vue modifiée lors de la suppression interactive (la suppression normale via le bouton Précédent semble fonctionner).
  • si l'image d'arrière-plan est définie à l'aide de setBackgroundImage(UIImage(), for: .default), l'animation de transition ne fonctionne pas correctement et reviendra à sa position une fois l'opération terminée.

Ces propriétés particulières ont été définies pour que la barre de navigation apparaisse d'une certaine manière. Je dois donc faire quelques ajustements pour la récupérer ou supporter le comportement étrange. J'essaierai de me rappeler de mettre à jour ce qui précède si je rencontre autre chose ou trouve d'autres solutions ou différences dans d'autres versions de système d'exploitation.

0
Ohifriend
//
//  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
            }
        }
    }
}
0
Sang_longan

Bonjour aux personnes qui utilisent UISearchController et qui attachent ensuite leur UISearchBar au navigationItem.titleView. J'ai passé 4-5 heures folles de ma journée à résoudre ce problème. Suivre l'approche recommandée par iOS 11+, qui consiste à placer la searchController dans le navigation.searchController, ne convient pas uniquement à mon cas. L'écran qui a ce searchController/searchBar a un backButton, un personnalisé.

J'ai testé cela sous iOS 10, iOS 11 et 12. Sous différents appareils. Je devais juste le faire. Je ne peux pas rentrer à la maison sans résoudre ce démon. C’est le plus parfait que je puisse faire pour aujourd’hui, étant donné mon échéancier serré.

Donc, je veux juste partager ce dur travail que j'ai fait, c'est à vous de tout mettre où vous voulez (par exemple des variables dans votre viewModel). Ici ça va:

Dans mon premier écran (disons l'écran d'accueil, qui n'a pas ce contrôleur de recherche), j'ai ceci dans ma viewDidLoad().

self.extendedLayoutIncludesOpaqueBars = true

Dans mon deuxième écran, celui qui a le searchController, je l'ai dans ma viewDidAppear.

remplacer func viewDidAppear (_ animated: Bool) { super.viewDidAppear (animé)

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

et voici ma déclaration 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
}()

J'espère donc que cela aidera quelqu'un un jour.

0
Glenn

EDIT: La réponse @zgjie est une meilleure solution à ce problème: https://stackoverflow.com/a/46356265/1713123

Cela semble se produire car dans iOS 11, la valeur de hauteur par défaut de SearchBar a été modifiée à 56, au lieu de 44 sur les versions précédentes d'iOS.

Pour l'instant, j'ai appliqué cette solution de contournement en définissant la hauteur de la barre de recherche à 44:

let barFrame = searchController.searchBar.frame
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: barFrame.width, height: 44)    

Une autre solution pourrait être d'utiliser la propriété new searchController sur navigationItem dans iOS 11 :

navigationItem.searchController = searchController

Mais de cette façon, un searchBar apparaît sous le titre de navigation.

0
Alexandre Morgado