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

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

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

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

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

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

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

любой входной ценится.

18 ответов


попробуй такое

for(id subview in [yourSearchBar subviews])
{
    if ([subview isKindOfClass:[UIButton class]]) {
        [subview setEnabled:YES];
    }
}

этот метод работал в 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].)


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

Это мое решение, которое повторно включает кнопку "Отмена" каждый раз, когда она отключена с помощью 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;
    }
}

начиная с iOS 6 кнопка кажется UINavigationButton (private class) вместо UIButton.

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

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

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

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


Это, казалось, работало для меня (в viewDidLoad):

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

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


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

UIButton *btnCancel = [self.searchBar valueForKey:@"_cancelButton"];
[btnCancel setEnabled:YES];

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

UISearchBar+alwaysEnableCancelButton.h

#import <UIKit/UIKit.h>

@interface UISearchBar (alwaysEnableCancelButton)

@end

UISearchBar+alwaysEnableCancelButton.м

#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

а вот немного более надежное решение, которое работает на 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];

вуаля! Кнопка "Отмена" остается включенной.


for (UIView *firstView in searchBar.subviews) {
    for(UIView* view in firstView.subviews) {
        if([view isKindOfClass:[UIButton class]]) {
             UIButton* button = (UIButton*) view;
             [button setEnabled:YES];
        }
    }
}

я нашел другой подход для работы в iOS 7.

то, что я пытаюсь, это что-то вроде приложения Twitter iOS. Если вы нажмете на увеличительное стекло на вкладке Timelines,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).

enter image description here

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

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


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

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

Я попытался реализовать решение, которое всегда поддерживает кнопку "Отмена", даже при выполнении более сложных вещей с помощью панели поиска. Это реализовано как пользовательский подкласс UISearchView в Swift 4. Он использует значение(forKey:) трюк, чтобы найти кнопку отмены и текстовое поле поиска, и слушает, когда поле поиска заканчивается редактирование и повторное включение кнопки Отмена. Он также включает кнопку отмены при переключении флага showsCancelButton.

Он содержит несколько утверждений, чтобы предупредить вас, если внутренние детали 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
    }
}

дом на smileyborg это, просто поместите это в делегат панели поиска:

- (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 и выше.


для 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
        }
    }
}

вы можете создать свой 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);
    }
}

лучшим решением является

[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;

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

[(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES];

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

searchBar.subviews.flatMap({.subviews}).forEach({ ( as? UIButton)?.isEnabled = true })