51

如果您在 iPhone 上的联系人应用程序中输入搜索词,然后点击“搜索”按钮,键盘将被隐藏,但取消按钮仍处于启用状态。在我的应用程序中,当我调用 resignFirstResponder 时,取消按钮被禁用。

任何人都知道如何在保持取消按钮处于启用状态的同时隐藏键盘?

我使用以下代码:

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

键盘滑出视野,但搜索文本字段右侧的“取消”按钮被禁用,因此我无法取消搜索。联系人应用程序将取消按钮保持在启用状态。

我认为也许一种解决方案是深入到 searchBar 对象并在实际文本字段上调用 ​​resignFirstResponder,而不是搜索栏本身。

任何输入表示赞赏。

4

22 回答 22

29

此方法适用于 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] 之后的任何地方调用它。)

于 2013-08-09T15:53:47.103 回答
22

尝试这个

for(id subview in [yourSearchBar subviews])
{
    if ([subview isKindOfClass:[UIButton class]]) {
        [subview setEnabled:YES];
    }
}
于 2012-04-01T22:33:56.330 回答
10

当您开始滚动表格而不是点击“搜索”按钮时,接受的解决方案将不起作用。在这种情况下,“取消”按钮将被禁用。

这是我的解决方案,每次使用 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;
    }
}
于 2012-09-13T12:21:14.187 回答
9

从 iOS 6 开始,按钮似乎是 UINavigationButton(私有类)而不是 UIButton。

我已经调整了上面的例子,看起来像这样。

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

然而,这显然很脆弱,因为我们正在处理内部结构。它还可以启用比按钮更多的功能,但它对我有用,直到找到更好的解决方案。

我们应该要求苹果公司揭露这一点。

于 2012-10-26T00:03:37.237 回答
8

这似乎对我有用(在 viewDidLoad 中):

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

我意识到我可能应该正确使用 UISearchDisplayController,但这对于我当前的实现来说是一个简单的修复。

于 2013-10-01T21:14:33.087 回答
7

您可以使用运行时 API 访问取消按钮。

UIButton *btnCancel = [self.searchBar valueForKey:@"_cancelButton"];
[btnCancel setEnabled:YES];
于 2015-07-13T10:45:40.103 回答
5

我通过在 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
于 2013-01-25T19:29:24.937 回答
4

这是一个更强大的解决方案,适用于 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];

瞧!取消按钮保持启用状态。

于 2013-09-26T23:29:31.460 回答
3

直到 iOS 12,你可以这样使用:-

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

从 iOS 13 开始,如果您使用 like (forKey: "_cancelButton"),那么不幸的是,这种私有 API 的使用会被捕获并导致崩溃。

适用于 iOS 13+ 和 Swift 5+

if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
    cancelButton.isEnabled = true
}
于 2020-03-03T08:59:26.167 回答
2

我找到了一种不同的方法让它在 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的元素,我注意到最后一个元素,即第三个元素,是 type UINavigationButton,而不是在公共 API 中。所以我开始改用键值编码。我检查了是否UINavigationButton响应setEnabled:,幸运的是,它响应。所以我将属性设置为@YES. 原来那UINavigationButton 取消按钮。

如果苹果决定改变 a 的实现,这势必会被打破UISearchBar,但到底是什么。它现在有效。

于 2014-02-25T17:26:33.993 回答
2

David Douglas 答案的 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
            }
        }
    }
}
于 2015-11-05T06:11:15.700 回答
2

大多数已发布的解决方案都不可靠,并且会在各种情况下禁用“取消”按钮。

我试图实现一个始终启用取消按钮的解决方案,即使在使用搜索栏执行更复杂的事情时也是如此。这是在 Swift 4 中作为自定义 UISearchView 子类实现的。它使用 value(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
    }
}
于 2017-12-03T11:32:14.517 回答
1

基于笑脸的回答,只需将其放在您的 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 及更高版本。

于 2014-09-05T07:00:16.100 回答
1

对于 iOS 9/10(已测试)、Swift 3(更短):

searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true })
于 2017-07-05T10:43:20.733 回答
1

对于 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
        }
    }
}
于 2017-01-09T07:47:33.137 回答
1

对于 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()
  }
于 2020-01-26T16:09:27.617 回答
0
for (UIView *firstView in searchBar.subviews) {
    for(UIView* view in firstView.subviews) {
        if([view isKindOfClass:[UIButton class]]) {
             UIButton* button = (UIButton*) view;
             [button setEnabled:YES];
        }
    }
}
于 2014-03-04T07:14:16.523 回答
0

You can create your CustomSearchBar inheriting from UISearchBar and implement this method:

- (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);
    }
}
于 2015-03-03T20:55:46.830 回答
0

一种对 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从委托中调用,则可以覆盖该函数以在显示按钮时也启用该按钮。)

于 2021-04-09T09:49:14.983 回答
0

更好更简单的方法:

[(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES];
于 2017-06-22T05:29:18.147 回答
0

斯威夫特 5 和 iOS 14

if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
    cancelButton.isEnabled = true
}
于 2021-03-13T05:13:21.890 回答
0

更好的解决方案是

[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;
于 2016-10-20T10:34:06.617 回答