56

我在 tableviewcontroller 中设置了一个 searchBar。我已经引用了这个类似的问题UISearchBar cannot become first responder after UITableView do re-appear但我仍然无法将其设置为第一响应者。在 .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 动画,但没有出现键盘。

4

22 回答 22

60

我也注意到了这个问题。似乎发生的事情是becomeFirstResponder在 searchController 仍在“加载”时完成调用。如果您注释掉,becomeFirstResponder您会发现没有区别。所以我们需要一种在 searchController 加载完成后调用 becomeFirstResponder 的方法。

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

- (void)didPresentSearchController:(UISearchController *)searchController

这个方法在 searchController 出现后立即被调用。然后我打电话给becomeFirstResponder

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

这解决了问题。您会注意到,当 searchController 加载时,搜索栏现在有了焦点。

于 2015-02-15T14:31:06.600 回答
44

带有 - 的解决方案(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];
}

斯威夫特 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)
}
于 2015-07-20T08:03:07.313 回答
19

好吧,我找到了真正适合我的解决方案。

[self.searchController setActive:YES];打电话前不要打电话[self.searchController.searchBar becomeFirstResponder];

更好的是,根本不打电话[self.searchController setActive:YES];

只打电话[self.searchController.searchBar becomeFirstResponder];,键盘就会按原样弹出,没有任何延迟。

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

于 2016-06-08T21:06:05.767 回答
15

斯威夫特 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()
    }
}
于 2018-02-28T15:29:28.677 回答
13

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

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  searchController.active = true
  Async.main {
    self.searchController.searchBar.becomeFirstResponder()
  }
}
于 2016-06-16T02:10:02.970 回答
8

与其他答案非常相似,但我必须访问. ViewDidAppear在出现SearchBarController之前无法执行View,然后只能在主队列中执行以下操作UI

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

        dispatch_async(dispatch_get_main_queue(), {
            self.searchController.searchBar.becomeFirstResponder()
        });
于 2016-08-07T19:57:37.683 回答
7

简单的 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()
}

有用 ;-)

于 2017-02-06T14:11:13.363 回答
6

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()
       }
  }
}
于 2019-06-14T12:35:54.373 回答
5

Xcode 11.4,斯威夫特 5.2

如果您只想SearchBar出现但不激活TextField& 键盘。无需将其分派到主线程。

在此处输入图像描述

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

如果你想TextField用键盘激活,那么你需要在主线程上调用它。无需SearchController激活,这是自动发生的。

在此处输入图像描述

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    DispatchQueue.main.async {
        self.searchController.searchBar.becomeFirstResponder()
    }
}

这可能取决于您如何配置SearchController. 这就是我配置我的方式:

// Defined in class
let searchController = UISearchController(searchResultsController: nil)

// Called in viewDidLoad
navigationItem.searchController = searchController
searchController.searchResultsUpdater = self
searchController.searchBar.scopeButtonTitles = ["A", "B"]
searchController.searchBar.delegate = self          
searchController.obscuresBackgroundDuringPresentation = false
于 2020-05-01T09:19:21.227 回答
4

替代方法

这个答案着眼于与此事件相关的许多问题,并解释了用于隔离和确定问题具体原因的调试逻辑。在此过程中,我分享了在其他情况下可能有效的其他可能性,并解释了为什么这在我的情况下有效。


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

(解决方案的来源self.definesPresentationContext-请参阅 SO 答案

要记住的一件事是如何呈现 SearchBar 的上下文。嵌入在工具栏、导航栏、另一个 UIView 中,作为输入或输入附件视图。我发现所有这些都会对搜索栏的显示或关闭时间和内部动画产生一些影响。

我已经尝试了所有提出的解决方案,但在我重新考虑如何尝试使用 searchBar 之前,这些解决方案都没有奏效。在我的例子中,我从一个已经有一个初始搜索控制器的控制器(A)推送一个带有搜索控制器的控制器(B) 。在进行拉刷新时,我以编程方式将每个搜索控制器嵌入到导航项的 titleView 中。

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

我发现当我从控制器 A 推送视图控制器时调用我的函数来切换插入工作,但从控制器 B 推送时键盘无法正确显示。这两种情况之间的区别在于,controllerA 是静态 tableview 控制器,而 controllerB 有一个 tableview 控制器,我通过添加自己的搜索控制器使其可搜索。

例如,带有搜索栏的控制器A与带有搜索栏的控制器B

使用多个断点并检查不同点的 searchController 和搜索栏的状态,我能够确定searchController.canBecomeFirstResponder正在返回false。我还发现我需要在 searchController 和 searchBar 上设置SearchResultsUpdaterto和 delegates。self

我终于注意到,self.definesPresentationContext = true当我将 controllerB 推到导航堆栈上时,controllerA 上的设置不允许显示键盘。我的解决方案是将控制器移到self.definesPresentationContext = true控制器AviewDidAppear上,并在prepare(for:sender:)控制器A的方法中将其更改为self.definesPresentationContext = false目的地为控制器B时。这解决了我的键盘显示问题。

关于动画的一句话~我发现在为navigationItem或navigationBar分配东西时,系统有一些内置的定时和默认动画。我避免在 moveToParent 方法中添加自定义动画、代码或延迟演示,因为在许多情况下都会发生意外行为。

为什么这个解决方案有效

Apple文档指出了definesPresentationContext默认行为,并指出了一些情况,其中此上下文会调整控制器分配用于管理键盘外观的行为。在我的例子中,控制器 A 被分配来管理演示而不是控制器 B,所以我只是通过调整这个值来改变这种行为:

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

于 2017-05-07T18:13:49.727 回答
3

我将其修复如下:

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

func didPresentSearchController(_ searchController: UISearchController) {

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

发生的事情是,一旦视图出现,您将 searchControllers 的“isActive”属性设置为 true。这会触发名为 didPresentSearchController 的委托方法。当它触发时,这意味着 searchcontroller 是可见的,因此可以成为第一响应者。

于 2020-09-13T04:41:09.093 回答
3

答案是在 viewDidAppear 中调用 becomeFirstResponder。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    searchBar?.becomeFirstResponder()
}
于 2018-03-26T02:18:41.690 回答
3

根据@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()
        }
    }
}
于 2018-07-31T12:19:25.070 回答
3

这就是它在 Swift 4 中对我有用的东西

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    searchController.isActive = true
    DispatchQueue.main.async{
        self.searchController.searchBar.becomeFirstResponder()
    }
}
于 2018-05-17T22:58:07.170 回答
2

我为此奋斗了一段时间,但通过以下方式使其工作:

  • 初始化searchController输入viewDidLoad()
  • 设置active = true_viewDidAppear()
    • didPresentSearchController()这会在UISearchControllerDelegate扩展中触发。
  • 设置searchBar.becomeFirstResponder()_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()
        }
    }
}
于 2016-08-19T00:22:32.793 回答
2

我认为可能有一个更清洁的解决方案。我发现键盘在出现时有点“闪烁”然后又回落,并且呼入becomeFirstResponder工作didPresentSearchController:正常,但是键盘迟到了,动画有点古怪。

用检查演示来包装我的重新加载数据方法让每个人都很高兴:

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

我通过在resignFirstResponder. resignFirstResponder被 调用reloadData,而后者又被 调用updateSearchResultsForSearchController:。事实证明,它updateSearchResultsForSearchController:是在搜索控制器的呈现过程中调用的。如果你在这段时间里弄乱了 UISearchBar 所在的视图层次结构,搜索控制器就会变得无聊。我的猜测是 reloadData 调用导致UICollectionReusableView标题视图出现并返回视图层次结构并迫使 UISearchBar 子视图辞职第一响应者。

我看到的另一个症状是搜索词在取消时没有重置到搜索栏的中间,这导致它在以后的点击中无法正确显示。

于 2015-09-22T14:43:45.590 回答
2

混合@edwardmp 和@mislovr 答案的解决方案对我有用(键盘弹出稍有延迟):

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

- (void) showKeyboard {
    [self.searchController.searchBar becomeFirstResponder];
}
于 2016-05-24T08:27:14.253 回答
1

在目标 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];
    });
}
于 2018-05-02T07:42:18.660 回答
1

斯威夫特 5

class ViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // Only after viewDidAppear searchController can be activated
        searchController?.isActive = true
    }
}

extension ViewController: UISearchControllerDelegate {
    // didPresentSearchController not work for me
    func presentSearchController(_ searchController: UISearchController) {
        searchController.searchBar.becomeFirstResponder()
    }
}
于 2020-01-31T00:34:52.237 回答
0
  1. 定义搜索栏。
  2. SearchBar 文本字段“searchField”的选择。
  3. viewDidLoad 或 viewWillAppear 调用代码。

@IBOutlet 弱变量 searchBar:UISearchBar!

让 textFieldUISearchBar = searchBar.value(forKey: "searchField") as? UITextField

textFieldUISearchBar?.becomeFirstResponder()

于 2021-05-21T08:39:34.510 回答
0

对我来说,使用viewDidAppear. 在(用 iOS 10,Swift 3 测试)中使用becomeFirstResponder异步会更好:viewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()

    DispatchQueue.main.async {
        searchController.searchBar.becomeFirstResponder()
    }
}
于 2017-02-09T02:07:12.503 回答
0

This is what it worked for me in 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()
}
于 2017-05-19T22:12:48.950 回答