UISearchBar禁止自动禁用取消按钮

37
我已经在表格视图中实现了一个UISearchBar,几乎所有的功能都可以使用,但是有一个小问题:当我输入文本并按下键盘上的搜索按钮时,键盘消失,搜索结果是表格中唯一显示的项目,文本保留在UISearchBar中,但取消按钮被禁用。
我一直在尝试让我的列表尽可能接近苹果联系人应用程序的功能,并且在该应用程序中按下搜索时,不会禁用取消按钮。
当我查看UISearchBar头文件时,我注意到_searchBarFlags结构中autoDisableCancelButton标志是私有的。
在设置UISearchBar时,我是否遗漏了什么东西?
11个回答

51
我找到了一个解决方法。你可以使用这个for循环来遍历搜索栏的子视图,并在键盘上按下搜索按钮时启用它。
for (UIView *possibleButton in searchBar.subviews)
{
    if ([possibleButton isKindOfClass:[UIButton class]])
    {
        UIButton *cancelButton = (UIButton*)possibleButton;
        cancelButton.enabled = YES;
        break;
    }
}

2
这对我在iOS 6上没有起作用,因为唯一存在的按钮是UINavigationButton,它似乎是一个私有类。 - Ben L.
1
你尝试过在 UISearchBar 上设置 showsCancelButton 属性吗? - rplankenhorn
2
UINavigationButton 是 UIButton 的子类,所以这段代码是可以正常运行的,并且没有使用私有类。请参考 https://github.com/kennytm/iphone-private-frameworks/blob/master/UIKit/UINavigationButton.h - Dmitry Salnikov
只需要在正确的时机使用它,即在搜索栏失去焦点(resignFirstResponder)之后。 - paiv
对我来说没问题!我使用((UIButton*)possibleButton).enabled = YES;,因为我觉得使用cancelButton太过繁琐。 - meaning-matters

15

我不得不稍微调整一下这个,在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;
            }
        }
    }
}

2
你应该直接从内部if语句中返回,而不是使用break。那个break语句只会将你从内部for循环中退出,并且在找到取消按钮后仍将继续循环searchBar.subviews。 - rplankenhorn

5

有两种简单的方法可以实现这个

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
    //  The small and dirty
    [(UIButton*)[searchBar valueForKey:@"_cancelButton"] setEnabled:YES];

    // The long and safe
     UIButton *cancelButton = [searchBar valueForKey:@"_cancelButton"];
    if ([cancelButton respondsToSelector:@selector(setEnabled:)]) {
         cancelButton.enabled = YES;
    }
}

你应该选择第二个,如果苹果在后台更改它,它不会使你的应用程序崩溃。

顺便说一下,我从iOS 4.0到8.2测试了它,没有任何变化,而且我在我的商店批准的应用程序中使用它也没有任何问题。


1
这个函数可行,但你的应用程序可能会因使用私有方法而被拒绝。请谨慎。 - Pedro José Cardoso
@PedroJoséCardoso,这并不算私有API的使用,我从未因此看到过任何拒绝。 - Laszlo

3

这是让它在我的iOS 6上起作用的方法:

searchBar.showsCancelButton = YES;
searchBar.showsScopeBar = YES;
[searchBar sizeToFit];
[searchBar setShowsCancelButton:YES animated:YES];

2
这并不能解决问题:用户想要禁用自动禁用功能,即不允许UISearchBar进行禁用。 - dwery

3

根据我在这里的答案,请将以下内容放入您的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);
    });
}

3

以下是我的解决方案,适用于所有版本的iOS中的所有情况。

也就是说,其他解决方案无法处理当用户拖动滚动视图导致键盘被取消时的情况。

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

// This will handle whenever the text field is resigned non-programatically
// (IE, because it's set to resign when the scroll view is dragged in your storyboard.)
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
    [self performSelector:@selector(enableCancelButton:) withObject:searchBar afterDelay:0.001];
}

// Also follow up every [searchBar resignFirstResponder];
// with [self enableCancelButton:searchBar];

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包含一个名为subviewCacheNSArray,我注意到最后一个元素是第三个,类型为UINavigationButton,不在公共API中。因此,我决定改用键值编码。我检查了UINavigationButton是否响应setEnabled:,幸运的是,它确实响应。所以我将属性设置为@YES。结果证明,那个UINavigationButton就是取消按钮。
如果苹果决定更改UISearchBar内部的实现,这个方法可能会失效,但是现在它可以工作。

2

以下是一个使用扩展(extension)轻松获取取消按钮的Swift 3解决方案:

extension UISearchBar {
    var cancelButton: UIButton? {
        for subView1 in subviews {
            for subView2 in subView1.subviews {
                if let cancelButton = subView2 as? UIButton {
                    return cancelButton
                }
            }
        }
        return nil
    }
}

现在来看使用方法:
class MyTableViewController : UITableViewController, UISearchBarDelegate {

    var searchBar = UISearchBar()

    func viewDidLoad() {
        super.viewDidLoad()
        searchBar.delegate = self
        tableView.tableHeaderView = searchBar
    }

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        DispatchQueue.main.async {
            if let cancelButton = searchBar.cancelButton {
                cancelButton.isEnabled = true
                cancelButton.isUserInteractionEnabled = true
            }
        }
    }
}

1
我见过的最干净的解决方案。谢谢。 - gbhall
无法在iOS 13+上工作,因为subView2不是一个UIButton - Eric

1

对于Monotouch或Xamarin iOS,我有以下C#解决方案适用于iOS 7和iOS 8:

foreach(UIView view in searchBar.Subviews)
{
    foreach(var subview in view.Subviews)
    {
        //Console.WriteLine(subview.GetType());
        if(subview.GetType() == typeof(UIButton))
        {
            if(subview.RespondsToSelector(new Selector("setEnabled:")))
            {
                UIButton cancelButton = (UIButton)subview;
                cancelButton.Enabled = true;
                Console.WriteLine("enabledCancelButton");
                return;
            }
        }
    }
}

这个答案基于 David Douglas 的解决方案。

1

更完整的答案:

  • iOS 7以来,在searchBar下面有一个额外的子视图层级
  • 启用取消按钮的好地方是在searchBarTextDidEndEditing

.

extension MyController: UISearchBarDelegate {
  public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
    DispatchQueue.main.async {
    // you need that since the disabling will
    // happen after searchBarTextDidEndEditing is called
      searchBar.subviews.forEach({ view in
        view.subviews.forEach({ subview in
          // ios 7+
          if let cancelButton = subview as? UIButton {
            cancelButton.isEnabled = true
            cancelButton.isUserInteractionEnabled = true
            return
          }
        })
        // ios 7-
        if let cancelButton = subview as? UIButton {
          cancelButton.isEnabled = true
          cancelButton.isUserInteractionEnabled = true
          return
        }
      })
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接