[Swift] MapKitを使って”場所のサジェスト”を簡単に実装してみる
はじめに
モバイルアプリサービス部の中安です。
最近イベント告知のブログばっかり書いてて、さすがによくないと思ったので久しぶりに「実装してみる」シリーズなブログを書こうかと思います(汗)
今回やるのは「場所のサジェスト機能」を簡単に実装してみるというものです。
「サジェスト機能」
インターネットのサーチエンジンで、検索した文字列に関連の深い語句を逐次予測して表示する機能。米国グーグル社のサーチエンジンではオートコンプリートといい、語句の候補を、他の利用者の検索語句や同社のデータベースから機械的に抽出して表示している。予測表示機能。検索予測機能。検索候補機能。(デジタル大辞泉)
「サジェスト機能」は、ここにも書いてあるように、文字を入力したら続きの候補が出てくる、いわゆる「オートコンプリート機能」とも言いかえられる機能のことです。
そして、この機能は MapKit フレームワークの MKLocalSearchCompleter クラスを使用することで、あまり難しいことを考えなくてもサクッと作れてしまいます。
まずは基本実装まで
サンプルなので、ここらへんは簡潔に
ストーリーボード
画面の一番上に UITextField
、その下に UITableView
を置いているだけです。
テーブルビューに配置するセルは、その CellStyle
を .subtitle
、識別用のIDを "cell"
としています。
ビューコントローラ
ビューコントローラのソースコードです。 とりあえずデーブルビューが表示できる程度の空っぽな実装にしてあります。
import UIKit class ViewController: UIViewController { @IBOutlet private weak var tableView: UITableView! @IBOutlet private weak var textField: UITextField! private var searchCompleter = MKLocalSearchCompleter() override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self } @IBAction private func textFieldEditingChanged(_ sender: Any) { // あとで } } extension ViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // あとで return cell } }
IBOutlet
はそれぞれのUIパーツに、IBAction
はテキストフィールドの editingChanged
イベントに紐づけています。
テキストフィールドに入力されると候補が一覧に出てくるような実装をこれからしていきます。
MKLocalSearchCompleter
A utility object for generating a list of completion strings based on a partial search string that you provide.
提供する部分検索文字列に基づいて補完文字列のリストを生成するためのユーティリティオブジェクトです。
https://developer.apple.com/documentation/mapkit/mklocalsearchcompleter
MKLocalSearchCompleter
クラスは、リファレンスの見出しにもある通りの機能を提供してくれます。
与えた文字列から自動的に「場所」の検索をして、MKLocalSearchCompletion
というデータオブジェクトの配列を作って返してくれます。
これを先ほどのビューコントローラクラスに実装してみます。
インポート
MapKit
をインポートします。
import UIKit import MapKit
MKLocalSearchCompleter オブジェクト
メンバ変数に MKLocalSearchCompleter
のオブジェクトを初期化して代入しておきます。
class ViewController: UIViewController { // (省略) private var searchCompleter = MKLocalSearchCompleter() // (省略) }
デリゲートになる
ビューコントローラが MKLocalSearchCompleter
のデリゲートになります。
extension ViewController: MKLocalSearchCompleterDelegate { // 正常に検索結果が更新されたとき func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { tableView.reloadData() } // 検索が失敗したとき func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) { // エラー処理 } }
class ViewController: UIViewController { // (省略) override func viewDidLoad() { // (省略) searchCompleter.delegate = self } }
テキストフィールドの入力値が変わったとき
MKLocalSearchCompleter
の queryFragment
プロパティに文字を代入すると、自動的に検索が始まります。
class ViewController: UIViewController { @IBAction private func textFieldEditingChanged(_ sender: Any) { searchCompleter.queryFragment = textField.text! } }
ネットワークを利用して結果を取得してきますが、 ネットワークに関する処理やキャッシュデータの持たせ方などを考える必要はないです。 前述のデリゲートメソッドで、成功時と失敗時のことだけを実装すればいいわけです。
テーブルビューに反映させる
検索の結果は MKLocalSearchCompleter
の results
プロパティに入っています。
ここには先述の MKLocalSearchCompletion
が配列で格納されているので、それをテーブルビューで表示するだけです。
extension ViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return searchCompleter.results.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) let completion = searchCompleter.results[indexPath.row] cell.textLabel?.text = completion.title cell.detailTextLabel?.text = completion.subtitle return cell } }
title
プロパティには「場所の名前」、 subtitle
プロパティには「住所」などの付加情報が文字列で入っています。
表示結果
ここまで実装できれば、もう完成です。 実際にアプリを動かしてみて、テキストフィールドに適当に文字列を入力してみます。
自分は大阪の人間なので、「しん」と打ってみると自動的に「新大阪」や「心斎橋」「新今宮」などの大阪の場所の名前が一覧化されました。
データ自体は取れているので、あとは煮るなり焼くなりできるかと思います。
少しカスタマイズ
フィルタの種別
MKLocalSearchCompleter
の filterType
プロパティに対して、フィルタの種別を与えてやります。
searchCompleter.filterType = .locationsOnly
.locationsAndQueries
: 地図上の場所と一般的なクエリの両方を取得する.locationsOnly
: 地図上の場所のみを取得する
リファレンスには .locationsAndQueries
では "cof" と検索すると "coffee" がサジェストされてくると書いてあります。
位置指定
先ほどの例では自分が大阪にいるので、大阪の検索結果が取れました。しかし、その位置を指定したいこともあると思います。
その場合は region
を指定してやります。
let tokyoStation = CLLocationCoordinate2DMake(35.6811673, 139.7670516) // 東京駅 let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001) // ここは適当な値です let region = MKCoordinateRegion(center: tokyoStation, span: span) searchCompleter.region = region
例では、東京駅を中心として検索結果を得るよう緯度経度を渡しました。
この設定を含めて実際にアプリを動作させてみると
同じ検索文字列でも「新宿」や「新橋」「新丸の内」などの東京の地名になりました。
最後に
このようなサジェスト機能は GoogleMap の API などを使っても実現できると思いますが、iOS の機構だけでも手軽に作ることができます。
この記事で書いたのは本当に簡単なサンプルですが、少ないコード量でもちょっと「おっ」と思える機能を実現できます。
何かのお役に立てれば幸いです。