it-swarm.com.ru

UISearchBar увеличивает высоту панели навигации в iOS 11

У меня есть UISearchBar, являющийся частью панели навигации, например:

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

После обновления до iOS 11 что-то странное случилось с панелью поиска в моем приложении. На iOS 10 и ранее моя навигационная панель выглядела так:

 enter image description here

Теперь с iOS 11 у меня есть: 

 enter image description here

Как вы можете видеть, есть разница в округлении двух полос поиска, что меня не беспокоит. Проблема в том, что панель поиска увеличивает высоту панели навигации. Поэтому, когда я перехожу на другой контроллер, он тоже выглядит странно:

 enter image description here

Фактически, эта странная черная линия плюс высота текущей панели навигации равна высоте панели навигации, показанной на втором рисунке ... 

Любые идеи, как избавиться от черной линии и иметь одинаковую высоту панели навигации на всех контроллерах представления? 

69
radioaktiv

Вы можете добавить ограничение высоты 44 в строку поиска для iOS 11.

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

// Objective-c 

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

Я получил черную линию под NavigationBar с SearchBar в iOS 11 в двух случаях:

  • когда я выдвинул еще один ViewControllers из ViewController с помощью UISearchBar  enter image description here

  • когда я отклонил ViewController с помощью UISearchBar с помощью «перетащить вправо, чтобы уволить»  enter image description here

Мое решение было: добавить этот код в мой ViewController с помощью 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
}

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

Я полагаю, что в iOS 11 UISearchBar теперь имеет высоту, равную 56, а UINavigationBar использует автоматическое расположение, чтобы соответствовать его подпредставлениям, следовательно, оно увеличивает высоту. Если вы по-прежнему хотите использовать UISearchBar в качестве titleView, как в предыдущей версии iOS 11, я обнаружил, что лучший способ сделать это - встроить UISearchBar в настраиваемое представление, установить высоту этого представления на 44 и назначить его навигационному элементу.

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

попробуйте этот код на контроллере представления «ACKNOWLEDGMENTS»in viewDidLoad

self.extendedLayoutIncludesOpaqueBars = true
11
Silverwind

В Objective-C

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

Я не мог использовать решение для сохранения navBar на уровне 44 .... Так что мне потребовался день, но в конце концов я нашел решение, которое не изменяет высоту панели и не размещает кнопку в середине панели. Проблема заключается в том, что кнопки размещены в виде стека, который настроен как горизонтальный вид стека и, следовательно, не адаптируется к изменению высоты. 

Это делается на 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)];
}

on viewWillApear (или в любое время после добавления представления в стек навигации)

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

И subviewOfClass - это категория в 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

Спасибо вам всем! Я наконец нашел решение.

Добавление следующего кода в ViewController с помощью UISearchBar.

  1. Первый шаг: viewDidLoad
-(void)viewDidLoad
{
    [super viewDidLoad];
    self.extendedLayoutIncludesOpaqueBars = YES;
    ...
}
override func viewDidLoad() {
    super.viewDidLoad()
    self.extendedLayoutIncludesOpaqueBars = true
}
  1. Второй шаг: 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

Все, что вам нужно сделать, это создать подкласс UISearchBar и переопределить «intrinsicContentSize»:

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

 enter image description here

0
Jagie

Я нашел решение Mai Mai единственным, которое действительно пригодно для использования.
Однако это все еще не идеально:
При вращении устройства строка поиска не изменяется должным образом и остается в меньшем размере. 

Я нашел решение для этого. Вот мой код в Objective C с соответствующими частями аннотирован:

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

Теперь остался еще один случай, который я еще не выяснил. Для воспроизведения сделайте следующее:
- начать в портретной
- активировать поле поиска
- повернуть в ландшафт
- ошибка: размер панели не изменяется 

0
de.

В моем случае, большая высота UINavigationBar не была для меня проблемой. Мне просто нужно перестроить элементы левой и правой панели кнопок. Вот решение, которое я придумал:

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

По сути, мы ищем представления стека, которые содержат элементы кнопок панели, а затем меняем их верхние и нижние значения ограничений. Да, это грязный хак, и я не буду рекомендовать его использовать, если вы можете исправить свою проблему любым другим способом.

0
Roman Serga

Все решения не работали для меня, поэтому, прежде чем я нажал на контроллер просмотра, я сделал:

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

    self.navigationItem.titleView = UIView()
}

И сделать панель поиска при возвращении: 

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

    self.navigationItem.titleView = UISearchBar()
}
0
Mantas Laurinavičius

Я исправил это, добавив ограничение на viewDidAppear на контроллере вида карты, в который встроена панель поиска.

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

Не удалось прокомментировать, но хотел поделиться некоторыми дополнительными проблемами, с которыми я столкнулся, потратив много часов, пытаясь докопаться до сути этой проблемы даже после использования одного из других решений. 

Похоже, лучшим решением для меня было ответ Эндрю :

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.view.setNeedsLayout() // force update layout
    navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}

Однако, по крайней мере, в iOS 12.1, если ваша UINavigationBar:

  • если для isTranslucent установлено значение false, контроллер представления с панелью поиска, по-видимому, не получает обратно настроенную компоновку своего представления при интерактивном отклонении (нормальное отклонение с помощью кнопки «назад» работает).
  • если его фоновое изображение установлено с помощью setBackgroundImage(UIImage(), for: .default), анимация перехода не будет работать должным образом и после завершения вернется к своей позиции.

Однако эти особые свойства были настроены так, чтобы панель навигации отображалась определенным образом, поэтому мне нужно внести некоторые коррективы, чтобы вернуть ее назад или смириться со странным поведением. Постараюсь не забыть обновить вышеупомянутое, если я столкнусь с чем-либо еще или найду другие решения или различия в других версиях ОС.

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

Привет людям, которые используют UISearchController и затем присоединяют его UISearchBar к navigationItem.titleView. Я потратил 4-5 часов своего дня, чтобы решить эту проблему. Следуя рекомендованному iOS 11+ подходу, добавление searchController к navigation.searchController не совсем подходит для моего случая. Экран с этим searchController/searchBar имеет пользовательскую кнопку BackButton.

Я проверил это в iOS 10, iOS 11 и 12. На разных устройствах. Я просто должен был. Я не могу пойти домой без разгадки этого демона. Это самое лучшее, что я мог сделать на сегодня, учитывая мой сжатый срок.

Так что я просто хочу поделиться этой тяжелой работой, которую я проделал, от вас зависит, чтобы вы поместили все куда угодно, например, в переменную вашего viewModel Здесь это идет:

На моем первом экране (скажем, на главном экране, на котором нет этого контроллера поиска), у меня это есть в моей функции viewDidLoad().

self.extendedLayoutIncludesOpaqueBars = true

На моем втором экране, тот, который имеет searchController, у меня есть это в моей viewDidAppear.

переопределить функцию viewDidAppear (_ animated: Bool) { super.viewDidAppear (анимированный)

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

и вот объявление моего 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
}()

Поэтому я надеюсь, что это поможет кому-нибудь когда-нибудь.

0
Glenn

Правка: Ответ @zgjie является лучшим решением для этой проблемы: https://stackoverflow.com/a/46356265/1713123

Похоже, это происходит потому, что в iOS 11 значение высоты SearchBar по умолчанию было изменено на 56, вместо 44 в предыдущих версиях iOS.

Сейчас я применил этот обходной путь, установив высоту searchBar обратно в 44:

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

Другим решением может быть использование свойства new searchController для navigationItem в iOS 11 :

navigationItem.searchController = searchController

Но таким образом да searchBar появляется под заголовком навигации.

0
Alexandre Morgado