見出し画像

【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

◆───-- - - - 

ラフアンドレディでの採用はこちら ↓ ↓ ↓

ラフアンドレディでは、みんなのびのびと仕事をしています!エンジニアが長く幸せに活躍できる環境で、仲間と楽しく働いてみませんか?

この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?