it-swarm.com.de

SearchBar kann nicht als firstResponder festgelegt werden

Ich habe eine Suchleiste, die ich in einem Tableview-Controller einsetze. Ich habe auf diese ähnliche Frage verwiesen UISearchBar kann nicht zum Ersthelfer werden, nachdem UITableView erneut angezeigt wurde , kann es aber immer noch nicht als Ersthelfer festlegen. In .h-Datei:

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

In Sicht 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;

Und in viewDidAppear:

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

Wenn ich zur Ansicht wechsle, wird die searchBar animiert, aber es wird keine Tastatur angezeigt.

36
user3411663

Mir ist dieses Problem auch aufgefallen. Es scheint, dass der Aufruf von becomeFirstResponder durchgeführt wird, wenn der searchController noch geladen wird. Wenn Sie becomeFirstResponder auskommentieren, stellen Sie fest, dass es keinen Unterschied gibt. Wir brauchen also einen Weg, um becomeFirstResponder aufzurufen, nachdem der searchController 'fertig' geladen wurde.

Bei verschiedenen Delegatmethoden habe ich festgestellt, dass es eine Delegatmethode gibt:

- (void)didPresentSearchController:(UISearchController *)searchController

Diese Methode wird direkt nach der Präsentation des searchControllers aufgerufen. Dann mache ich den Aufruf von becomeFirstResponder:

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

Dies behebt das Problem. Sie werden feststellen, dass beim Laden des searchControllers die Suchleiste jetzt den Fokus hat.

45
edwardmp

Die Lösung mit - (void)didPresentSearchController:(UISearchController *)searchController hat nicht funktioniert, da diese Delegatmethode nur aufgerufen wird, wenn der Benutzer auf die Suchleiste tippt ...

Diese Lösung hat jedoch funktioniert:

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

Nun, ich habe die Lösung gefunden, die eigentlich perfekt für mich funktioniert.

Rufen Sie nicht [self.searchController setActive:YES]; auf, bevor Sie [self.searchController.searchBar becomeFirstResponder]; aufrufen.

Was ist besser, rufen Sie [self.searchController setActive:YES]; überhaupt nicht an. 

Rufen Sie nur [self.searchController.searchBar becomeFirstResponder]; auf, und die Tastatur wird ohne Verzögerung wie gewünscht angezeigt.

Es scheint ein bisschen wie ein Fehler zu sein, und viele Leute bestätigen es. Überprüfen Sie zum Beispiel hier: Wenn Sie der UISearchController-UISearchBar den Fokus über becomeFirstResponder zuweisen, erscheint die Tastatur nicht.

15
Roland T.

Die Funktion searchController.searchBar.becomeFirstResponder() muss im Hauptthread und nach searchController.active = true in der viewDidLoad-Methode aufgerufen werden. Hier ist die volle Lösung. Es funktioniert unter iOS 9.3

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

Sehr ähnlich zu anderen Antworten, aber ich musste auf die Hauptwarteschlange zugreifen in der ViewDidAppear. Die SearchBarController kann erst dann ausgeführt werden, wenn die View erscheint, und dies kann nur in der Hauptwarteschlange für UI erfolgen. :

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

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

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

Es klappt ;-)

5
Vitya Shurapov

Swift 4, iOS 11

Für mich geht das

// 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

Die Antwort besteht darin, in viewDidAppear in den Namen comeFirstResponder zu wechseln.

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

Dies hat in Swift 4 für mich funktioniert

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

Ich denke, es könnte eine sauberere Lösung geben. Ich stellte fest, dass die Tastatur nach oben blies und dann wieder runter lief und der Aufruf von becomeFirstResponder in didPresentSearchController: funktionierte, aber die Tastatur kam spät, und die Animation war etwas skurril. 

Das Umwickeln meiner Reload-Daten-Methode mit einer Präsentationsüberprüfung hat alle glücklich gemacht:

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

Ich habe dies gefunden, indem ich einen Haltepunkt in resignFirstResponder gesetzt habe. resignFirstResponder wurde von der reloadData aufgerufen, die wiederum von updateSearchResultsForSearchController: aufgerufen wurde. Es stellt sich heraus, dass updateSearchResultsForSearchController: während der Präsentation des Suchcontrollers aufgerufen wird. Wenn Sie sich in der Ansichtshierarchie befinden, in der sich die UISearchBar in dieser Zeit befindet, wird der Suchcontroller fehlerhaft. Meine Vermutung ist, dass der Aufruf von reloadData dazu führte, dass die UICollectionReusableView-Header-Ansicht herauskam und in die Ansichtshierarchie zurückkehrt und die UISearchBar-Untersicht zwingt, den ersten Responder zurückzutreten.

Ein anderes Symptom, das ich sah, war, dass der Suchbegriff beim Abbrechen nicht auf die Mitte der Suchleiste zurückgesetzt wurde, was dazu führte, dass er bei zukünftigen Klicks nicht richtig dargestellt wurde.

2
Brian King

Ich kämpfte eine Weile damit, bekam es aber durch: 

  • searchController in viewDidLoad() initialisieren
  • Einstellung von active = true in viewDidAppear()
    • Dies löst didPresentSearchController() in der Erweiterung UISearchControllerDelegate aus.
  • Einstellung von searchBar.becomeFirstResponder() in didPresentSearchController()

Hier ist das vollständige Beispiel, es verwendet 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

Die Lösung des Mischens von @edwardmp- und @mislovr-Antworten funktionierte für mich (die Tastatur springt etwas verzögert heraus):

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

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

Swift 5 Lösung:

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

Basierend auf der Lösung von @ mislovr war die Verzögerung von 0.1 nicht lang genug. Hier ist mein aktualisierter Code zu dieser Antwort.

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

Alternativer Ansatz

Diese Antwort beschäftigt sich mit einer Reihe von Problemen, die mit diesem Vorkommnis zusammenhängen, und erläutert die Debugging-Logik, die zum Isolieren und Bestimmen der Spezifischen Ursache des Problems in meinem Fall verwendet wird. Auf dem Weg teile ich andere .__ Möglicherweise haben Sie in anderen Situationen gearbeitet und erklären Sie, warum dies in meiner Situation funktioniert.


tldr; Schauen Sie sich die self.definesPresentationContext = true an, die isBeingDismissed, isBeingPresented, canBecomeFirstResponder und Delegiertenzuweisung des SearchControllers, der Searchbar, SearchResultsUpdater und ihre Delegatmethoden.

(Source von self.definesPresentationContext-Lösung - Siehe SO Antwort )

Dabei ist zu beachten, wie die SearchBar dargestellt wird. Eingebettet in eine Symbolleiste, Navigationsleiste, eine andere UIView, als Eingabe- oder Eingabezubehöransicht. Ich habe festgestellt, dass dies Auswirkungen auf das Timing und die internen Animationen der Suchleiste hat, wenn diese angezeigt oder abgelehnt wird.

Ich habe alle vorgestellten Lösungen ausprobiert und keine davon funktionierte, bis ich überlegte, wie ich die searchBar verwenden wollte. In meinem Fall habe ich einen Controller (B) mit einem Suchcontroller von einem Controller (A) geschoben, auf dem sich bereits ein erster Suchcontroller befand. Ich habe jeden Suchcontroller programmgesteuert in die titleView meines Navigationselements eingebettet, wenn eine Pull-Aktualisierung durchgeführt wird. 

Die Antworten, die auf die Hinzufügung von searchbar.becomeFirstResponder() in den Lebenszyklus schließen lassen, waren für meinen Anwendungsfall nicht sinnvoll, da die Ansicht vollständig geladen war, als ich meine Suchleiste in das navigationItem einfügen und anzeigen wollte. Die Logik schien auch verwirrend zu sein, da die Lebenszyklusmethoden des View-Controllers bereits im Haupt-Thread ausgeführt werden sollten. Das Hinzufügen einer Verzögerung zur Präsentation schien auch eine Störung der internen Vorgänge der Anzeige und der Animation zu sein, die vom System verwendet werden.

Ich habe festgestellt, dass das Aufrufen meiner Funktion zum Umschalten der Einfügung funktioniert hat, als ich den View-Controller von ControllerA aus gedrückt habe. Die Tastatur würde jedoch nicht ordnungsgemäß angezeigt, wenn Sie von ControllerB aus gedrückt wurden. Der Unterschied zwischen diesen beiden Situationen bestand darin, dass ControllerA ein statischer Tableview-Controller war und ControllerB einen Tableview-Controller hatte, den ich durch Hinzufügen eines eigenen Suchcontrollers auffindbar gemacht hatte. 

z.B. controllerA mit der Suchleiste wechselt zu controllerB mit der Suchleiste 

Mit einer Reihe von Haltepunkten und der Überprüfung des Status von searchController und der Suchleiste an verschiedenen Punkten konnte ich feststellen, dass der searchController.canBecomeFirstResponderfalse zurückgegeben hat. Ich fand auch, dass ich SearchResultsUpdater auf self und die Delegierten sowohl auf dem searchController als auch auf der searchBar setzen musste. 

Ich stellte schließlich fest, dass die Einstellung self.definesPresentationContext = true auf ControllerA nicht zulässt, dass die Tastatur angezeigt wird, wenn ich ControllerB auf den Navigationsstapel drückte. Meine Lösung bestand darin, self.definesPresentationContext = true in viewDidAppear auf ControllerA und in der prepare(for:sender:)-Methode von ControllerA zu verschieben. Ich ändere es in self.definesPresentationContext = false, wenn das Ziel ControllerB ist. Das Problem mit der Tastaturanzeige wurde in meinem Fall behoben.

A Word on animation ~ Ich habe festgestellt, dass das System beim Zuordnen von Elementen zu navigationItem oder navigationBar einige eingebaute Timing- und Standardanimationen hat. Ich vermeide das Hinzufügen von benutzerdefinierten Animationen, Code in moveToParent-Methoden oder verzögerte Präsentationen, da in vielen Fällen unerwartetes Verhalten auftritt. 

Warum funktioniert diese Lösung?

Apple documentation on definesPresentationContext gibt das Standardverhalten an und weist auf einige Situationen hin, in denen dieser Kontext das Verhalten anpasst, das der Controller zum Verwalten der Tastaturdarstellung zugewiesen hat. In meinem Fall wurde ControllerA dazu verpflichtet, die Präsentation zu verwalten, und nicht ControllerB. Daher habe ich dieses Verhalten durch Ändern dieses Werts geändert:

Bei Verwendung des currentContext- oder overCurrentContext-Stils zur Darstellung einer View Controller, steuert diese Eigenschaft den vorhandenen View Controller In Ihrer Ansicht wird die Controller-Hierarchie durch das neue .__ abgedeckt. Inhalt. Wenn eine kontextbasierte Präsentation auftritt, beginnt UIKit am Darstellen des View Controllers und Aufrufen der View Controller-Hierarchie . Wenn ein View-Controller gefunden wird, dessen Wert für diese Eigenschaft "true" ist, Der View Controller wird aufgefordert, den neuen View Controller zu präsentieren. Wenn nein View Controller definiert den Präsentationskontext, UIKit fragt die Root View Controller des Fensters, um die Präsentation zu handhaben. Der Standard Wert für diese Eigenschaft ist falsch. Eine vom System bereitgestellte Ansicht Controller, z. B. UINavigationController, ändern den Standardwert um wahr zu sein.

1
Tommie C.

In Ziel 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

Dies hat in Swift 3 für mich funktioniert.

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

Für mich gibt es eine große Verzögerung bei der Verwendung von viewDidAppear. Es kann besser sein, becomeFirstResponder asynchron in viewDidLoad zu verwenden (getestet mit iOS 10, Swift 3):

override func viewDidLoad() {
    super.viewDidLoad()

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