Как включить кнопку отмены в UISearchBar?

В приложении «Контакты» на iPhone, если вы вводите поисковый запрос, затем нажимаете кнопку «Поиск», клавиатура скрывается, НО кнопка отмены все еще активна. В моем приложении кнопка отмены отключается, когда я вызываю resignFirstResponder.

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

Я использую следующий код:

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    [searchBar resignFirstResponder];
}

Клавиатура выскальзывает из поля зрения, но кнопка «Отмена» справа от текстового поля поиска отключена, поэтому я не могу отменить поиск. Приложение контактов поддерживает кнопку отмены во включенном состоянии.

Я думаю, что, возможно, одно из решений - погрузиться в объект searchBar и вызвать resignFirstResponder в фактическом текстовом поле, а не в самой строке поиска.

Любой вклад приветствуется.


person Christopher    schedule 01.04.2012    source источник
comment
Если кто-то ищет решение C #, его можно найти здесь.   -  person testing    schedule 09.01.2015


Ответы (22)


попробуй это

for(id subview in [yourSearchBar subviews])
{
    if ([subview isKindOfClass:[UIButton class]]) {
        [subview setEnabled:YES];
    }
}
person Malek_Jundi    schedule 01.04.2012
comment
См. Мою записку Бену ниже. Я решил удалить код для доступа к внутреннему устройству, чтобы избежать каких-либо проблем. Нажатие на кружок x в поле поиска снова активирует кнопку отмены, и, надеюсь, это не будет проблемой для пользователей. - person Christopher; 19.11.2012
comment
Это решение не сработает, если вы начнете прокручивать таблицу вместо нажатия кнопки «Поиск». В этом случае кнопка «Отмена» будет отключена. См. Мой старый ответ ниже, посвященный этой проблеме. - person Marián Černý; 30.01.2017
comment
Это не будет работать в iOS 10. Иерархия представлений UISearchBar была изменена. Вам нужно пойти на один уровень глубже. - person Aliaksandr Bialiauski; 18.04.2017

Этот метод работал в iOS7.

- (void)enableCancelButton:(UISearchBar *)searchBar
{
    for (UIView *view in searchBar.subviews)
    {
        for (id subview in view.subviews)
        {
            if ( [subview isKindOfClass:[UIButton class]] )
            {
                [subview setEnabled:YES];
                NSLog(@"enableCancelButton");
                return;
            }
        }
    }
}

(Также не забудьте вызвать его где-нибудь после использования [_searchBar resignFirstResponder].)

person David Douglas    schedule 09.08.2013
comment
+1 для Также не забудьте вызвать его где-нибудь после использования [_searchBar resignFirstResponder]. - person onmyway133; 02.09.2014
comment
Этот ответ сработал, но ответ, помеченный как правильный в этой ветке, не работал в iOS 10. - person coolcool1994; 30.07.2017

Принятое решение не сработает, когда вы начнете прокручивать таблицу вместо нажатия кнопки «Поиск». В этом случае кнопка «Отменить» будет неактивна.

Это мое решение, которое повторно включает кнопку «Отмена» каждый раз, когда она отключается с помощью KVO.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // Search for Cancel button in searchbar, enable it and add key-value observer.
    for (id subview in [self.searchBar subviews]) {
        if ([subview isKindOfClass:[UIButton class]]) {
            [subview setEnabled:YES];
            [subview addObserver:self forKeyPath:@"enabled" options:NSKeyValueObservingOptionNew context:nil];
        }
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    // Remove observer for the Cancel button in searchBar.
    for (id subview in [self.searchBar subviews]) {
        if ([subview isKindOfClass:[UIButton class]])
            [subview removeObserver:self forKeyPath:@"enabled"];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // Re-enable the Cancel button in searchBar.
    if ([object isKindOfClass:[UIButton class]] && [keyPath isEqualToString:@"enabled"]) {
        UIButton *button = object;
        if (!button.enabled)
            button.enabled = YES;
    }
}
person Marián Černý    schedule 13.09.2012
comment
Хорошее решение. Обратите внимание, что вы можете захватывать события прокрутки, реализовав - (void)scrollViewDidScroll:(UIScrollView *)scrollView и снова включив кнопку там. - person Olivier; 15.05.2014

В iOS 6 кнопка выглядит как UINavigationButton (частный класс) вместо UIButton.

Я изменил приведенный выше пример, чтобы он выглядел так.

for (UIView *v in searchBar.subviews) {
    if ([v isKindOfClass:[UIControl class]]) {
        ((UIControl *)v).enabled = YES;
    }
}

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

Мы должны попросить Apple разоблачить это.

person Ben L.    schedule 26.10.2012
comment
Спасибо, Бен! Я провел несколько тестов с моим существующим кодом и обнаружил, что если я коснусь кружка x в поле поиска, кнопка отмены снова активируется. Это на iOS 5.1. Я еще не перешел на iOS 6. Поэтому я решил удалить код, чтобы снова включить кнопку отмены, так как я хочу избежать проблем с доступом к внутренним компонентам. Как вы говорите, было бы неплохо, если бы API разрешил нам прямой доступ к этой кнопке. Я планирую пока перестраховаться :-) Спасибо за публикацию. - person Christopher; 19.11.2012
comment
Я смешал этот подход с дополнительной задержкой - иначе setEnabled не будет распознан или перезаписан внутри ... см. Заголовок stackoverflow.com/questions/8536625/uisearchbar-cancel-button - person ff10; 12.12.2012

Мне показалось, что это сработало (в viewDidLoad):

__unused UISearchDisplayController* searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];

Я понимаю, что, вероятно, мне следовало бы правильно использовать UISearchDisplayController, но это было несложным решением для моей текущей реализации.

person n8chur    schedule 01.10.2013
comment
Самое чистое решение для меня - person Matt Green; 18.11.2013

Вы можете использовать API среды выполнения для доступа к кнопке отмены.

UIButton *btnCancel = [self.searchBar valueForKey:@"_cancelButton"];
[btnCancel setEnabled:YES];
person Burhanuddin Sunelwala    schedule 13.07.2015
comment
Разве этот доступ к среде выполнения не является частным API и возможностью отклонения приложения? - person kyleturner; 18.07.2015
comment
Нет, это не частный API, а кодирование ключевого значения. Заявка не будет отклонена. - person Burhanuddin Sunelwala; 20.07.2015

Я расширил то, что уже опубликовали другие, реализовав это как простую категорию на UISearchBar.

UISearchBar + alwaysEnableCancelButton.h

#import <UIKit/UIKit.h>

@interface UISearchBar (alwaysEnableCancelButton)

@end

UISearchBar + alwaysEnableCancelButton.m

#import "UISearchBar+alwaysEnableCancelButton.h"

@implementation UISearchBar (alwaysEnableCancelButton)

- (BOOL)resignFirstResponder
{
    for (UIView *v in self.subviews) {
        // Force the cancel button to stay enabled
        if ([v isKindOfClass:[UIControl class]]) {
            ((UIControl *)v).enabled = YES;
        }

        // Dismiss the keyboard
        if ([v isKindOfClass:[UITextField class]]) {
            [(UITextField *)v resignFirstResponder];
        }
    }

    return YES;
}
@end
person Brains    schedule 25.01.2013
comment
Похоже, это должно сработать, и я считаю его более чистым решением, чем мое решение с KVO. +1 - person Marián Černý; 30.01.2017

Вот немного более надежное решение, которое работает на iOS 7. Оно рекурсивно просматривает все подпредставления панели поиска, чтобы убедиться, что оно включает все UIControls (включая кнопку «Отмена»).

- (void)enableControlsInView:(UIView *)view
{
    for (id subview in view.subviews) {
        if ([subview isKindOfClass:[UIControl class]]) {
            [subview setEnabled:YES];
        }
        [self enableControlsInView:subview];
    }
}

Просто вызовите этот метод сразу после вызова [self.searchBar resignFirstResponder] следующим образом:

[self enableControlsInView:self.searchBar];

Вуаля! Кнопка отмены остается активной.

person smileyborg    schedule 26.09.2013

До iOS 12 можно использовать так: -

if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "_cancelButton") as? UIButton{
    cancelButton.isEnabled = true
}

Начиная с iOS 13, если вы используете как (forKey: "_cancelButton"), такое использование частного API перехватывается и, к сожалению, приводит к сбою.

Для iOS 13+ и swift 5+

if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
    cancelButton.isEnabled = true
}
person Vipul Kumar    schedule 03.03.2020

Я нашел другой способ заставить его работать в iOS 7.

Я пробую что-то вроде приложения Twitter для iOS. Если щелкнуть увеличительное стекло на вкладке «Временная шкала», появится UISearchBar с активированной кнопкой «Отмена», отображением клавиатуры и экраном недавних поисков. Прокрутите экран недавних поисков, он скрывает клавиатуру, но оставляет кнопку «Отмена» нажатой.

Это мой рабочий код:

UIView *searchBarSubview = self.searchBar.subviews[0];
NSArray *subviewCache = [searchBarSubview valueForKeyPath:@"subviewCache"];
if ([subviewCache[2] respondsToSelector:@selector(setEnabled:)]) {
    [subviewCache[2] setValue:@YES forKeyPath:@"enabled"];
}

Я пришел к этому решению, установив точку останова в scrollViewWillBeginDragging: моего табличного представления. Я заглянул в свой UISearchBar и обнажил его подвиды. Он всегда имеет только один тип UIView (моя переменная searchBarSubview).

введите описание изображения здесь

Затем этот UIView содержит NSArray с именем subviewCache, и я заметил, что последний элемент, который является третьим, имеет тип UINavigationButton, а не в общедоступном API. Поэтому я решил вместо этого использовать кодирование «ключ-значение». Я проверил, отвечает ли UINavigationButton на setEnabled:, и, к счастью, да. Поэтому я установил для свойства значение @YES. Оказывается, это UINavigationButton кнопка "Отмена".

Это обязательно сломается, если Apple решит изменить реализацию внутренностей UISearchBar, но что за черт. Пока работает.

person MLQ    schedule 25.02.2014
comment
Спасибо, Мэтт. Я попробую это, когда у меня будет возможность. - person Christopher; 27.02.2014

Версия SWIFT для ответа Дэвида Дугласа (проверено на iOS9)

func enableSearchCancelButton(searchBar: UISearchBar){
    for view in searchBar.subviews {
        for subview in view.subviews {
            if let button = subview as? UIButton {
                button.enabled = true
            }
        }
    }
}
person Tal Haham    schedule 05.11.2015

Большинство опубликованных решений не являются надежными и позволяют отключать кнопку «Отмена» при различных обстоятельствах.

Я попытался реализовать решение, при котором кнопка «Отмена» всегда остается включенной, даже если вы выполняете более сложные операции с панелью поиска. В Swift 4 это реализовано в виде настраиваемого подкласса UISearchView. Он использует трюк со значением (forKey :), чтобы найти и кнопку отмены, и текстовое поле поиска, и прослушивает, когда поле поиска заканчивает редактирование, и повторно включает кнопку отмены. Он также включает кнопку отмены при переключении флага showCancelButton.

Он содержит несколько утверждений, чтобы предупредить вас, если внутренние детали UISearchBar когда-либо изменятся и не позволят ему работать.

import UIKit

final class CancelSearchBar: UISearchBar {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        guard let searchField = value(forKey: "_searchField") as? UIControl else {
            assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
            return
        }

        searchField.addTarget(self, action: #selector(enableSearchButton), for: .editingDidEnd)
    }

    override var showsCancelButton: Bool {
        didSet { enableSearchButton() }
    }

    @objc private func enableSearchButton() {
        guard showsCancelButton else { return }
        guard let cancelButton = value(forKey: "_cancelButton") as? UIControl else {
            assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
            return
        }

        cancelButton.isEnabled = true
    }
}
person Dag Ågren    schedule 03.12.2017
comment
Красиво и хорошо работает! Обратите внимание, что в iOS 11 эти свойства называются searchField и cancelButton, без подчеркивания. - person GSnyder; 06.06.2018

Основываясь на ответе смайлика, просто поместите это в свой делегат searchBar:

- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{   
    dispatch_async(dispatch_get_main_queue(), ^{
        __block __weak void (^weakEnsureCancelButtonRemainsEnabled)(UIView *);
        void (^ensureCancelButtonRemainsEnabled)(UIView *);
        weakEnsureCancelButtonRemainsEnabled = ensureCancelButtonRemainsEnabled = ^(UIView *view) {
            for (UIView *subview in view.subviews) {
                if ([subview isKindOfClass:[UIControl class]]) {
                [(UIControl *)subview setEnabled:YES];
                }
                weakEnsureCancelButtonRemainsEnabled(subview);
            }
        };

        ensureCancelButtonRemainsEnabled(searchBar);
    });
 }

Это решение хорошо работает на iOS 7 и выше.

person followben    schedule 05.09.2014

Для iOS 10, Swift 3:

for subView in self.movieSearchBar.subviews {
    for view in subView.subviews {
        if view.isKind(of:NSClassFromString("UIButton")!) {
            let cancelButton = view as! UIButton
            cancelButton.isEnabled = true
        }
    }
}
person Saoud Rizwan    schedule 09.01.2017

Для iOS 9/10 (протестировано), Swift 3 (короче):

searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true })
person neegra    schedule 05.07.2017

Для iOS 11 и выше, Swift 4-5:

extension UISearchBar {
  func alwaysShowCancelButton() {
    for subview in self.subviews {
      for ss in subview.subviews {
        if #available(iOS 13.0, *) {
          for s in ss.subviews {
            self.enableCancel(with: s)
          }
        }else {
          self.enableCancel(with: ss)
        }
      }
    }
  }
  private func enableCancel(with view:UIView) {
   if NSStringFromClass(type(of: view)).contains("UINavigationButton") {
      (view as! UIButton).isEnabled = true
    }
  }
}

UISearchBarDelegate

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    self.searchBar.resignFirstResponder()
    self.searchBar.alwaysShowCancelButton()
  }
person Giang    schedule 26.01.2020

Вы можете создать свой CustomSearchBar, унаследованный от UISearchBar, и реализовать этот метод:

- (void)layoutSubviews {

    [super layoutSubviews];

    @try {
        UIView *baseView = self.subviews[0];

        for (UIView *possibleButton in baseView.subviews)
        {
            if ([possibleButton respondsToSelector:@selector(setEnabled:)]) {
                [(UIControl *)possibleButton setEnabled:YES];
            }
        }
    }
    @catch (NSException *exception) {
        NSLog(@"ERROR%@",exception);
    }
}
person Diego Lima    schedule 03.03.2015

Лучшее решение

[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;
person migrant    schedule 20.10.2016

Лучший и простой метод:

[(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES];
person Lal Krishna    schedule 22.06.2017

Swift 5 и iOS 14

if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
    cancelButton.isEnabled = true
}
person oskarko    schedule 13.03.2021

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

let cancelButtonAppearance = UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self])
cancelButtonAppearance.isEnabled = true
cancelButtonAppearance.tag = -4321

Затем мы можем использовать тег, здесь магическое число -4321, чтобы найти тег:

extension UISearchBar {
    var cancelButton: UIControl? {
         func recursivelyFindButton(in subviews: [UIView]) -> UIControl? {
             for subview in subviews.reversed() {
                 if let control = subview as? UIControl, control.tag == -4321 {
                     return control
                 }
                 if let button = recursivelyFindButton(in: subview.subviews) {
                     return button
                 }
             }
             return nil
         }
         return recursivelyFindButton(in: subviews)
     }
}

И, наконец, используйте searchBar.cancelButton?.isEnabled = true всякий раз, когда панель поиска теряет фокус, например, в делегате. (Или, если вы используете настраиваемый подкласс и вызываете setShowsCancelButton из делегата, вы можете переопределить эту функцию, чтобы также активировать кнопку всякий раз, когда она отображается.)

person Arkku    schedule 09.04.2021
comment
(Изменение порядка subviews сокращает количество шагов с 17 до 3 в iOS 14.4, но, конечно, это необязательно и может быть изменено.) - person Arkku; 09.04.2021

person    schedule
comment
Некоторое объяснение было бы неплохо - person John Dvorak; 04.03.2014