【SwiftUI】Mapkitを使った位置情報の取得とピンの表示

# はじめに
SwiftUIでMapkitとFirebaseを使用し、位置情報をデータベースに保存することを目的とする。
今回はSwiftUIで現在地を取得し、地図を表示するところまでを記載。
Firebaseの導入方法は前回記事を参考にしてください。

**参考記事**
[【SwiftUI】CocoaPods導入手順とFirebaseの設定](https://qiita.com/m37335/items/6c507495f840d4bdaa43)

# 開発環境
OSX 10.15.7 (Catalina) 
Xcode 12.0.1
CocoaPods 1.10.0

# Mapkitのインポートと地図表示まで

MapkitとCoreLocationをインポートする。
Alertは位置情報の取得が設定できなかった場合に通知を行うように設定。

```swift:ContentView.swift
import SwiftUI
import Firebase

// 以下の行を追加
import CoreLocation
import MapKit

struct ContentView: View {
    // 以下を追記
    @State var manager = CLLocationManager()
    @State var alert = false
    
    var body: some View {
        // 以下の行を追加
        // ContentViewに地図を表示
        mapView(manager: $manager, alert: $alert).alert(isPresented: $alert) {

            Alert(title: Text("Please Enable Location Access In Setting Panel!!!"))
        }
    }
}

// MKMapViewの設定
```

## mapViewの作成
地図表示にはmakeUIView、updateUIViewの設定が必要です。

[【SwiftUI】MapKitを導入して地図を表示する](https://qiita.com/chino_tweet/items/73e6df230ab77a2f5887)
を参考にすると、makeUIViewのところは```code```の部分は```MKMapView(frame: .zero)```を記載するだけで地図の表示は可能です。今回は位置情報を取得するため、```showsUserLocation```を```true```に設定しています。

makeCoordinatorはカスタムインスタンスを作成し、makeUIView(context:)メソッドを呼び出す前にこのメソッドを呼び出します。makeCoordinatorはclassで設定したコーディネーターを提供します。

```swift:ContentView.swift
// MKMapViewの設定
struct mapView : UIViewRepresentable {
    typealias UIViewType = MKMapView
    
    @Binding var manager : CLLocationManager
    @Binding var alert : Bool

    let map = MKMapView()
    
    func makeCoordinator() -> mapView.Coordinator {
        
        return mapView.Coordinator(parent1: self)
    }
    
    func  makeUIView(context: UIViewRepresentableContext<mapView>) -> MKMapView {
        // Tokyo 35.6804° N, 139.7690° E
        let center = CLLocationCoordinate2D(latitude: 35.6804, longitude: 139.7690)
        let region = MKCoordinateRegion(center: center, latitudinalMeters: 10000, longitudinalMeters: 10000)
        map.region = region

        manager.delegate = context.coordinator
        manager.startUpdatingLocation()
        map.showsUserLocation = true
        manager.requestWhenInUseAuthorization()
        return map
    }
    
    func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<mapView>) {
        
    }
    
    class Coordinator: NSObject, CLLocationManagerDelegate {
        
        var parent : mapView
        
        init(parent1 : mapView) {
            
            parent = parent1
        }
        
        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            
            if status == .denied{
                
                parent.alert.toggle()
                print("denied")
            }
        }
        
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            
            let location = locations.last

            let point = MKPointAnnotation()
            
            let georeader = CLGeocoder()
            georeader.reverseGeocodeLocation(location!) { (places, err) in
                
                if err != nil {
                    
                    print((err?.localizedDescription)!)
                    return
                }
                
                let place = places?.first?.locality
                point.title = place
                point.subtitle = "Current Place"
                point.coordinate = location!.coordinate
                self.parent.map.removeAnnotations(self.parent.map.annotations)
                self.parent.map.addAnnotation(point)
                
                let region = MKCoordinateRegion(center: location!.coordinate, latitudinalMeters: 10000, longitudinalMeters: 100000)
                print(region)
                self.parent.map.region = region
                
            }
        }
    }
}
```

locationManagerのところで、位置情報を取得し、地図上にピンを落とし、表示する設定にしています。simulatorを利用した場合はピンが落とされますが、SwiftUI画面のpreview画面では表示されません。simulatorではGPS情報は取得できませんでしたが、実機で確認したところ、現在地を表すピンが表示されました。

ここではCLGeocoderを呼び出しているため、逆ジオコーディングを行い、地名を表示することも可能です。他にもピンの表示の仕方もここを変更すれば実装が可能です。
ここは今後実装します。


## Xcodeプロジェクトの'info.plist'に追加
![Info_plist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/592d45f0-29d0-6ff9-a66f-b00025cfe78c.png)

Privacy - Location Usage Description 
開発者に許可を与える。なぜ、ユーザーの位置情報を取得するかの理由を記載。
>[Deprecated] Privacy - Location Usage Description (NSLocationUsageDescription) - Allows the developer to describe why the app wants to access the user location. NOTE: This key has been deprecated in iOS 8 (and greater). Use NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription instead.

Privacy - Location When In Use Usage Description
開発者に許可を与える。アプリ使用中にユーザーの位置情報を取得する理由を記載。
>Privacy - Location When In Use Usage Description (NSLocationWhenInUseUsageDescription) - Allows the developer to describe why the app wants to access the user's location while it is running.

![Info_plist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/ada9bda7-6433-309d-eb8c-ccce15ed80d1.png)

「Location When In Use Usage Description」のValueを編集し、文字列を入力すると、初回起動時の確認画面で、文字を表示することができます。
下記のsimulator画像参考。

## プレビューで確認し、地図を表示
現在の位置情報が取得できます。
Simulatorを起動した場合は初回時に「位置情報の取得」許可が求められます。

![ContentView_swift.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/4e47918a-d7c4-bc9a-1e88-457b69c15765.png)

初回起動画面
![iPhone_11_–_14_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/b7652110-95f2-dc43-e5d8-a34a47b9b8f5.png)

位置情報取得とピンの表示
simulatorでGPS取得ができずになぜか、サンフランシスコが表示されている。
![iPhone_11_–_14_0-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/ef29d678-bc2b-7fb9-16be-f7e94643aad8.png)

デバックの際に```[GEOAddressObject] [NSLocale currentLocale] failed for NSLocaleCountryCode@```のエラーが表示されるのはsimulatorの「設定」→「一般」→「言語と地域」のところで「地域」に日本を登録すると、エラーは出なくなります。

以上です。