iphone - ¿Cómo habilitar el botón cancelar con UISearchBar?
cancel-button (18)
A partir de iOS 6, el botón parece ser un UINavigationButton (clase privada) en lugar de un UIButton.
He modificado el ejemplo anterior para que se vea así.
for (UIView *v in searchBar.subviews) {
if ([v isKindOfClass:[UIControl class]]) {
((UIControl *)v).enabled = YES;
}
}
Sin embargo, esto es obviamente frágil, ya que estamos trabajando con las partes internas. También puede habilitar más que el botón, pero funciona para mí hasta que se encuentre una mejor solución.
Deberíamos pedirle a Apple que exponga esto.
En la aplicación de contactos en el iPhone, si ingresas un término de búsqueda, toca el botón "Buscar", el teclado está oculto, PERO el botón de cancelar aún está habilitado. En mi aplicación, el botón cancelar se desactiva cuando llamo a resignFirstResponder.
¿Alguien sabe cómo ocultar el teclado mientras mantiene el botón cancelar en estado habilitado?
Yo uso el siguiente código:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
}
El teclado se desliza fuera de la vista, pero el botón "Cancelar" a la derecha del campo de texto de búsqueda está desactivado, por lo que no puedo cancelar la búsqueda. La aplicación de contactos mantiene el botón cancelar en estado habilitado.
Creo que tal vez una solución es sumergirse en el objeto searchBar y llamar a resignFirstResponder en el campo de texto real, en lugar de la barra de búsqueda en sí.
Cualquier entrada apreciada.
Amplié lo que otros ya han publicado implementando esto como una categoría simple en UISearchBar.
UISearchBar + alwaysEnableCancelButton.h
#import <UIKit/UIKit.h>
@interface UISearchBar (alwaysEnableCancelButton)
@end
UISearchBar + alwaysEnableCancelButton.m
#import "UISearchBar+alwaysEnableCancelButton.h"
@implementation UISearchBar (alwaysEnableCancelButton)
- (BOOL)resignFirstResponder
{
for (UIView *v in self.subviews) {
// Force the cancel button to stay enabled
if ([v isKindOfClass:[UIControl class]]) {
((UIControl *)v).enabled = YES;
}
// Dismiss the keyboard
if ([v isKindOfClass:[UITextField class]]) {
[(UITextField *)v resignFirstResponder];
}
}
return YES;
}
@end
Aquí hay una solución un poco más sólida que funciona en iOS 7. Atravesará de forma recursiva todas las subvistas de la barra de búsqueda para asegurarse de que habilita todos los UIControl
(que incluyen el botón Cancelar).
- (void)enableControlsInView:(UIView *)view
{
for (id subview in view.subviews) {
if ([subview isKindOfClass:[UIControl class]]) {
[subview setEnabled:YES];
}
[self enableControlsInView:subview];
}
}
Simplemente llame a este método inmediatamente después de llamar a [self.searchBar resignFirstResponder]
esta manera:
[self enableControlsInView:self.searchBar];
Voila! El botón Cancelar permanece habilitado.
Encontré un enfoque diferente para hacerlo funcionar en iOS 7.
Lo que intento es algo así como la aplicación de Twitter para iOS. Si hace clic en la lupa en la pestaña Líneas de tiempo, aparece UISearchBar
con el botón Cancelar activado, el teclado que se muestra y la pantalla de búsquedas recientes. Desplácese por la pantalla de búsquedas recientes y oculta el teclado, pero mantiene activado el botón Cancelar.
Este es mi código de trabajo:
UIView *searchBarSubview = self.searchBar.subviews[0];
NSArray *subviewCache = [searchBarSubview valueForKeyPath:@"subviewCache"];
if ([subviewCache[2] respondsToSelector:@selector(setEnabled:)]) {
[subviewCache[2] setValue:@YES forKeyPath:@"enabled"];
}
Llegué a esta solución estableciendo un punto de interrupción en scrollViewWillBeginDragging:
mi vista de scrollViewWillBeginDragging:
Busqué en mi UISearchBar
y desnudé sus subvistas. Siempre tiene solo uno, que es del tipo UIView
(mi variable searchBarSubview
).
Luego, ese UIView
contiene un NSArray
llamado subviewCache
y noté que el último elemento, que es el tercero, es del tipo UINavigationButton
, no en la API pública. Así que me propuse usar codificación de clave-valor en su lugar. Comprobé si el UINavigationButton
responde a setEnabled:
y, por suerte, lo hace. Así que configuré la propiedad a @YES
. Resulta que ese UINavigationButton
es el botón Cancelar.
Esto está destinado a romperse si Apple decide cambiar la implementación de las entrañas de UISearchBar
, pero qué demonios. Funciona por ahora.
Este método funcionó en iOS7.
- (void)enableCancelButton:(UISearchBar *)searchBar
{
for (UIView *view in searchBar.subviews)
{
for (id subview in view.subviews)
{
if ( [subview isKindOfClass:[UIButton class]] )
{
[subview setEnabled:YES];
NSLog(@"enableCancelButton");
return;
}
}
}
}
(También asegúrese de llamarlo a cualquier lugar después de usar [_searchBar resignFirstResponder]).
Esto pareció funcionar para mí (en viewDidLoad):
__unused UISearchDisplayController* searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
Me doy cuenta de que probablemente debería usar UISearchDisplayController correctamente, pero esta era una solución fácil para mi implementación actual.
La mayoría de las soluciones publicadas no son sólidas y permitirán que el botón Cancelar se deshabilite en diversas circunstancias.
Intenté implementar una solución que siempre mantiene el botón Cancelar habilitado, incluso cuando se hacen cosas más complicadas con la barra de búsqueda. Esto se implementa como una subclase UISearchView personalizada en Swift 4. Utiliza el truco de valor (forKey :) para buscar el botón cancelar y el campo de texto de búsqueda, y escucha cuando el campo de búsqueda finaliza la edición y vuelve a habilitar el botón cancelar. También habilita el botón cancelar al cambiar el indicador showsCancelButton.
Contiene un par de afirmaciones para advertirle si los detalles internos de UISearchBar cambian alguna vez y evitar que funcione.
import UIKit
final class CancelSearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
guard let searchField = value(forKey: "_searchField") as? UIControl else {
assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
return
}
searchField.addTarget(self, action: #selector(enableSearchButton), for: .editingDidEnd)
}
override var showsCancelButton: Bool {
didSet { enableSearchButton() }
}
@objc private func enableSearchButton() {
guard showsCancelButton else { return }
guard let cancelButton = value(forKey: "_cancelButton") as? UIControl else {
assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
return
}
cancelButton.isEnabled = true
}
}
La solución aceptada no funcionará cuando empiece a desplazarse por la tabla en lugar de tocar el botón "Buscar". En ese caso, el botón "Cancelar" se desactivará.
Esta es mi solución que vuelve a habilitar el botón "Cancelar" cada vez que se desactiva mediante KVO.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Search for Cancel button in searchbar, enable it and add key-value observer.
for (id subview in [self.searchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:YES];
[subview addObserver:self forKeyPath:@"enabled" options:NSKeyValueObservingOptionNew context:nil];
}
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Remove observer for the Cancel button in searchBar.
for (id subview in [self.searchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]])
[subview removeObserver:self forKeyPath:@"enabled"];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Re-enable the Cancel button in searchBar.
if ([object isKindOfClass:[UIButton class]] && [keyPath isEqualToString:@"enabled"]) {
UIButton *button = object;
if (!button.enabled)
button.enabled = YES;
}
}
Método mejor y más fácil:
[(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES];
Para iOS 10, Swift 3:
for subView in self.movieSearchBar.subviews {
for view in subView.subviews {
if view.isKind(of:NSClassFromString("UIButton")!) {
let cancelButton = view as! UIButton
cancelButton.isEnabled = true
}
}
}
Para iOS 9/10 (probado), Swift 3 (más corto):
searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true })
Puede crear su CustomSearchBar heredando de UISearchBar e implementar este método:
- (void)layoutSubviews {
[super layoutSubviews];
@try {
UIView *baseView = self.subviews[0];
for (UIView *possibleButton in baseView.subviews)
{
if ([possibleButton respondsToSelector:@selector(setEnabled:)]) {
[(UIControl *)possibleButton setEnabled:YES];
}
}
}
@catch (NSException *exception) {
NSLog(@"ERROR%@",exception);
}
}
Puede usar la API de tiempo de ejecución para acceder al botón cancelar.
UIButton *btnCancel = [self.searchBar valueForKey:@"_cancelButton"];
[btnCancel setEnabled:YES];
Una mejor solución es
[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;
Versión de SWIFT para la respuesta de David Douglas (probado en iOS9)
func enableSearchCancelButton(searchBar: UISearchBar){
for view in searchBar.subviews {
for subview in view.subviews {
if let button = subview as? UIButton {
button.enabled = true
}
}
}
}
prueba esto
for(id subview in [yourSearchBar subviews])
{
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:YES];
}
}
Basándote en la respuesta de smileyborg , simplemente coloca esto en tu búsqueda Delegado de barra:
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
dispatch_async(dispatch_get_main_queue(), ^{
__block __weak void (^weakEnsureCancelButtonRemainsEnabled)(UIView *);
void (^ensureCancelButtonRemainsEnabled)(UIView *);
weakEnsureCancelButtonRemainsEnabled = ensureCancelButtonRemainsEnabled = ^(UIView *view) {
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UIControl class]]) {
[(UIControl *)subview setEnabled:YES];
}
weakEnsureCancelButtonRemainsEnabled(subview);
}
};
ensureCancelButtonRemainsEnabled(searchBar);
});
}
Esta solución funciona bien en iOS 7 y superior.
for (UIView *firstView in searchBar.subviews) {
for(UIView* view in firstView.subviews) {
if([view isKindOfClass:[UIButton class]]) {
UIButton* button = (UIButton*) view;
[button setEnabled:YES];
}
}
}