无法将searchBar设置为firstResponder

我有一个searchBar我设置在一个tableviewcontroller。 我已经引用了这个类似的问题UISearchBar不能成为UITableView重新出现后的第一响应者,但仍然无法将其设置为第一响应者。 在.h文件中:

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

鉴于didload:

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

当我继续观看searchBaranimation,但没有出现键盘。

我也注意到这个问题。 什么似乎发生的是调用成为becomeFirstResponder是当searchController仍然“加载”完成。 如果您发表评论becomeFirstResponder您注意到没有区别。 所以我们需要在searchController加载完成后调用becomeFirstResponder。

当我看到各种委托方法,我注意到有一个委托方法:

 - (void)didPresentSearchController:(UISearchController *)searchController 

这个方法在searchController被提交之后被调用。 然后,我打电话成为becomeFirstResponder

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

这解决了这个问题。 你会注意到,当searchController加载时,search栏现在有焦点。

解决方法 – (void)didPresentSearchController:(UISearchController *)searchController不起作用,因为只有在用户点击search栏时调用此委托方法…

但是,这个解决scheme确实有效:

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

Swift 3

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

函数searchController.searchBar.becomeFirstResponder()必须在主线程中调用,在viewDidLoad方法中searchController.active = true之后searchController.active = true 。 这里是完整的解决scheme。 它适用于iOS 9.3

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

非常类似于其他答案,但我不得不访问 ViewDidAppear 的主队列SearchBarController不能被执行,直到View出现,然后只能在UI的主队列中这样做:

 searchController.active = true // doubtful whether this is needed dispatch_async(dispatch_get_main_queue(), { self.searchController.searchBar.becomeFirstResponder() }); 

那么,我发现这个解决scheme实际上对我来说是完美的。

不要调用[self.searchController setActive:YES]; 在调用[self.searchController.searchBar becomeFirstResponder];之前[self.searchController.searchBar becomeFirstResponder];

有什么更好的,不要调用[self.searchController setActive:YES]; 在所有。

只调用[self.searchController.searchBar becomeFirstResponder]; 而键盘只是popup来,没有任何延迟。

这似乎有点像一个错误,很多人都在确认它。 例如,在这里检查: 当通过becomeFirstResponder将焦点分配给UISearchController的UISearchBar时,键盘不会出现

我认为可能有一个更清洁的解决scheme。 我发现键盘有点“闪烁”,然后在呈现时becomeFirstResponderdidPresentSearchController:调用didPresentSearchController:正在工作,但键盘晚点了,animation有点古怪。

包装我的重新加载数据的方法与演示检查使每个人都高兴:

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

我通过在resignFirstResponder设置一个断点来find这个。 resignFirstResponderreloadData调用,然后被updateSearchResultsForSearchController:调用。 事实certificate, updateSearchResultsForSearchController:在search控制器的演示过程中被调用。 如果您在此期间使用UISearchBar所在的视图层次结构,则search控制器将会获得borked。 我的猜测是reloadData调用导致UICollectionReusableView标题视图出来,并返回到视图层次结构,迫使UISearchBar子视图退出第一响应者。

我看到的另一个症状是search字词没有重置到取消search栏的中间,导致它不能在将来的点击中正确显示。

我与此战斗了一段时间,但得到它的工作:

  • viewDidLoad()初始化searchController
  • viewDidAppear()设置active = true
    • 这会触发UISearchControllerDelegate扩展中的didPresentSearchController()
  • didPresentSearchController()设置didPresentSearchController()

以下是完整的示例,它使用Google地图自动完成。

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

Swift3变种:

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

有用 ;-)

混合@edwardmp和@mislovr的解决scheme为我工作的答案(键盘popup一个轻微的延迟):

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

对我来说,使用viewDidAppear会有很大的滞后。 在viewDidLoad (iOS 10,Swift 3testing)中asynchronous使用becomeFirstResponder会更好:

 override func viewDidLoad() { super.viewDidLoad() DispatchQueue.main.async { searchController.searchBar.becomeFirstResponder() } } 

替代方法

这个答案着眼于与这种情况相关的一些问题,并解释了用于隔离和确定问题的具体原因的debugging逻辑。 一路上,我分享其他可能在其他情况下工作的可能性,并解释为什么在我的情况下工作。


tldr ; 查看self.definesPresentationContext = true,isBeingDismissed,isBeingPresented,canBecomeFirstResponder,并委派SearchController,Searchbar,SearchResultsUpdater及其委托方法的赋值。

self.definesPresentationContext解决scheme的来源 – 请参阅答案 )

需要注意的一点是SearchBar是如何呈现的。 embedded工具栏,导航栏,另一个UIView,作为input或input附件视图。 所有这些,我发现,它们正在呈现或解散search栏的时间和内部animation有一些影响。

我已经尝试了所有的解决scheme,并没有这些工作,直到我重新考虑我是如何使用searchBar。 在我的情况下,我是从一个控制器(A)向控制器(B)推出一个search控制器,而控制器(A)上已经有了一个初始的search控制器。 我编程的方式embedded在我的导航项目的titleView中的每个search控制器时,进行拉刷新。

build议将searchbar.becomeFirstResponder()添加到生命周期中的答案对于我的用例来说没有意义,因为当我想要插入search栏并将其显示在navigation项中时,该视图已完全加载。 该逻辑也似乎令人困惑,因为视图控制器生命周期方法应该已经在主线程上运行。 对演示文稿添加延迟也似乎是对系统使用的显示和animation的内部操作的干扰。

我发现调用我的function来切换插入工作,当我从controllerA推视图控制器,但键盘将无法正确显示时,从controllerB推。 这两种情况的不同之处在于,controllerA是一个静态tableview控制器,controllerB有一个tableview控制器,我可以通过添加自己的search控制器来进行search。

例如,带search栏的controllerA通过search栏search到controllerB

使用一些断点并检查searchController和search栏的状态,我可以确定searchController.canBecomeFirstResponder返回false 。 我还发现,我需要将SearchResultsUpdater设置为self和searchController和searchBar上的委托。

我终于注意到,在controllerA上设置self.definesPresentationContext = true ,当我将controllerB推入导航堆栈时,不允许显示键盘。 我的解决scheme是将self.definesPresentationContext = true移动到viewDidAppear ,并在controllerA的prepare(for:sender:)方法中,当目标是controllerB时,将其更改为self.definesPresentationContext = false 。 这解决了我的情况下键盘显示问题。

关于animation的一个词 〜我发现,当将事物分配给navigationItem或navigationBar时,系统具有一些内置的时间和默认animation。 我避免添加自定义animation,moveToParent方法中的代码或延迟的演示文稿,因为在许多情况下会发生意外的行为。

这个解决scheme为什

有关definesPresentationContext Apple 文档指出了默认行为,并logging了一些情况,在这些情况下,此上下文调整控制器分配的pipe理键盘外观的行为。 在我的情况下,controllerA被认为是pipe理演示文稿而不是controllerB,所以我只是通过调整这个值来改变这个行为:

使用currentContext或overCurrentContext样式呈现视图控制器时,此属性控制视图控制器层次结构中的哪个现有视图控制器实际上由新内容覆盖。 当发生基于上下文的表示时,UIKit在呈现视图控制器处开始并且走向视图控制器分层结构。 如果它发现一个视图控制器的值为这个属性是真的,它要求该视图控制器呈现新的视图控制器。 如果没有视图控制器定义表示上下文,UIKit会要求窗口的根视图控制器来处理表示。 此属性的默认值为false。 一些系统提供的视图控制器(如UINavigationController)将默认值更改为true。

这就是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() }