もぶわさおの「ダメ人間さんいらっしゃい」ブログ

技術系ブログにしたかったのにそうもいかなくなったけど、閉鎖するのもアレだからなんか書くブログです

SearchBarに入力中の文字を使い、リストから候補を探したい場合(インクリメンタルサーチ)

SearchBarに日本語を入力する際、変換候補が現れると入力が楽ですよね

それを実装する際にはまったので、その実装を説明します

 

まず、SearchBarに「確定前の入力内容」が飛んでくるイベントは

func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 

です

入力中の検索バーの内容(いままさに入力している文字以外)は、searchBar.textから取得可能です

今まさに入力している文字は、textに入っていますし、textが編集される文字列のインデックスはrangeに入っています

 

じゃあ、searchBar.textのrange位置にtextを入れてやれば、今まさに入力されている状態と同じ文字がくるかと思うじゃないですか

やってみたソースが以下

let searchText = searchBar.text! as NSString).replacingCharacters(in: range, with: text)

でも、これ、うまくいかないんです

 

やってみてもらえればわかりますが、rangeでくるデータが意外に微妙なのです

日本語入力の場合、「あ」→「い」という風に、押す回数によって文字が変わりますよね?(フリック入力でない場合)

その際にやってくるrangeの中身がみんな同じなので、使えません

文字を足そうとしているのか、置き換えようとしているのか、判別つかないんです

 

じゃあ、どうしましょう?

結果として、遅延実行という手を取りました

ちょっと時間をあけて、処理を行うという手法です

複数回キーを押されたにしても、遅延して処理を実行することにより、searchBarには入力後の値が入っているだろう想定でやるのです

だので、以下のようになります

// searchBarに文字入力されたら呼ばれるメソッド(確定前でも呼ばれる)
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        // リストを全消去する
        self.list?.removeAll()
            
        // 検索文字列を生成する
        let searchText = searchBar.text!
            
        // 検索対象の文字列を絞り込んで、リストにする
        self.list = self.stationList?.filter({($0 as! StationMaster).stationNameHiragana!.contains(searchText)})
            
        // リストを再描画する
        self.tableView.reloadData()
    }
    return true
}

わかりますかね

DispatchQueue.main.asyncAfterの中の処理は0.1秒ほど遅れて実行されます

その中でやっているのは、searchTextの中の文字を信用して検索しているのです

やってみた実績として、上記の方法をオススメします