MapKitに関して学んだあれこれ

# 概要・背景
なぜ全く業務に関係ないMapKitを学習しているかというと、「行きたい場所をピンで刺して忘れないようにしておく機能を持つアプリ」を個人開発しており、MapKitをこれでもかと言うほど使っているからです。
正直今回の発表で説明しても業務内で活用できる点はないと思われますが、どうか温かい目で見ていただければ嬉しく思います。
(※技術的に未熟で間違っていることも多々あると思われますが、コメントでご指摘・ご教示いただければ幸いでございます。)

## 開発環境
MacBookAir: M1,2020 (チップApple M1)
macOS: Monterey 12.5
Swift: 5.7?ぐらい
Xcode: 14.0

iPhone: iPhone13　128GB　スターライト
iOS: 16.0

# MapKitとは
MapKitとはそもそもなんぞや？と問いかけたいのですが、その名の通り地図に関するあれこれを簡単に実装できてしまう、フレームワークのことです。もう少し頭が良さそうに言えば、
>Display map or satellite imagery within your app, call out points of interest, and determine placemark information for map coordinates.
>アプリ内で地図または衛星画像を表示し、関心のあるポイントを呼び出し、地図座標の目印情報を決定します。
>>by Apple

[mapkit公式ドキュメント](https://developer.apple.com/documentation/mapkit/)

とのことでもし、地図に関するアプリを作りたいのであり、そんなにこだわりがなけれなとりあえず、MapKitを使うことをお勧めします。「なんだ簡単じゃん。」と思われる方も多いかと思いますが、MapKitは意外と奥が深いなと言う印象でした。
MapKitを学習し始めてから、純正のマップアプリは本当によくできているなと感じました。そんなMapKitの機能のごく一部を説明します。

余談ですが、マップ関連のライブラリには、GoogleMapもあります。
```MapViewController.swift
//cocoapodの導入やGoogleMapsPlatformを設定することで使えるみたいです！
import GoogleMaps
```
しかし、googlemap関連の情報は思いのほか少なかったので、これ以上は言及しないことにします。

### 前提条件
先にMapKitを使用する方がおそらく使うであろう設定についてお伝えします。工程は、3つです。
###### ①StoryboardのViewに、下記のMapKitViewを各々表示したいサイズで制約を加えて追加してください。↓これ
![MapKitView](https://gyazo.com/435acbe58766db3f7a079a9916d32015.png "MapKitView")

###### ②①で追加した、MapKitViewをSwiftファイルのコードに紐付け(@IBOutlet)、お好きな名前をおつけください。(慣用的に、宣言名は「mapView」を使われることが多いみたいなので、特にこだわりがなければ、「mapView」を使用することをお勧めします。)
ここで注意しないといけないのが、単純にコードにMapKitViewを宣言するだけでは何も変更できないと言うことです。
僕は、何も考えずにコードを書いていたので、後々気づきました。(笑)
MapKitViewを紐付けしなくても、位置情報の取得等はできてしまうので、それがまた厄介なところです。
上記の作業に加えて、以下をインポートしておいてください。
```MapViewController.swift
//MapKitを使う際は、この２つがインポートするみたいです。
import MapKit
//↓↓公式によれば、「デバイスの位置と方向を取得してくれるフレームワーク」とのことです！
import CoreLocation
```
[corelocation公式ドキュメント](https://developer.apple.com/documentation/corelocation/)
###### ③②でコードに書いた「mapView」を使いやすくするために、下記の形でクロージャーによる情報の追加を行なってあげることをお勧めします。

```MapViewController.swift
    @IBOutlet private weak var mapView: MKMapView! = {
        let map = MKMapView()
        map.translatesAutoresizingMaskIntoConstraints = false
        return map
    }()
```

これで最初に行う定番の設定は終了です！！

## 実装編
###### ①現在地を取得する（前編）
現在地をアプリ内で取得するには、まず許可を求めるアラートを出さなければなりません。そのために、まず設定を変更します。
まずは、当該Xcodeのファイルの「~.xcodeproj」→「TARGETS」→「Build Setting」→「Info.plist」の『Privacy - Location Always~』と『Privacy - Location When~』に下記のように位置情報を求める際に表示される文言を追加してください。　![MapKitView](https://gyazo.com/7381bfb049ef1cf1932639f95f7bfaaa.png "MapKitView")

次に行わないといけないのが、StoryboardのMapview設定のところで、UserLocationにチェックを入れることです。
なお、ここまで現在地についての解説をしていますが、現在地を取得しているかどうかを確認するためにはiPhone等の実機が必要になることをお忘れなく。
![MapKitView](https://gyazo.com/d9709ebd294b30fc7ac1f7f78dec5c29.png "MapKitView")
(※現在地のスクショを表示したいのですが、自宅の位置がバレてしまうので、割愛します。)

###### ②現在地を取得する（中盤編）
初回起動もしくは、現在取得不可の時以外にアプリ起動時、マップを自動的に現在地周辺に照準を合わせる機能を実装しました。
照準を合わせてどれだけズームして表示するかは、定数「meters」で定義しています。
一定数、アプリ起動時でも現在地を表示したくない方もいると思うので、その場合は、緯度と経度を指定してあげて東京駅に照準を合わせてアプリ内のマップがスタートする実装を加えました！

```MapViewController.swift
    //    現在地付近からマップスタートする関数
    private func setupFocace() {
        mapView.showsUserLocation = true
        if let userLocation = locationManager.location?.coordinate {
            let meters: CLLocationDistance = 1000.0
            let mapRegion = MKCoordinateRegion(center: userLocation,
                                               latitudinalMeters: meters,
                                               longitudinalMeters: meters)
            mapView.setRegion(mapRegion, animated: true)
            return
        }
        //初回ログインした場合もしくは、位置情報許可しない場合、東京駅周辺からスタート
        let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
        let tokyoStation = CLLocationCoordinate2DMake(35.681236, 139.767125)
        let region = MKCoordinateRegion(center: tokyoStation, span: span)
        mapView.region = region
    }
```


###### ③現在地を取得する（後編）
地図上で現在地以外の場所を見ている時に、画面の照準を現在地に合わせたい時があると思うのですが、その場合はstoryboad上にUIButtonを設置して@IBActionで紐付けをします。
タッチされた時に、MKCoordinateRegionの機能を使って、mapViewに表示されているユーザーの現在地の座標をとってmapの照準を変更することができます！
意外とめんどくさい実装でした。(笑)

```MapViewController.swift
    @IBAction private func tappedZoomUserLocationButton() {
        var region:MKCoordinateRegion = mapView.region
        region.center = mapView.userLocation.coordinate
        region.span.latitudeDelta = 0.02
        region.span.longitudeDelta = 0.02
        mapView.setRegion(region, animated:true)
    }
```
おそらく、現在地関連で実装することはこれぐらいですが、うまく使えば目的地までの経路や時間も表示できたりしちゃうみたいなので、是非調べてみてください！


###### ④Point Of Interestを用いて、地図上に表示する施設等を選別する
Apple純正の地図アプリでは、地図上に駅や銀行、警察署などの施設を表示しているのですが、この表示も各自が開発しているアプリに合わせて表示するかどうかを決めることができます。以下は、僕が開発しているアプリ内での設定です。僕の場合、選択したものは地図上から除外しています！
【Point Of Interest例】
![IMG_8768.PNG](https://gyazo.com/a0e04b7c8d7741ee19508e2063a2a85c.png)
例えば、防犯用の地図アプリを開発しているとすると、「Porice」「Fire Station」のみを表示させるなどの小技が発動できます！
具体的な使い方としては、Filterを「Exclusion」にすると選択されたものを除外してそれ以外のものを表示することができ、「Inclusion」をすると選択されたもののみを地図上に表示することができます。大学等も指定すれば、以下のようにアイコンを表示できるはずです！
【地図上のアイコン例】
![IMG_8768.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630898/815d6ca8-401a-a630-4fde-2ec8384b3e14.png)

###### ⑤マップ上で長押しした場所にピンを刺す実装
よくある、目的地や気になった場所をピンで刺すような動作みなさんも使われたことあるかと思います。今回僕が作成しているアプリでは、マップ上にピンを刺して、その住所を取得する必要があるので非常に重要な実装になりました。
そんな実装をしましたが、以下のようにまずは、「recognizeLongPress(sender: UILongPressGestureRecognizer)」でロングタップ時の処理を作成しておきます。
ここで注意したいのは、「self.mapView.removeAnnotations(self.mapView.annotations)」にて、ロングタップを何回したとしても、一番最後に差したピンのみを表示するという処理を入れることです。
使用上、他にもピンがあるとどの場所を登録したいのかわからなくなるので、最新のピンしか表示しないようにしています。

```MapViewController.swift
   //ロングタップした時に呼ばれる関数
    @objc private func recognizeLongPress(sender: UILongPressGestureRecognizer) {
        if sender.state == .ended {
            // 位置情報を取得
            let location = sender.location(in: mapView)
            let coordinate = mapView.convert(location, toCoordinateFrom: mapView)

            // 逆ジオコーデインング
            let geoAddress = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
            
            CLGeocoder().reverseGeocodeLocation(geoAddress) { placemarks, error in
                guard let placemark = placemarks?.first, error == nil else { return }
                
                //住所表記
                print(placemark.administrativeArea! + placemark.locality! + placemark.name!)
                self.address = "\(placemark.administrativeArea!)\(placemark.locality!)\(placemark.name!)"
                // ピンを生成
                let pin = MKPointAnnotation()
                pin.title = placemark.administrativeArea! + placemark.locality! + placemark.name!
                // タップした位置情報に位置にピンを追加
                pin.coordinate = coordinate
                
                // 表示されているすべてのピンを削除
                self.mapView.removeAnnotations(self.mapView.annotations)
                self.mapView.addAnnotation(pin)
            }
        }
    }
```

そして、次に「recognizeLongPress(sender: UILongPressGestureRecognizer)」関数で取得してきた情報を実際に地図上に設置する関数を作成して行きます。こちらの関数では特殊なことはなく、ジェスチャー関連の使い方を学びました。

```MapViewController.swift
    //マップを長押しでpin設置
    private func setupPin() {
        let myLongPress: UILongPressGestureRecognizer = UILongPressGestureRecognizer()
        myLongPress.addTarget(self, action: #selector(recognizeLongPress))
        mapView.addGestureRecognizer(myLongPress)
    }
```

そして、以下のような形で自分のピンを刺したい場所にピンを刺すことができました！

【地図上のアイコン例】
![Screen Shot 2022-10-27 at 19.38.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630898/4cb7977c-2b12-3e53-161c-3ae393826fb7.png)

技術的にはあと「ピンを刺した場所の住所とタイトル等を投稿してFirebaseに保存できるようにする」「データベースに登録されている場所にはずっとピンが刺さっている状況にする」「ピンの色をステータスによって変更する」などなど、やりたいことは山のようにあります！


# 最後に
読んだ書籍の備忘録やアーキテクチャ等の情報を投稿することが多く、初めてガッツリ技術的なことを投稿したので、批判がくることがすごく楽しみです！
と言うのは冗談で、アウトプットすることで改めて個人開発や今回の技術とは全く関係のない業務での開発にも何かしらのメリットがあるなと実感しました。
個人開発でのコードの見直しの良い機会になったので、発表がなくても今後も定期的に技術的なことに関しても発表して行きたいと思います。
素晴らしい機会をありがとうございます！！

##### 参考文献
[mapkit公式ドキュメント](https://developer.apple.com/documentation/mapkit/)
[corelocation公式ドキュメント](https://developer.apple.com/documentation/corelocation/)
[【Swift5】MapKitまとめ](https://qiita.com/PictoMki/items/bbda6df26291e40e89b8)
[iOS16 MapKitの新機能 : 地図から場所を選ぶ、通りを見回す、検索補完（MKLookAroundScene, MKMapFeatureAnnotation, MKLookAroundSceneRequest, selectableMapFeatures)](https://qiita.com/PictoMki/items/bbda6df26291e40e89b8)
[【Swift】Apple MapKitの基本的な使い方](https://hiromiick.com/apple-mapkit-basic/)