簡體   English   中英

如何使用 UISearchBar 啟用取消按鈕?

[英]How to enable cancel button with UISearchBar?

在 iPhone 上的聯系人應用程序中,如果您輸入搜索詞,然后點擊“搜索”按鈕,鍵盤會隱藏,但取消按鈕仍處於啟用狀態。 在我的應用程序中,當我調用 resignFirstResponder 時,取消按鈕被禁用。

任何人都知道如何在將取消按鈕保持在啟用狀態的同時隱藏鍵盤?

我使用以下代碼:

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

鍵盤滑出視圖,但搜索文本字段右側的“取消”按鈕被禁用,因此我無法取消搜索。 聯系人應用程序將取消按鈕保持在啟用狀態。

我認為也許一種解決方案是深入 searchBar 對象並在實際文本字段上調用 ​​resignFirstResponder,而不是搜索欄本身。

任何輸入表示贊賞。

此方法適用於 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] 之后的任何地方調用它。)

嘗試這個

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

當您開始滾動表格而不是點擊“搜索”按鈕時,已接受的解決方案將不起作用。 在這種情況下,“取消”按鈕將被禁用。

這是我的解決方案,每次使用 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(私有類)而不是 UIButton。

我已經調整了上面的例子,看起來像這樣。

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

然而,這顯然很脆弱,因為我們正在處理內部結構。 它還可以啟用比按鈕更多的功能,但它對我有用,直到找到更好的解決方案。

我們應該要求蘋果公司揭露這一點。

這似乎對我有用(在 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.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

這是一個適用於 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];

瞧! 取消按鈕保持啟用狀態。

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

我發現了一種使其在 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取消按鈕。

如果 Apple 決定更改UISearchBar內部結構的實現,這肯定會中斷,但到底是怎么回事。 它現在有效。

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

大多數已發布的解決方案並不健壯,並且會在各種情況下禁用“取消”按鈕。

我試圖實現一個始終保持“取消”按鈕處於啟用狀態的解決方案,即使在使用搜索欄執行更復雜的操作時也是如此。 這是在 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
    }
}

基於Smileyborg 的回答,只需將其放在您的 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 及更高版本。

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

對於 iOS 9/10(已測試)、Swift 3(較短):

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

對於 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()
  }
for (UIView *firstView in searchBar.subviews) {
    for(UIView* view in firstView.subviews) {
        if([view isKindOfClass:[UIButton class]]) {
             UIButton* button = (UIButton*) view;
             [button setEnabled:YES];
        }
    }
}

您可以創建繼承自 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);
    }
}

更好的解決方案是

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

更好和簡單的方法:

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

斯威夫特 5 和 iOS 14

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

一種應對 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 ,您可以覆蓋該函數以在按鈕顯示時也啟用該按鈕。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM