ios objective-c uisearchbar first-responder uisearchcontroller

ios - No se puede establecer searchBar como firstResponder



objective-c uisearchbar (17)

Enfoque alternativo

Esta respuesta analiza una serie de cuestiones relacionadas con esta ocurrencia y explica la lógica de depuración utilizada para aislar y determinar la causa específica del problema en mi caso. En el camino, comparto otras posibilidades que podrían haber funcionado en otras situaciones y explico por qué esto funciona en mi situación.

tldr ; Mire self.definesPresentationContext = true, isbeingDismissed, isBeingPresented, canBecomeFirstResponder y delegue la asignación de SearchController, Searchbar, SearchResultsUpdater y sus métodos delegados.

( Fuente de la solución self.definesPresentationContext - Vea la respuesta SO )

Una cosa a tener en cuenta es el contexto de cómo se está presentando SearchBar. Incrustado en una barra de herramientas, barra de navegación, otra UIView, como una vista de entrada de entrada o entrada. Todo lo cual, he encontrado que tiene algún impacto en el tiempo y la animación interna de la barra de búsqueda a medida que se presenta o descarta.

Intenté todas las soluciones presentadas y ninguna de ellas funcionó hasta que reconsideré cómo estaba tratando de usar la barra de búsqueda. En mi caso, estaba presionando un controlador (B) con un controlador de búsqueda de un controlador (A) que ya tenía un controlador de búsqueda inicial. Programé incrustado cada uno de los controladores de búsqueda dentro de titleView de mi elemento de navegación al hacer una actualización de extracción.

Las respuestas que sugerían agregar searchbar.becomeFirstResponder() en el ciclo de vida no tenían sentido para mi caso de uso ya que la vista estaba completamente cargada cuando quería insertar y mostrar mi barra de búsqueda en el elemento de navegación. La lógica también parecía confusa ya que los métodos del ciclo de vida del controlador de visualización ya deberían estar operando en el hilo principal. Agregar un retraso a la presentación también parecía ser una interferencia con las operaciones internas de la pantalla y la animación utilizada por el sistema.

Descubrí que llamar a mi función para alternar la inserción funcionaba cuando empujaba el controlador de vista desde la controladora A pero el teclado no se visualizaba correctamente cuando se lo empujaba desde el controladorB. La diferencia entre estas dos situaciones era que el controllerA era un controlador de tabla de visión estático y el controladorB tenía un controlador de tabla de vista que yo había hecho que se pudiera buscar agregando su propio controlador de búsqueda.

eg controllerA con la barra de búsqueda segues al controllerB con la barra de búsqueda

Utilizando varios puntos de interrupción y examinando el estado de searchController y la barra de búsqueda en diferentes puntos, pude determinar que searchController.canBecomeFirstResponder devolvía false . También encontré que necesitaba establecer SearchResultsUpdater en self y los delegados en searchController y searchBar.

Finalmente noté que establecer self.definesPresentationContext = true en el controlador A no permitía que se mostrara el teclado cuando self.definesPresentationContext = true el controlador self.definesPresentationContext = true en la pila de navegación. Mi solución fue mover self.definesPresentationContext = true para viewDidAppear en controllerA y en el método prepare(for:sender:) del controllerA lo cambio a self.definesPresentationContext = false cuando el destino es controllerB. Esto resolvió el problema de visualización del teclado en mi caso.

Una palabra sobre animación ~ He encontrado que al asignar cosas al elemento de navegación o barra de navegación, el sistema tiene algunas temporizaciones y animaciones predeterminadas incorporadas. Evito agregar animación personalizada, código en métodos moveToParent o presentaciones retrasadas porque en muchos casos ocurre un comportamiento inesperado.

Por qué esta solución funciona

La documentation Apple sobre definesPresentationContext indica el comportamiento predeterminado y toma nota de algunas situaciones en las que este contexto ajusta el comportamiento que el controlador asignó para administrar la apariencia del teclado. En mi caso, el controlador A se asignó para gestionar la presentación en lugar del controlador B, así que simplemente cambié ese comportamiento ajustando este valor:

Cuando se usa el estilo currentContext o overCurrentContext para presentar un controlador de vista, esta propiedad controla qué controlador de vista existente en su jerarquía de controlador de vista está realmente cubierto por el nuevo contenido. Cuando se produce una presentación basada en el contexto, UIKit comienza en el controlador de vista de presentación y sube la jerarquía del controlador de vista. Si encuentra un controlador de vista cuyo valor para esta propiedad es verdadero, solicita que el controlador de vista presente el nuevo controlador de vista. Si ningún controlador de vista define el contexto de la presentación, UIKit le pide al controlador de vista raíz de la ventana que maneje la presentación. El valor predeterminado para esta propiedad es falso. Algunos controladores de vista proporcionados por el sistema, como UINavigationController, cambian el valor predeterminado a verdadero.

Tengo una barra de búsqueda que estoy configurando en un controlador de tabla. Hice referencia a esta pregunta similar. UISearchBar no puede convertirse en el primero en responder después de que UITableView volvió a aparecer, pero todavía no puedo configurarlo como primera respuesta. En archivo .h:

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

En vista 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;

Y en viewDidAppear:

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

Cuando paso a la vista, la barra de búsqueda se anima, pero no aparece el teclado.


Basado en la solución de @ mislovr, la demora 0.1 no fue lo suficientemente larga. Aquí está mi código actualizado para esa respuesta.

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


Bueno, encontré la solución que en realidad funciona perfectamente para mí.

No llame [self.searchController setActive:YES]; antes de llamar a [self.searchController.searchBar becomeFirstResponder];

Lo que es mejor, no llame [self.searchController setActive:YES]; en absoluto.

Llamar solo [self.searchController.searchBar becomeFirstResponder]; y el teclado simplemente aparece como debería, sin ningún retraso.

Parece ser algo así como un error y mucha gente lo confirma. Por ejemplo, compruebe aquí: al asignar foco a través de becomeFirstResponder a UISearchBar de UISearchController, el teclado no aparece


Creo que podría haber una solución más limpia. Descubrí que el teclado estaba como ''parpadeando'' y luego volviendo a bajar cuando se presentaba, y llamando a becomeFirstResponder en didPresentSearchController: funcionaba, pero el teclado llegaba tarde y la animación era un poco peculiar.

Envolver mi método de recarga de datos con un cheque de presentación hizo felices a todos:

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

Encontré esto estableciendo un punto de interrupción en resignFirstResponder . se llamaba a reloadData desde reloadData , que a su vez fue llamado por updateSearchResultsForSearchController: Resulta que se llama a updateSearchResultsForSearchController: durante la presentación del controlador de búsqueda. Si se ensucia con la jerarquía de vista en la que se encuentra UISearchBar durante este tiempo, el controlador de búsqueda se vuelve loco. Supongo que la llamada a reloadData estaba causando que la vista del encabezado UICollectionReusableView salga y regrese a la jerarquía de vistas y obligando a la subvista UISearchBar a renunciar al primer respondedor.

Otro síntoma que vi fue que el término de búsqueda no se restablecía en el medio de la barra de búsqueda al cancelar, lo que hizo que no se presentara correctamente en futuros clics.


En el Objetivo 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]; }); }


Esto es lo que funcionó para mí en 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() }


Esto es lo que funcionó para mí en Swift 4

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


La función searchController.searchBar.becomeFirstResponder() debe invocarse en el hilo principal y después de searchController.active = true en el método viewDidLoad. Aquí está la solución completa. Funciona en iOS 9.3

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


La respuesta es llamar a becomeFirstResponder en viewDidAppear.

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


La solución con - (void)didPresentSearchController:(UISearchController *)searchController no funcionó, ya que este método de delegado se (void)didPresentSearchController:(UISearchController *)searchController solo cuando el usuario toca la barra de búsqueda ...

Sin embargo, esta solución funcionó:

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


La solución de mezclar @edwardmp y @mislovr responde algo así como funcionó (el teclado se abre con un ligero retraso):

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


Luché con esto por un tiempo pero lo hice funcionar por:

  • Inicializando viewDidLoad() en viewDidLoad()
  • Configuración active = true en viewDidAppear()
    • Esto desencadena didPresentSearchController() en la extensión UISearchControllerDelegate .
  • Configuración de searchBar.becomeFirstResponder() en didPresentSearchController()

Aquí está el ejemplo completo, usa Autocompletar de Google Maps.

// 1. override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) resultSearchController.isActive = true } // 2. ->> UISearchControllerDelegate func didPresentSearchController(_ searchController: UISearchController) { DispatchQueue.main.async { searchController.searchBar.becomeFirstResponder() } }


Me di cuenta de este problema también. Lo que parece suceder es que la llamada a becomeFirstResponder se realiza cuando searchController todavía está ''cargando''. Si comenta en becomeFirstResponder , observa que no hay diferencia. Entonces necesitamos una forma de llamar a becomeFirstResponder después de que SearchController esté ''listo'' para cargar.

Cuando miré varios métodos de delegado noté que hay un método delegado:

- (void)didPresentSearchController:(UISearchController *)searchController

Este método se llama justo después de que se haya presentado searchController. Luego realizo la llamada para becomeFirstResponder en becomeFirstResponder :

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

Esto soluciona el problema. Notará que cuando se carga searchController, la barra de búsqueda ahora tiene foco.


Muy similar a otras respuestas, pero tuve que acceder a la cola principal en ViewDidAppear . No se puede actuar sobre SearchBarController hasta que aparezca la View y luego solo se puede hacer en la cola principal para la UI :

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


Para mí, hay un retraso bastante grande al usar viewDidAppear . Puede ser mejor utilizar becomeFirstResponder asincrónicamente en viewDidLoad (probado con iOS 10, Swift 3):

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


Swift 4, iOS 11

Esto funciona para mi

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


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

Funciona ;-)