web-dev-qa-db-ja.com

searchBarをfirstResponderとして設定できません

Tableviewcontrollerに設定しているsearchBarがあります。私はこの同様の質問を参照しました ITableViewが再表示された後、UISearchBarがファーストレスポンダーになることはできません しかし、それをファーストレスポンダーとして設定することはまだできません。 .hファイル内:

@property (strong, nonatomic) UISearchController *searchController;
@property (strong, nonatomic) IBOutlet UISearchBar *searchBar;

ビューのロード:

self.searchController = [[UISearchController alloc]initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;

self.searchController.searchBar.frame = CGRectMake(self.searchController.searchBar.frame.Origin.x, self.searchController.searchBar.frame.Origin.y, self.searchController.searchBar.frame.size.width, 44.0);
self.tableView.tableHeaderView = self.searchController.searchBar;

そしてviewDidAppearで:

-(void)viewDidAppear:(BOOL)animated {
  [self.searchController setActive:YES];
  [self.searchController.searchBar becomeFirstResponder];
  [super viewDidAppear:animated];
}

ビューに移動すると、searchBarはアニメーション化しますが、キーボードは表示されません。

43
user3411663

私もこの問題に気づきました。起こりそうなのは、searchControllerがまだ「ロード中」のときにbecomeFirstResponderの呼び出しが行われることです。 becomeFirstResponderをコメントアウトすると、違いはありません。したがって、searchControllerが「完了」した後に、becomeFirstResponderを呼び出す方法が必要です。

さまざまなデリゲートメソッドを見ると、デリゲートメソッドがあることに気付きました。

- (void)didPresentSearchController:(UISearchController *)searchController

このメソッドは、searchControllerが提示された直後に呼び出されます。次に、becomeFirstResponderを呼び出します。

- (void)didPresentSearchController:(UISearchController *)searchController
{
    [searchController.searchBar becomeFirstResponder];
}

これにより問題が修正されます。 searchControllerがロードされると、検索バーにフォーカスが移ります。

55
edwardmp

-(void)didPresentSearchController:(UISearchController *)searchControllerを使用したソリューションは機能しませんでした。このデリゲートメソッドは、ユーザーが検索バーをタップしたときにのみ呼び出されるためです...

ただし、このソリューションは機能しました。

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self performSelector:@selector(showKeyboard) withObject:nil afterDelay:0.1];
}

- (void) showKeyboard
{
    [self.searchController.searchBar becomeFirstResponder];
}

Swift

delay(0.1) { self.searchController.searchBar.becomeFirstResponder() }

func delay(_ delay: Double, closure: @escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
42
mislovr

まあ、私は実際に私のために完全に機能しているソリューションを見つけました。

[self.searchController setActive:YES];を呼び出す前に[self.searchController.searchBar becomeFirstResponder];を呼び出さないでください

さらに、[self.searchController setActive:YES];を呼び出さないでください。

[self.searchController.searchBar becomeFirstResponder];のみを呼び出すと、キーボードが遅延なくすぐに飛び出します。

バグのようなもので、多くの人が確認しています。たとえば、ここでチェックしてください: becomeFirstResponderを介してUISearchControllerのUISearchBarにフォーカスを割り当てると、キーボードは表示されません

17
Roland T.

関数searchController.searchBar.becomeFirstResponder()は、メインスレッドで、viewDidLoadメソッドのsearchController.active = trueの後に呼び出す必要があります。完全なソリューションは次のとおりです。 iOS 9.3で動作します

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  searchController.active = true
  Async.main {
    self.searchController.searchBar.becomeFirstResponder()
  }
}
10
Tan Vu

Swift 4、iOS 11

わたしにはできる

// 1.
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    resultSearchController.isActive = true
}

// 2. ->> UISearchControllerDelegate
func didPresentSearchController(_ searchController: UISearchController) {

    DispatchQueue.main.async {
        searchController.searchBar.becomeFirstResponder()
    }
}
8

他の回答と非常に似ていますが、ViewDidAppearメインキューにアクセスする必要がありました。 SearchBarControllerは、Viewが現れるまで処理できず、UIのメインキューでのみ実行できます。

searchController.active = true  // doubtful whether this is needed

        dispatch_async(dispatch_get_main_queue(), {
            self.searchController.searchBar.becomeFirstResponder()
        });
8
Mason Black

簡単Swiftバリアント:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.titleView = mySearchController.searchBar
    mySearchController.searchResultsUpdater = self
    mySearchController.delegate = self

}

override func viewDidAppear(_ animated: Bool) {
    DispatchQueue.main.async {
        self.mySearchController.isActive = true
    }
}

func presentSearchController(_ searchController: UISearchController) {
    mySearchController.searchBar.becomeFirstResponder()
}

できます ;-)

6
Vitya Shurapov

答えは、viewDidAppearでbecomeFirstResponderを呼び出すことです。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    searchBar?.becomeFirstResponder()
}
3
bigDeeOT

代替アプローチ

この回答では、この発生に関連するいくつかの問題を調べ、私の場合の問題の特定の原因を特定して特定するために使用されるデバッグロジックについて説明します。途中で、私は他の状況で機能したかもしれない他の可能性を共有し、これが私の状況で機能する理由を説明します。


tldr; self.definesPresentationContext = true、isBeingDismissed、isBeingPresented、canBecomeFirstResponder、およびSearchController、Searchbar、SearchResultsUpdaterおよびそれらのデリゲートメソッドのデリゲート割り当てを見てください。

Sourceof _self.definesPresentationContext_ solution- SO answer を参照)

心に留めておくべきことの1つは、SearchBarの表示方法のコンテキストです。入力または入力アクセサリビューとして、ツールバー、ナビゲーションバー、別のUIViewに埋め込まれます。そのすべてが、検索バーが表示または非表示にされるときに、検索バーのタイミングと内部アニメーションに何らかの影響を与えることがわかりました。

提示されたすべてのソリューションを試しましたが、searchBarの使用方法を再考するまで、これらのいずれも機能しませんでした。私の場合、私はコントローラー(B)からコントローラー(A)既に初期のsearchcontrollerがその上にありました。プルリフレッシュを実行するときに、ナビゲーションアイテムのtitleView内に各検索コントローラーをプログラムで埋め込みました。

ライフサイクルにsearchbar.becomeFirstResponder()を追加することを示唆する回答は、ナビゲーションバーに検索バーを挿入して表示したい時点でビューが完全に読み込まれたため、ユースケースには意味がありませんでした。 View Controllerのライフサイクルメソッドはすでにメインスレッドで動作しているはずなので、ロジックも混乱しているように見えました。プレゼンテーションに遅延を追加することは、システムで使用されるディスプレイとアニメーションの内部操作の干渉とも思われます。

ControllerAからView Controllerを押したときに挿入を切り替える関数を呼び出すことはできましたが、controllerBから押したときにキーボードが正しく表示されないことがわかりました。これら2つの状況の違いは、controllerAが静的Tableview Controllerであり、controllerBには独自の検索コントローラーを追加して検索可能にしたTableview Controllerがあったことです。

例えばcontrollerAサーチバーを使用するとcontrollerBをサーチバーで使用します

いくつかのブレークポイントを使用して、さまざまなポイントでsearchControllerと検索バーのステータスを調べると、_searchController.canBecomeFirstResponder_がfalseを返していることがわかりました。また、SearchResultsUpdaterselfに設定し、searchControllerとsearchBarの両方でデリゲートを設定する必要があることもわかりました。

最後に、controllerAに_self.definesPresentationContext = true_を設定しても、controllerBをナビゲーションスタックにプッシュしたときにキーボードを表示できないことに注意しました。私の解決策は、controllerAの_self.definesPresentationContext = true_をviewDidAppearに移動し、controllerAのprepare(for:sender:)メソッドで、宛先がcontrollerBのときに_self.definesPresentationContext = false_に変更することでした。これにより、私の場合のキーボード表示の問題が解決しました。

アニメーションに関する単語〜navigationItemまたはnavigationBarに物事を割り当てると、システムにはタイミングとデフォルトのアニメーションが組み込まれていることがわかりました。多くの場合、予期しない動作が発生するため、カスタムアニメーション、moveToParentメソッドのコード、遅延プレゼンテーションの追加は避けます。

このソリューションが機能する理由

Apple documentation on definesPresentationContextはデフォルトの動作を示し、このコンテキストがキーボードの外観を管理するためにコントローラーが割り当てた動作を調整する状況を示します。私の場合、controllerAはcontrollerBではなくプレゼンテーションを管理するように指定されていたので、この値を調整してその動作を変更しました。

CurrentContextスタイルまたはoverCurrentContextスタイルを使用してView Controllerを表示する場合、このプロパティは、View Controller階層内のどの既存のView Controllerが実際に新しいコンテンツでカバーされるかを制御します。コンテキストベースのプレゼンテーションが発生すると、UIKitは表示するView Controllerから開始し、View Controller階層をたどります。このプロパティの値がtrueであるView Controllerを見つけると、そのView Controllerに新しいView Controllerを表示するように要求します。プレゼンテーションコンテキストを定義しているView Controllerがない場合、UIKitはウィンドウのルートView Controllerにプレゼンテーションの処理を要求します。このプロパティのデフォルト値はfalseです。 UINavigationControllerなどの一部のシステム提供のView Controllerは、デフォルト値をtrueに変更します。

3
Tommie C.

これは、Swift 4

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    searchController.isActive = true
    DispatchQueue.main.async{
        self.searchController.searchBar.becomeFirstResponder()
    }
}
2
Lisa Liu

@mislovrのソリューションに基づいて、0.1遅延が長すぎました。これがその答えに対する私の更新されたコードです。

func presentSearchController() {
    searchController.isActive = true

    Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak searchController] timer in
        guard let searchController = searchController else {
            timer.invalidate()
            return
        }

        if searchController.searchBar.canBecomeFirstResponder {
            searchController.searchBar.becomeFirstResponder()
            timer.invalidate()
        }
    }
}
2
cnotethegr8

もっときれいな解決策があると思います。キーボードが表示されたときに「ブリッピング」してからダウンするようなものであり、didPresentSearchController:becomeFirstResponderを呼び出すことは機能していましたが、キーボードが遅くなり、アニメーションが少し風変わりでした。

データのリロードメソッドをプレゼンテーションのチェックでラップすると、誰もが幸せになりました。

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
    if (!searchController.isBeingPresented && !searchController.isBeingDismissed) {
        [self.collectionView reloadData];
    }
}

resignFirstResponderにブレークポイントを設定することでこれを見つけました。 resignFirstResponderreloadDataから呼び出されていましたが、これはupdateSearchResultsForSearchController:によって呼び出されました。検索コントローラーの表示中にupdateSearchResultsForSearchController:が呼び出されることがわかりました。この間にUISearchBarが含まれているビュー階層をいじると、検索コントローラーが中断されます。私の推測では、reloadData呼び出しによりUICollectionReusableViewヘッダービューが表示され、ビュー階層に戻り、UISearchBarサブビューに最初のレスポンダーを強制的に辞任させたと考えられます。

私が見た別の症状は、キャンセル時に検索用語が検索バーの中央にリセットされなかったため、将来のクリックで適切に表示されないことでした。

2
Brian King

私はこれとしばらくの間戦いましたが、それを機能させました:

  • viewDidLoad()searchControllerの初期化
  • viewDidAppear() で_active = true_を設定する
    • これにより、UISearchControllerDelegate拡張でdidPresentSearchController()がトリガーされます。
  • searchBar.becomeFirstResponder()didPresentSearchController()を設定

完全な例は次のとおりです。GoogleMaps Autocompleteを使用しています。

_class myViewController: UIViewController {

    // MARK: Variables

    var resultsViewController: GMSAutocompleteResultsViewController?
    var searchController: UISearchController?
    var resultView: UITextView?

    // MARK: Outlets

    @IBOutlet var myView: UIView!

    // MARK: View Methods

    override func viewDidLoad() {
        super.viewDidLoad()

        resultsViewController = GMSAutocompleteResultsViewController()
        resultsViewController?.delegate = self

        searchController = UISearchController(searchResultsController: resultsViewController)
        searchController?.delegate = self
        searchController?.searchResultsUpdater = resultsViewController

        searchController?.searchBar.Prompt = "Search for a Place"
        searchController?.searchBar.placeholder = "place name"
        searchController?.searchBar.text = ""
        searchController?.searchBar.sizeToFit()

        searchController?.searchBar.returnKeyType = .Next
        searchController?.searchBar.setShowsCancelButton(true, animated: false)

        myView.addSubview((searchController?.searchBar)!)
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(true)
        searchController?.active = true
    }

    // MARK: GMSAutocompleteResultsViewControllerDelegate Extension

    extension myViewController: GMSAutocompleteResultsViewControllerDelegate {

        func resultsController(resultsController: GMSAutocompleteResultsViewController,
                       didAutocompleteWithPlace place: GMSPlace) {
            searchController?.active = false
            // Do something with the selected place.
            print("Place name: ", place.name)
            print("Place address: ", place.formattedAddress)
            print("Place attributions: ", place.attributions)
        }

        func resultsController(resultsController: GMSAutocompleteResultsViewController,
                       didFailAutocompleteWithError error: NSError){
            // TODO: handle the error.
            print("Error: ", error.description)
        }

        // Turn the network activity indicator on and off again.
        func didRequestAutocompletePredictionsForResultsController(resultsController: GMSAutocompleteResultsViewController) {
            UIApplication.sharedApplication().networkActivityIndicatorVisible = true
        }

        func didUpdateAutocompletePredictionsForResultsController(resultsController: GMSAutocompleteResultsViewController) {
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
        }
    }

    extension myViewController: UISearchControllerDelegate {

        func didPresentSearchController(searchController: UISearchController) {
            self.searchController?.searchBar.becomeFirstResponder()
        }
    }
}
_
2
Derek Soike

@edwardmpと@mislovrを混合するソリューションは、私のために働いていました(キーボードが少し遅れて飛び出します):

- (void)didPresentSearchController:(UISearchController *)searchController {
    [self performSelector:@selector(showKeyboard) withObject:nil afterDelay:0.001];
}

- (void) showKeyboard {
    [self.searchController.searchBar becomeFirstResponder];
}
2
Roland T.

Swift 5解決策:

override func viewDidLoad() {
        super.viewDidLoad()
        definesPresentationContext = true
        searchController.delegate = self
    }

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    searchController.isActive = true
}

extension ViewController: UISearchControllerDelegate {
 func didPresentSearchController(_ searchController: UISearchController) {
      DispatchQueue.main.async {[weak self] in
          self?.searchController.searchBar.becomeFirstResponder()
       }
  }
}
1
UnRewa

Objective Cの場合:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Setup your SearchViewController here...
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    // Will show Cancel button in White colour 
    [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTintColor:[UIColor whiteColor]];

    // searchController.active = YES;   // This is not necessary

    // set SearchBar first responder inside Main Queue block
    dispatch_async(dispatch_get_main_queue(), ^{
        [self->searchController.searchBar becomeFirstResponder];
    });
}
0
Subhasish

これは、Swift 3。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.perform(#selector(self.showKeyboard), with: nil, afterDelay: 0.1)
}

func showKeyboard() {
    self.searchController.searchBar.becomeFirstResponder()
}
0
pableiros

私にとって、viewDidAppearを使用するとかなり大きな遅れがあります。 becomeFirstResponderで非同期にviewDidLoadを使用する方が良い場合があります(iOS 10でテスト済みSwift 3):

override func viewDidLoad() {
    super.viewDidLoad()

    DispatchQueue.main.async {
        searchController.searchBar.becomeFirstResponder()
    }
}
0
dvs