IPhoneの連絡先アプリで検索語を入力し、[検索]ボタンをタップすると、キーボードは非表示になりますが、キャンセルボタンは有効のままです。私のアプリでは、resignFirstResponderを呼び出すと、キャンセルボタンが無効になります。
キャンセルボタンを有効な状態に保ちながらキーボードを非表示にする方法を知っている人はいますか?
私は次のコードを使用します:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
}
キーボードはスライドして見えなくなりますが、検索テキストフィールドの右側にある[キャンセル]ボタンは無効になっているため、検索をキャンセルできません。連絡先アプリは、キャンセルボタンを有効な状態に維持します。
多分1つの解決策は、searchBarオブジェクトに飛び込み、検索バー自体ではなく、実際のテキストフィールドでresignFirstResponderを呼び出すことだと思います。
どんな入力も歓迎します。
これを試して
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の時点で、ボタンはUIButtonではなくUINavigationButton(プライベートクラス)のように見えます。
上記の例をこのように調整しました。
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の単純なカテゴリとして実装することで、ここで他の人が既に投稿したものを拡張しました。
ISearchBar + alwaysEnableCancelButton.h
#import <UIKit/UIKit.h>
@interface UISearchBar (alwaysEnableCancelButton)
@end
ISearchBar + 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];
出来上がり! [キャンセル]ボタンは有効なままです。
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アプリのようなものです。 [タイムライン]タブで虫眼鏡をクリックすると、[キャンセル]ボタンがアクティブになり、キーボードが表示され、最近の検索画面とともに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
を調べて、そのサブビューをむき出しにしました。常に1つだけで、タイプはUIView
(変数searchBarSubview
)です。
次に、そのUIView
はNSArray
と呼ばれるsubviewCache
を保持し、最後の要素(3番目)はUINavigationButton
型であり、パブリックAPI。そこで、代わりにキーと値のコーディングを使用することにしました。 UINavigationButton
がsetEnabled:
に応答するかどうかを確認しましたが、幸いなことに応答します。そこで、プロパティを@YES
に設定します。 UINavigationButton
is [キャンセル]ボタンであることがわかります。
これは、AppleがUISearchBar
の内部の実装を変更することを決定したが、一体何が起こった場合に壊れます。今のところ動作します。
投稿されたソリューションのほとんどは堅牢ではなく、さまざまな状況で[キャンセル]ボタンが無効になります。
検索バーでより複雑な操作を行う場合でも、[キャンセル]ボタンを常に有効にしておくソリューションを実装しようとしました。これは、カスタムUISearchViewサブクラスとしてSwift 4。キャンセルボタンを編集して再度有効にし、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
}
}
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
}
}
}
}
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 })
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以降でうまく機能します。
より良い簡単な方法:
[(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES];
より良い解決策は
[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = 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);
}
}