web-dev-qa-db-fra.com

Impossible de définir la barre de recherche comme premier répondeur

J'ai un SearchBar que je mets dans un tableviewcontroller. J'ai référencé cette question similaire UISearchBar ne peut pas devenir le premier répondeur après la réapparition de UITableView mais je ne parviens toujours pas à la définir en tant que premier répondant. Dans le fichier .h:

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

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

Et dans viewDidAppear:

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

Lorsque je passe à l'affichage, la barre de recherche s'anime, mais aucun clavier ne s'affiche.

36
user3411663

J'ai aussi remarqué ce problème. Ce qui semble se passer, c'est que l'appel à becomeFirstResponder est effectué lorsque le searchController est encore en cours de chargement. Si vous commentez becomeFirstResponder, vous remarquerez qu'il n'y a pas de différence. Nous avons donc besoin d’un moyen d’appel pour devenir firstFreeResponder une fois le chargement de searchController terminé.

Lorsque j'ai consulté différentes méthodes de délégation, j'ai remarqué qu'il existe une méthode de délégation:

- (void)didPresentSearchController:(UISearchController *)searchController

Cette méthode est appelée juste après la présentation de searchController. Ensuite, je passe l'appel à becomeFirstResponder:

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

Cela corrige le problème. Vous remarquerez que lorsque searchController est chargé, la barre de recherche a maintenant le focus.

45
edwardmp

La solution avec - (void)didPresentSearchController:(UISearchController *)searchController n'a pas fonctionné car cette méthode de délégation est appelée uniquement lorsque l'utilisateur appuie sur la barre de recherche.

Cependant, cette solution a fonctionné:

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

Eh bien, j'ai trouvé la solution qui fonctionne parfaitement pour moi.

N'appelez pas [self.searchController setActive:YES]; avant d'appeler [self.searchController.searchBar becomeFirstResponder];

Quoi de mieux, n'appelle pas [self.searchController setActive:YES]; du tout. 

Appelez uniquement [self.searchController.searchBar becomeFirstResponder]; et le clavier apparaît comme il se doit, sans aucun délai.

Cela ressemble à un bug et beaucoup de gens le confirment. Par exemple, cochez cette case: Lors de l’attribution du focus via UISearchBar de UISearchBar par UppearchBar de UISearchController à l’un de nos contacts, le clavier n’apparaît pas

15
Roland T.

La fonction searchController.searchBar.becomeFirstResponder() doit être appelée dans le thread principal et après searchController.active = true dans la méthode viewDidLoad. Voici la solution complète. Cela fonctionne sur iOS 9.3

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

Très similaire aux autres réponses, mais je devais accéder à la file d'attente principale dans ViewDidAppear. Le SearchBarController ne peut pas être traité tant que le View n'apparaît pas et ne peut alors l'être que dans la file d'attente principale pour UI :

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

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

Facile Swift3 variante:

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

Ça marche ;-)

5
Vitya Shurapov

Swift 4, iOS 11

Ça marche pour moi

// 1.
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    resultSearchController.isActive = true
}

// 2. ->> UISearchControllerDelegate
func didPresentSearchController(_ searchController: UISearchController) {

    DispatchQueue.main.async {
        searchController.searchBar.becomeFirstResponder()
    }
}
5
Vladimir Vozniak

La réponse est d'appeler devenez premier répondeur dans viewDidAppear.

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

C'est ce qui a fonctionné pour moi dans Swift 4

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

Je pense qu'il pourrait y avoir une solution plus propre. J'ai constaté que le clavier était en quelque sorte «en train de sauter», puis de revenir en arrière, et appeler becomeFirstResponder dans didPresentSearchController: fonctionnait, mais le clavier arrivait tard et l'animation était un peu bizarre. 

Envelopper ma méthode de rechargement des données avec un chèque pour la présentation a fait plaisir à tout le monde:

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

J'ai trouvé cela en définissant un point d'arrêt dans resignFirstResponder. resignFirstResponder était appelé à partir de la reloadData, qui à son tour était appelée par updateSearchResultsForSearchController:. Il s'avère que updateSearchResultsForSearchController: est appelé lors de la présentation du contrôleur de recherche. Si vous bloquez la hiérarchie des vues dans laquelle se trouve la barre UISearchBar, le contrôleur de recherche est bouché. Je suppose que l'appel reloadData a provoqué la sortie de la vue d'en-tête UICollectionReusableView et son retour dans la hiérarchie des vues, forçant ainsi la sous-vue UISearchBar à quitter le premier répondant.

Un autre symptôme que j'ai constaté est que le terme de recherche n'était pas réinitialisé au centre de la barre de recherche lors de l'annulation, ce qui l'a empêché de s'afficher correctement lors de clics ultérieurs.

2
Brian King

Je me suis battu avec cela pendant un moment mais je l'ai fait fonctionner en: 

  • Initialisation de la searchController dans viewDidLoad()
  • Paramétrer active = true dans viewDidAppear()
    • Cela déclenche didPresentSearchController() dans l'extension UISearchControllerDelegate.
  • Paramétrage de searchBar.becomeFirstResponder() dans didPresentSearchController()

Voici l'exemple complet, il utilise Google Maps Autocomplete.

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()
        }
    }
}
2
Derek Soike

La solution consistant à mélanger les réponses @edwardmp et @mislovr a plutôt bien fonctionné pour moi (le clavier se détache légèrement)

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

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

Selon la solution de @ mislovr, le délai 0.1 n'était pas assez long. Voici mon code mis à jour pour cette réponse.

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

Approche alternative

Cette réponse examine un certain nombre de problèmes liés à cet événement et explique la logique de débogage utilisée pour isoler et déterminer la cause spécifique du problème aurait pu travailler dans d’autres situations et expliquer pourquoi cela fonctionne dans ma situation.


tldr; Regardez le self.definesPresentationContext = true, le isBeingDismissed, isBeingPresented, peutBecomeFirstResponder et délégation d'assignation du SearchController, de la barre de recherche, SearchResultsUpdater et leurs méthodes de délégué.

(Source of self.definesPresentationContext solution - Voir la réponse SO )

Une chose à garder à l'esprit est le contexte de la présentation de la barre de recherche. Intégré dans une barre d'outils, une barre de navigation, un autre UIView, en tant qu'entrée ou vue d'accessoire d'entrée. Tous ces éléments ont eu un impact sur le calendrier et l’animation interne de la barre de recherche lorsqu’elle est présentée ou rejetée.

J'ai essayé toutes les solutions présentées et aucune d'entre elles n'a fonctionné jusqu'à ce que je revienne sur la façon dont j'essayais d'utiliser SearchBar. Dans mon cas, je poussais un contrôleur (B) avec un contrôleur de recherche d’un contrôleur (A) qui avait déjà un contrôleur de recherche initial. J'ai intégré par programme chacun des contrôleurs de recherche dans le titleView de mon élément de navigation lors d'une actualisation par extraction. 

Les réponses suggérant l'ajout de searchbar.becomeFirstResponder() dans le cycle de vie n'avaient aucun sens pour mon cas d'utilisation car la vue était complètement chargée au moment où je voulais insérer et afficher ma barre de recherche dans navigationItem. La logique semblait également déroutante puisque les méthodes de cycle de vie du contrôleur de vue devraient déjà fonctionner sur le thread principal. L'ajout d'un délai à la présentation semblait également constituer une interférence avec les opérations internes de l'affichage et de l'animation utilisées par le système.

J'ai trouvé que l'appel de ma fonction pour basculer l'insertion fonctionnait lorsque j'ai poussé le contrôleur de vue depuis le contrôleur A mais que le clavier ne s'affichait pas correctement lorsqu'il était poussé depuis le contrôleur B. La différence entre ces deux situations était que controllerA était un contrôleur de table statique et que controllerB disposait d'un contrôleur de tableview que j'avais rendu consultable en ajoutant son propre contrôleur de recherche. 

par exemple. controllerA avec la barre de recherche se transforme en controllerB avec la barre de recherche 

En utilisant un certain nombre de points d'arrêt et en examinant l'état du searchController et de la barre de recherche à différents points, j'ai pu déterminer que le searchController.canBecomeFirstResponder retournait false. J'ai également constaté que je devais définir la variable SearchResultsUpdater sur self et les délégués à la fois sur searchController et sur searchBar. 

J'ai finalement constaté que le réglage de self.definesPresentationContext = true sur controllerA ne permettait pas l'affichage du clavier lorsque j'ai poussé controllerB sur la pile de navigation. Ma solution a été de déplacer le self.definesPresentationContext = true à viewDidAppear sur le contrôleurA et dans la méthode prepare(for:sender:) de controllerA. Je le change en self.definesPresentationContext = false lorsque la destination est controllerB. Cela a résolu le problème d'affichage du clavier dans mon cas.

Un mot sur l'animation ~ J'ai constaté que lors de l'attribution d'éléments à navigationItem ou à navigationBar, le système intègre des animations temporelles et par défaut intégrées. J'évite d'ajouter des animations personnalisées, du code dans les méthodes moveToParent ou des présentations en retard, car un comportement inattendu se produit dans de nombreux cas. 

Pourquoi cette solution fonctionne

Apple documentation on definesPresentationContext indique le comportement par défaut et note certaines situations dans lesquelles ce contexte ajuste le comportement attribué au contrôleur pour gérer l'apparence du clavier. Dans mon cas, controllerA était supposé gérer la présentation plutôt que controllerB, alors je viens de changer ce comportement en ajustant cette valeur:

Lorsque vous utilisez le style currentContext ou overCurrentContext pour présenter un Contrôleur de vue, cette propriété contrôle le contrôleur de vue existant dans votre vue, la hiérarchie du contrôleur est en fait couverte par le nouveau contenu. Quand une présentation basée sur le contexte se produit, UIKit commence par le présentation du contrôleur de vue et affichage de la hiérarchie du contrôleur de vue . S'il trouve un contrôleur de vue dont la valeur pour cette propriété est true, il demande à ce contrôleur de vue de présenter le nouveau contrôleur de vue. Sinon Le contrôleur de vue définit le contexte de présentation, UIKit demande au fichier le contrôleur de vue racine de la fenêtre pour gérer la présentation. Le défaut La valeur de cette propriété est false. Certaines vues fournies par le système Les contrôleurs, tels que UINavigationController, modifient la valeur par défaut à vrai.

1
Tommie C.

En objectif 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];
    });
}
0
Subhasish

C'est ce qui a fonctionné pour moi dans 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()
}
0
pableiros

Pour moi, le décalage avec viewDidAppear est assez important. Il peut être préférable d’utiliser becomeFirstResponder de manière asynchrone dans viewDidLoad (testé avec iOS 10, Swift 3):

override func viewDidLoad() {
    super.viewDidLoad()

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