如何使用UISearchBar启用取消按钮?

时间:2012-04-01 21:42:33

标签: iphone uisearchbar cancel-button

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

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

我使用以下代码:

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

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

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

任何意见都赞赏。

22 个答案:

答案 0 :(得分: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]之后,请务必在任何地方调用它。)

答案 1 :(得分:21)

试试这个

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

答案 2 :(得分: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;
    }
}

答案 3 :(得分:9)

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

我已将上面的示例调整为这样。

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

然而,这显然是脆弱的,因为我们正在与内部进行混杂。它还可以启用多个按钮,但它适用于我,直到找到更好的解决方案。

我们应该要求Apple揭露这一点。

答案 4 :(得分:8)

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

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

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

答案 5 :(得分:7)

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

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

答案 6 :(得分: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

答案 7 :(得分:4)

这是一个在iOS 7上运行的更强大的解决方案。它将递归遍历搜索栏的所有子视图,以确保它启用所有UIControl s(包括取消按钮)。

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

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

答案 8 :(得分:3)

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

答案 9 :(得分: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)。

enter image description here

然后,UIView持有名为NSArray的{​​{1}},我注意到最后一个元素是第三个属于subviewCache类型,而不是公开API。所以我开始使用键值编码。我检查UINavigationButton是否响应UINavigationButton,幸运的是,确实如此。所以我将属性设置为setEnabled:。事实证明,@YES 是取消按钮。

如果Apple决定改变UINavigationButton内部的实施,这肯定会破裂,但到底是什么。它现在有效。

答案 10 :(得分: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
            }
        }
    }
}

答案 11 :(得分:2)

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

我试图实现一个始终保持“取消”按钮的解决方案,即使使用搜索栏执行更复杂的操作也是如此。这是作为Swift 4中的自定义UISearchView子类实现的。它使用值(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
    }
}

答案 12 :(得分:2)

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

答案 13 :(得分:1)

smileyborg's answer上构建,只需将其放在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及更高版本。

答案 14 :(得分: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
        }
    }
}

答案 15 :(得分:1)

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

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

答案 16 :(得分: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()
  }

答案 17 :(得分:0)

您可以创建继承自UISearchBar的CustomSearchBar并实现此方法:

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

答案 18 :(得分:0)

更好的解决方案是

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

答案 19 :(得分:0)

更好&amp;简单的方法:

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

答案 20 :(得分:0)

Swift 5 和 iOS 14

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

答案 21 :(得分: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,您可以覆盖该函数以在按钮显示时也启用该按钮。)