无法将searchBar设置为firstResponder

时间:2015-01-14 20:37:16

标签: ios objective-c uisearchbar first-responder uisearchcontroller

我在tableviewcontroller中设置了searchBar。我引用了类似的问题UISearchBar cannot become first responder after UITableView did 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会动画,但不会出现键盘。

21 个答案:

答案 0 :(得分:49)

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

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

- (void)didPresentSearchController:(UISearchController *)searchController

在呈现searchController之后立即调用此方法。然后我打电话给becomeFirstResponder

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

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

答案 1 :(得分:41)

解决方案 - (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 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)
}

答案 2 :(得分:16)

好吧,我发现这个解决方案实际上对我很有用。

在致电[self.searchController setActive:YES];

之前,请勿致电[self.searchController.searchBar becomeFirstResponder];

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

仅拨打[self.searchController.searchBar becomeFirstResponder];,键盘就会弹出,不会有任何延迟。

它似乎有点像一个错误,很多人都在确认它。例如,请点击此处:When assigning focus via becomeFirstResponder to UISearchController's UISearchBar, the keyboard does not appear

答案 3 :(得分:9)

必须在主线程中调用函数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()
  }
}

答案 4 :(得分:8)

与其他答案非常相似,但我必须访问ViewDidAppear中的主要队列。 在SearchBarController出现之前无法对View采取行动,然后只能在UI的主要队列中执行此操作:

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

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

答案 5 :(得分:6)

Swift 4,iOS 11

它对我有用

staging

答案 6 :(得分:5)

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

有效; - )

答案 7 :(得分:3)

替代方法

  

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

  

<强> tldr ;看看self.definesPresentationContext = true ,.   isBeingDismissed,isBeingPresented,canBecomeFirstResponder,and   委托SearchController,Searchbar,   SearchResultsUpdater及其委托方法。

self.definesPresentationContext解决方案的来源 - See SO answer

要记住的一件事是SearchBar如何呈现的背景。嵌入在工具栏,导航栏,另一个UIView中,作为输入或输入附件视图。所有这些,我发现在搜索栏正在呈现或被驳回时,它会对搜索栏的时间和内部动画产生一些影响。

我尝试了所有提出的解决方案,直到我重新考虑如何使用searchBar才能解决这些问题。就我而言,我正在使用来自控制器(A)的搜索控制器推动控制器(B),该控制器上已经有一个初始搜索控制器。我在执行拉动刷新时以编程方式将每个搜索控制器嵌入到导航项的titleView中。

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

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

  

e.g。带有搜索栏的 controllerA 与搜索栏一起显示 controllerB

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

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

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

为什么这个解决方案有效

<{1>} definesPresentationContext上的Apple documentation表示默认行为,并指出此上下文调整控制器分配用于管理键盘外观的行为的一些情况。在我的情况下,controllerA被命名为管理演示而不是controllerB,所以我只是通过调整这个值来改变这种行为:

  

使用currentContext或overCurrentContext样式呈现时   视图控制器,此属性控制哪个现有视图控制器   在您的视图中,控制器层次结构实际上是由新的   内容。当基于上下文的演示发生时,UIKit开始于   呈现视图控制器并向上移动视图控制器层次结构。   如果找到一个视图控制器,其属性值为true,   它要求视图控制器呈现新的视图控制器。如果不   视图控制器定义了表示上下文,UIKit要求   窗口的根视图控制器来处理演示文稿。默认   此属性的值为false。一些系统提供的视图   控制器,如UINavigationController,更改默认值   为真。

答案 8 :(得分: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()
        }
    }
}

答案 9 :(得分:2)

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

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

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

答案 10 :(得分:2)

我认为可能有更清洁的解决方案。我发现键盘有点'眨眼',然后在显示时退回,并且在<html> <head> <script> function addval() { alert ("script ee"); var x=document.getElementById("myselect"); var y=document.getElementById("mtxt"); x.Add(y); alert ("inserted in dropdown"); } </script> </head> <body> <form> <select id="myselect" name="select"> <option>op1</option> <option>op2</option> <option>op3</option> <option>op4</option> </select> <input type="text" name="mytext" id="mtxt"> <input type="button" value="add" onClick="addval()"> </form> </body> </html> 中调用becomeFirstResponder正在工作,但是键盘进入较晚,动画有点古怪。

通过检查演示文稿来包装我的重新加载数据方法让每个人都满意:

didPresentSearchController:

我通过在- (void)updateSearchResultsForSearchController:(UISearchController *)searchController { if (!searchController.isBeingPresented && !searchController.isBeingDismissed) { [self.collectionView reloadData]; } } 中设置断点来找到这个。 resignFirstResponder正在调用resignFirstResponder,而reloadData则调用updateSearchResultsForSearchController:。事实证明,在搜索控制器的呈现过程中调用了updateSearchResultsForSearchController:。如果您在此期间使用UISearchBar所在的视图层次结构,则搜索控制器会被阻塞。我的猜测是reloadData调用导致UICollectionReusableView标题视图出现并返回到视图层次结构并强制UISearchBar子视图重新签署第一个响应者。

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

答案 11 :(得分:2)

答案是在viewDidAppear中调用becomeFirstResponder。

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

答案 12 :(得分:2)

这就是它在Swift 4中的作用

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

答案 13 :(得分:2)

基于@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()
        }
    }
}

答案 14 :(得分:2)

Xcode 11.4,Swift 5.2

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

enter image description here

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

如果您想用键盘激活TextField,则需要在主线程上调用它。无需激活SearchController,这会自动发生。

enter image description here

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

答案 15 :(得分: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()
    }
}

答案 16 :(得分:0)

对我而言,使用viewDidAppear时存在相当大的延迟。最好在becomeFirstResponder中异步使用viewDidLoad(使用iOS 10,Swift 3测试):

override func viewDidLoad() {
    super.viewDidLoad()

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

答案 17 :(得分:0)

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

答案 18 :(得分:0)

在目标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];
    });
}

答案 19 :(得分:0)

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

答案 20 :(得分:0)

我对此进行了如下修复:

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可见,因此可以成为第一响应者。