【问题标题】:Cannot set searchBar as firstResponder无法将 searchBar 设置为 firstResponder
【发布时间】:2015-03-13 04:03:49
【问题描述】:

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

当我转到视图时,searchBar 动画,但没有出现键盘。

【问题讨论】:

  • 如果你用 dispatch_after() 稍微延迟becomeFirstResponder 怎么办?
  • super viewDidAppear之前调用它有什么原因。
  • 我试着把它放在前后没有运气。搜索栏是以编程方式创建的,是否有一些我可能错过的明显连接?

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


【解决方案1】:

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

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

- (void)didPresentSearchController:(UISearchController *)searchController

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

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

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

【讨论】:

  • 这在 iOS8 中对我不起作用。只有当我手动触摸 SearchBar 的文本字段时,才会调用 didPresentSearchController
  • @Besi 我想我知道你做错了什么;您需要打开/激活 searchViewController,例如把它放在你的 viedDidLoad 中:[self.searchController setActive:TRUE];
  • @Besi 确保也将 becomeFirstResponder 调用扔到主线程上。出于某种原因,didPresentSearchController 在某些 iOS 版本的后台线程中被调用!
  • [self.searchController setActive:TRUE];必须放入viewDidAppear,如果放入viewDidLoad就不行了。
  • iOS 9.3 这不起作用。不幸的是,mislovr 的回答或 dispatch_after 是唯一的解决方案。
【解决方案2】:

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

【讨论】:

  • 或更简单的:[self.searchController.searchBar performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.1];
  • 这对我有用。仍然是一个有效的答案。
【解决方案3】:

我认为可能有更清洁的解决方案。我发现键盘在出现时有点“闪烁”然后又向下,并且在didPresentSearchController: 中调用becomeFirstResponder 正在工作,但是键盘来晚了,动画有点古怪。

将我的 reload data 方法包装为检查演示让每个人都很高兴:

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

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

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

【讨论】:

  • 那么你现在在哪里调用 becomeFirstResponder?
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

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

    最好不要打电话给[self.searchController setActive:YES];

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

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

    【讨论】:

    • 这对我不起作用。你在哪里打电话[self.searchController.searchBar becomeFirstResponder];
    【解决方案6】:

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

    【讨论】:

    • 设置 searchController.active 首先对我有用。为什么在视图生命周期方法中需要 Async.main 调用,这不保证在主线程上运行吗?
    • UISearchController 目前似乎有一些未完成的工作(好像他们太早调用他们的委托方法),并且 Async.main 只是安排 becomeFirstResponder() 发生在未来的迭代中,绕过那个错误。
    • 你很幸运,通过将它分派到主队列,它会在搜索控制器完成呈现后执行。正确的解决方案是从didPresentSearchController委托方法中调用becomeFirstResponder
    【解决方案7】:

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

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

    【讨论】:

    • 不设置 active 变量使它对我有用。
    • viewDidAppear 总是在主队列上调用。这段代码实际上是调度 becomeFirstResponder() 发生在主队列的未来迭代中,允许 UISearchController 完成它目前尚未完成的任何事情。
    【解决方案8】:

    我为此奋斗了一段时间,但通过以下方式解决了它:

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

    这是完整示例,它使用 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】:

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

      它有效;-)

      【讨论】:

      • 你为什么在viewDidAppear调用DispatchQueue.main.async?从来没有任何情况会在主线程以外的任何地方调用viewDidAppear,因此根本不需要它。
      • @edwardmp 这可能会发生,这个发送解决了我的问题。想给vitya 5炮礼炮。
      【解决方案10】:

      对我来说,使用viewDidAppear 时会有很大的延迟。在viewDidLoad中异步使用becomeFirstResponder会更好(用iOS 10,Swift 3测试):

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

      【讨论】:

        【解决方案11】:

        替代方法

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


        tldr;看看self.definesPresentationContext = true, isBeingDismissed、isBeingPresented、canBecomeFirstResponder 和 SearchController、Searchbar 的委托分配, SearchResultsUpdater 及其委托方法。

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

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

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

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

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

        例如controllerA with searchbar segues to controllerB with searchbar

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

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

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

        为什么这个解决方案有效

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

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

        【讨论】:

          【解决方案12】:

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

          【讨论】:

            【解决方案13】:

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

            【讨论】:

            • 对我有用,注意我确实需要在 viewWillAppear 中设置 self.definesPresentationContext = true 以使其正常运行。
            【解决方案14】:

            答案是在 viewDidAppear 中调用 becomeFirstResponder。

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

            【讨论】:

              【解决方案15】:

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

              【讨论】:

                【解决方案16】:

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

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

                【讨论】:

                  【解决方案17】:

                  根据@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()
                          }
                      }
                  }
                  

                  【讨论】:

                    【解决方案18】:

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

                    【讨论】:

                    • 在 iOS 11、iOS 12 和 iOS 13 上运行良好。
                    • 是的,这行得通,太糟糕了,动画完成并弹出键盘需要一点时间。试过viewWillAppear,但没用
                    【解决方案19】:

                    斯威夫特 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()
                        }
                    }
                    

                    【讨论】:

                      【解决方案20】:

                      Xcode 11.4、Swift 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
                      

                      【讨论】:

                      • 这实际上是最新的答案。我想要第二个选项,它适用于 iOS 13.6
                      • @rbaldwin 老兄。
                      • 这可行,但有一个问题。从第一个视图控制器推送后,我的第二个视图控制器中有这个搜索栏。它在第一次推送时有效,但是当我点击返回并再次来到此屏幕时,它显示错误Attempt to present UISearchController which is already presenting。要修复它,请在viewDidLoad 中调用它:definesPresentationContext = true
                      • 这是唯一适用于 iOS 14 的解决方案。谢谢!
                      【解决方案21】:

                      我将其修复如下:

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

                      【讨论】:

                      • 在 Stack Overflow 上不鼓励仅使用代码的答案,因为它们没有解释它如何解决问题。请编辑您的答案以解释此代码的作用以及它如何改进该问题已有的现有投票答案,以便对其他有类似问题的用户有用。
                      【解决方案22】:
                      1. 定义搜索栏。
                      2. SearchBar 文本字段“searchField”选项。
                      3. viewDidLoad 或 viewWillAppear 调用代码。

                      @IBOutlet 弱变量 searchBar: UISearchBar!

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

                      textFieldUISearchBar?.becomeFirstResponder()

                      【讨论】:

                      • 纯代码答案并不是特别有用。请添加一些关于此代码如何解决问题的说明。
                      • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-10-30
                      • 1970-01-01
                      • 2016-01-25
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多