【Swift】MapKitを使用した場所のサジェスト表示、座標情報の取得
こんにちは。ラフアンドレディのiOS大好きマン春蔵です。
今回は、地図系アプリでよくある
テキスト入力された場所をもとにサジェスト表示
選択された場所の座標情報(緯度、経度)を取得する処理
を紹介します。
サンプルについては最後にリンクを記載しています。
画面サンプル
場所のサジェスト表示
import SwiftUI
import MapKit
struct ContentView: View {
/// ViewModel
@StateObject var viewModel = ContentViewModel()
var body: some View {
VStack {
HStack {
// 場所入力欄
TextField("", text: $viewModel.location)
.padding(5)
.cornerRadius(5)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.primary.opacity(0.6), lineWidth: 0.3))
.onChange(of: viewModel.location) { newValue in
viewModel.onSearchLocation()
}
// 検索ボタン
Image(systemName: "magnifyingglass")
.imageScale(.large)
.onTapGesture {
viewModel.onSearch()
}
}
if viewModel.completions.count > 0 {
// 検索候補
List(viewModel.completions , id: \.self) { completion in
HStack{
VStack(alignment: .leading) {
Text(completion.title)
Text(completion.subtitle)
.foregroundColor(Color.primary.opacity(0.5))
}
Spacer()
}
.contentShape(Rectangle())
.onTapGesture{
viewModel.onLocationTap(completion)
}
}
} else {
HStack {
// 場所の詳細情報
Text(viewModel.locationDetail)
Spacer()
}
}
Spacer()
}
.padding()
}
}
class ContentViewModel : NSObject, ObservableObject, MKLocalSearchCompleterDelegate{
/// 位置情報検索クラス
var completer = MKLocalSearchCompleter()
/// 場所
@Published var location = ""
/// 検索クエリ
@Published var searchQuery = ""
/// 位置情報検索結果
@Published var completions: [MKLocalSearchCompletion] = []
/// 場所の詳細情報
@Published var locationDetail = ""
override init(){
super.init()
// 検索情報初期化
completer.delegate = self
// ポイントのみ
completer.resultTypes = .pointOfInterest
}
/// 住所変更時
func onSearchLocation() {
// マップ表示中の目的地と同じなら何もしない
if searchQuery == location {
completions = []
return
}
// 検索クエリ設定
searchQuery = location
// 場所が空の時、候補もクリア
if searchQuery.isEmpty {
completions = []
} else {
if completer.queryFragment != searchQuery {
completer.queryFragment = searchQuery
}
}
}
/// 検索結果表示
/// - Parameter completer: 検索結果の場所一覧
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
DispatchQueue.main.async {
if self.searchQuery.isEmpty {
self.completions = .init()
} else {
self.completions = completer.results
}
}
}
/// 場所をタップ
/// - Parameter completion: タップされた場所
func onLocationTap(_ completion:MKLocalSearchCompletion){
DispatchQueue.main.async {
// 場所を選択
self.location = completion.title
self.searchQuery = self.location
// 検索
self.onSearch()
}
}
/// 場所の詳細情報を検索
func onSearch(){
// 検索結果クリア
completions = []
locationDetail = ""
// 検索条件設定
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = self.location
// 検索実行
MKLocalSearch(request: request).start { (response, error) in
if let error = error {
print("MKLocalSearch Error:\(error)")
return
}
if let mapItem = response?.mapItems.first {
DispatchQueue.main.async {
self.locationDetail += "\n経度 : " + String(mapItem.placemark.coordinate.longitude)
self.locationDetail += "\n緯度 : " + String(mapItem.placemark.coordinate.latitude)
}
}
}
}
}
class ContentViewModel : NSObject, ObservableObject, MKLocalSearchCompleterDelegate{
・・・
override init(){
super.init()
// 検索情報初期化
completer.delegate = self
// 場所のみ(住所を省く)
completer.resultTypes = .pointOfInterest
}
・・・
NSObject, MKLocalSearchCompleterDelegateを継承、delegateを自クラスに設定する事によりMKLocalSearchCompleterに検索クエリを設定した際、completerDidUpdateResultsメソッドがコールバックされます。
completer.resultTypesをpointOfInterestに設定し、検索結果より住所を省いてます。
/// 住所変更時
func onSearchLocation() {
・・・
// 場所が空の時、候補もクリア
if searchQuery.isEmpty {
completions = []
} else {
if completer.queryFragment != searchQuery {
completer.queryFragment = searchQuery
}
}
}
completer.queryFragmentに検索する場所の名前を設定すると検索が行われ、completerDidUpdateResultsメソッドがコールバックされます。
/// 検索結果表示
/// - Parameter completer: 検索結果の場所一覧
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
DispatchQueue.main.async {
if self.searchQuery.isEmpty {
self.completions = .init()
} else {
self.completions = completer.results
}
}
}
検索クエリの結果がcompleter.resultsに配列型で返却されます。self.completionsに検索結果を設定する事により、画面上に候補が一覧表示されます。
場所の座標情報取得
/// 場所の詳細情報を検索
func onSearch(){
// 検索結果クリア
completions = []
locationDetail = ""
// 検索条件設定
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = self.location
// 検索実行
MKLocalSearch(request: request).start { (response, error) in
if let error = error {
print("MKLocalSearch Error:\(error)")
return
}
if let mapItem = response?.mapItems.first {
DispatchQueue.main.async {
self.locationDetail += "\n経度 : " + String(mapItem.placemark.coordinate.longitude)
self.locationDetail += "\n緯度 : " + String(mapItem.placemark.coordinate.latitude)
}
}
}
}
入力された場所に関する経度、緯度情報を MKLocalSearch(request: request).startメソッドで取得します。
経度、緯度は地図でピンを表示する、目的地付近でアラームを鳴らす等の機能で必要となる事が多いです。
コードサンプル
今回紹介したサンプルの全コードです
アプリケーション
今回紹介したMapKitの技術を利用したiOSアプリです。
それではまた!次回の講座でお会いしましょう!
春蔵でした!
───-- - - -
フォロー Me!
↓ ↓
Twitter : @RandR_inc
◆───-- - - -
ラフアンドレディでの採用はこちら ↓ ↓ ↓
ラフアンドレディでは、みんなのびのびと仕事をしています!エンジニアが長く幸せに活躍できる環境で、仲間と楽しく働いてみませんか?
この記事が参加している募集
この記事が気に入ったらサポートをしてみませんか?