【SwiftUI】MapKitを導入して地図を表示する

# この投稿は何？
SwiftUIフレークワークは宣言的にコードを記述することで、すばやくアプリケーションを開発できます。ただ、これまでのUIKitやAppKitで利用してきたUIコンポーネントのすべてがSwiftUIフレームワークに含まれてはいません。
この投稿では、__SwiftUIフレームワークの中でMapKitビューを利用する__ 方法を例に、UIKitビューをSwiftUIで扱う手順を解説します。

### 環境
- macOS10.15 beta8
- Xcode11 GM Seed2

### この投稿で構築するビューの画面
![スクリーンショット 2019-09-19 0.05.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/737e72c2-c22e-96e1-a6fc-d33914b65fac.png)

# 実装手順
_Content.swift_ に直接、地図ビューを構築することもできます。
ただ、_SwiftUIフレームワーク_による開発は __小さいビューを組み合わせる__ ことをコンセプトとしています。
地図を表示するだけの_カスタムSwiftUIビュー_を作ってから、_Content.swift_で利用することにします。

## SUMapView.swift
カスタムSwiftUIビューを記述するファイルです。
MapViewをSwiftUIビューとして利用するので、`SUMapView`という名前にしました。

### 全体のソースコード
プレビュープロバイダは一切、コードを編集していないのでプレビュー定義の部分は省略しています。

````Swift:SUMapView.swift
import SwiftUI
import MapKit

struct SUMapView: UIViewRepresentable {
    typealias UIViewType = MKMapView
    
    func makeUIView(context: UIViewRepresentableContext<SUMapView>) -> MKMapView {
        MKMapView(frame: .zero)
    }
    
    func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<SUMapView>) {
        let coordinate = CLLocationCoordinate2D(latitude: 37.3351, longitude: -122.0088)
        let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        uiView.setRegion(region, animated: true)
    }
}
````

### SUMapViewの画面
上記コードのプレビューです。
![スクリーンショット 2019-09-19 0.10.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/5f364609-7618-975a-7217-b8bcd9d5e200.png)



#### 1. ファイル作成
新しいファイルを作成します。
1. メニューバーから「File > New > File...」を選択する。
2. iOSのSwiftUI Viewを選択して、Nextをクリックする。
3. ファイル名をSUMapView.swiftをして、Createをクリックする。

#### 2. MapKitフレームワーク
`import` キーワードで、このファイルにMapKitを導入します。

````Swift:SUMapView.swiftにMapKitを導入する
import SwiftUI
import MapKit
````

#### 3. UIViewRepresentableプロトコル
`SUMapView`構造体に`UIRepresentable`プロトコルを採用します。

````Swift:SUMapView
strict SUMapView: UIViewRepresentable {
    ...
}
````

この時点で、`UIViewRepresentable`プロトコルに準拠するための要件を満たしていないので、エラーになります。また、SwiftUIビューとしてUIKit系のビューを描画するには、以下に挙げる2点のメソッドを実装する必要があります。

- `makeUIView(context:) -> Self.UIViewType` メソッドを実装する
- `updateUIView(_:context:)` メソッドを実装する

#### 4. makeUIView(context:) メソッド
`makeUIView(context:)`メソッドは「`SUMapView` 構造体がどんなビューか？」を決定します。このメソッドが返す`Self.UIViewType`型が、カスタムSwiftUIビューとして扱いたい`UIKit`ビューの型になります。
ここでは`MKMapView`型のことです。

`makeUIView(context:)`メソッドを記述しようとすると、Xcodeの補完機能が作動します。
該当するメソッドを選択すると、以下のように記述が補完されます。

````Swift:    
func makeUIView(context: UIViewRepresentableContext<SUMapView>) -> SUMapView.UIViewType { 
    // code...       
}
````

`// code...` の部分に、以下のように記述して `MKMapView` 型のビューを返すようにします。

````Swift:makeUIViewメソッドの実装
MKMapView(frame: .zero)    // return は省略
````

メソッドが`UIViewType`と一致する型を返していないので、下記のエラーが表示されます。
_Reference to invalid associated type 'UIViewType' of type 'SUMapView'_

#### 5. UIViewType
`UIViewType`型は未定義なので、任意の型として定義します。

````Swift:    
typealias UIViewType = MKMapView
````

これで、`makeUIView(context:)`メソッドが宣言する返り値型が `MKMapKit(frame: .zero)` と一致するようになったので、エラーが解消されます。
ちなみに...
先に `UIViewType` のタイプエイリアスを定義してから、`makeUIView(context:)`メソッドを実装すると以下のように補完されます。

````Swift:    
func makeUIView(context: UIViewRepresentableContext<SUMapView>) -> MKMapView {
    // code....    
}
````

返り値の型に注目してください。
タイプエイリアスの宣言を探す必要がなく、ビューの型が理解できます。

#### 6. updateUIView(_:context:) メソッド
このメソッドでは、「ビューが描画されるときの内容」を定義します。ここでは、Appleデベロッパーの聖地 __Apple Park__ の地図を表示することにします。
実装すると、エラーがすべて解消されます。

## ContentView.swift
プロジェクトに最初から含まれる既定ファイルです。
このビューに、作成したカスタムSwiftUIビュー `SUMapView` を組み込むカタチにします。

### 全体のソースコード
プレビューの定義は省略しました。
デフォルトのTextビューを`SUMapView`に置き換えます。

````Swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        SUMapView()
            .edgesIgnoringSafeArea(.all)
            .statusBar(hidden: true)
    }
}
````

モディファイアも使えるので、、MapKitビューがSwiftUIビューっぽく利用できていることがわかります。

### 完成図

![スクリーンショット 2019-09-19 0.05.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/3abaafde-01da-6234-94e1-09fb508ac283.png)