iOS 13でMapKitに新しく追加されたPOI(Point of Interest)フィルタリングを触ってみる

## はじめに
WWDC19で発表された内容が濃すぎて，
調べて使ってみたいけど扱えていない内容がまだまだたくさんです😅

今回はそのひとつで iOS 13 から新しく MapKit に追加され
**Point of Interest(POI)** フィルタリングを触ってみました。

このセッションの短い時間ではありましたが紹介されています。(10分あたりから)
**What’s New in MapKit and MapKit JS**
[https://developer.apple.com/videos/play/wwdc2019/236/](https://developer.apple.com/videos/play/wwdc2019/236/)

## MapKit における POI とは

MapKit における **POI** とは Map に表示されている，
レストランとか学校など様々な場所表示です。それぞれアイコンもあります。
言われてみればあーあれかってなりますよね。
<img width="400" alt="スクリーンショット 2019-12-23 18.22.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/f699869c-264b-150f-4d3c-a892aa1649b2.jpeg">
種類的には，執筆時点で 40種類あります。(セッション資料より)
Apple Park みたいな特別な場所は専用のものが用意されているようです。
<img width="600" alt="スクリーンショット 2019-12-23 18.22.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/6590de3a-7a0d-1556-d502-9ec69fe98b0e.png">
MapKit チームはデベロッパにもっとこういうのあったらどうか？と
ユースケースや提案があれば是非頼むというスタンスのようです。

**参考：MKPointOfInterestCategory**
[https://developer.apple.com/documentation/mapkit/mkpointofinterestcategory?language=swift](https://developer.apple.com/documentation/mapkit/mkpointofinterestcategory?language=swift)


## iOS 12 までの POI のフィルタリング

通常の Map アプリでは情報が多くてもいいと思います。
都市部だと明らかにアイコンの乱立がありユーザの目移りが問題になります。
それぞれのアプリの Map の用途によって
**POI** のフィルタリング(出し分け)ができるとアプリの UX が上がりそうです。

iOS 12 までは，POI の全表示 or 全非表示はできていました。
MapView の `showsPointsOfInterest` に Bool 値を与えていました。
コードは下記です。

```swift
let mapView = MKMapView()
mapView.showsPointsOfInterest = false
```

こんな感じで POI が非表示になります。全部非表示にはならないんだなぁ。
純正 Map 自体も更新されているだろうし，APIの方がついていってない感じ。

|デフォルト|設定OFF|
|:--:|:--:|
|<img width="375" alt="IMG_1919.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/5eed8772-a1d9-10f9-59c0-54db72bce0ee.png">|<img width="375" alt="IMG_1920.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/be727e4c-66ec-e9a1-cae3-f1c84954fe2e.png">|

**参考：showsPointsOfInterest**
[https://developer.apple.com/documentation/mapkit/mkmapview/1452102-showspointsofinterest?language=swift](https://developer.apple.com/documentation/mapkit/mkmapview/1452102-showspointsofinterest?language=swift)

## iOS 13 以降の POI フィルタリング

今までは，POI を全表示 or 全非表示しかできなかったですが，
iOS 13 からフィルタリングが可能になりました。

`showsPointsOfInterest` は
iOS 13 以降では，レファレンスの通り **Deprecated** になっています。
代わりに `pointOfInterestFilter` を使うように変わっています。

**参考：pointOfInterestFilter**
[https://developer.apple.com/documentation/mapkit/mkmapview/3143417-pointofinterestfilter?language=swift](https://developer.apple.com/documentation/mapkit/mkmapview/3143417-pointofinterestfilter?language=swift)

`pointOfInterestFilter` を使って，POI のフィルタリングができます。
前述した `MKPointOfInterestCategory` が出し分けとして利用されます。

以下 3種類の POI フィルタリングを紹介します。

都合によりスクショを貼っていますが，
マップの拡大・縮小を行なっても設定は維持されます。

### この POI だけ表示したい，ケース

POI が多いので **レストランやカフェ，フードマーケットだけ表示させたいなぁ〜**
そういう場合は，`MKPointOfInterestCategory` を格納する `Array` を用意して，
レストランとカフェ，フードマーケットをフィルタリングします。
`include` なので直感的でわかりやすいですね。

```swift
let category: [MKPointOfInterestCategory] = [.restaurant, .cafe, .foodMarket]
let filter = MKPointOfInterestFilter(including: category)
mapView.pointOfInterestFilter = filter
```

実行結果はこちらです。まだ多い気もするけどだいぶ絞れましたね。
よく見るとデフォルトだと表示されないレストランも表示されています。
これでレストラン探しが捗りそうな気がしますね！

|デフォルト|フィルタリング後|
|:--:|:--:|
|![IMG_0521.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/c2328275-6ee6-029c-3ad4-f6d7c10fd56d.png)|![IMG_0523.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/8b90bf87-8e8a-4326-9452-d613872bbf98.png)|

### この POI だけ表示したくない，ケース

**逆に飲食店を非表示にしたいなぁ〜** みたいなケースがあったとしましょう。

逆に `exclude` で該当のカテゴリのみ非表示にできます。

```swift
let category: [MKPointOfInterestCategory] = [.restaurant, .foodMarket, .cafe]
let filter = MKPointOfInterestFilter(excluding: category)
mapView.pointOfInterestFilter = filter
```

こちらも飲食店が非表示になって表示されるようになったお店などがあります。

|デフォルト|フィルタリング後|
|:--:|:--:|
|![IMG_0521.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/c2328275-6ee6-029c-3ad4-f6d7c10fd56d.png)|![IMG_0524.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/df73817c-ca50-a2ab-2c2a-4eabb99ccf34.png)|

### 全部表示しない，ケース

**該当のアノテーション以外の情報はいらないんや〜** 
という場合は `excludingAll` を使います。

```swift
let filter = MKPointOfInterestFilter.excludingAll
mapView.pointOfInterestFilter = filter
```

結果は下記です(左側)。
今までの `showsPointsOfInterest` が `false` のときよりも POI が消えてますね。
あまりないケースとは思います。

|[新]全て非表示|[旧]全て非表示|
|:--:|:--:|
|<img width="375" alt="IMG_1920.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/24533234-4112-8f66-37fd-4378e3a78cfe.png">|<img width="375" alt="IMG_1920.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/be727e4c-66ec-e9a1-cae3-f1c84954fe2e.png">|

## 具体的なユースケース(妄想)🤔

POIのフィルタリングはむやみに設定するものではなく，
アプリを利用するユーザのことを意識して行うべきです。
何個か妄想してみました。

### 例1
例えば，飲食店のアプリでお店の場所を示すのに住所と Map を掲載するとします。
お店に駐車場がないので周りのコインパーキングなどを利用してほしいとします。
この場合，駐車場のアイコンだけを表示するようにすると
ユーザは他のロケーションに目移りせずに駐車場の位置を意識できます。

```swift
let category: [MKPointOfInterestCategory] = [.parking]]
let filter = MKPointOfInterestFilter(including: category)
mapView.pointOfInterestFilter = filter
```

|フィルタリング前|フィルタリング後|
|:--:|:--:|
|![IMG_0526.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/a0aed82f-a153-6b9d-929b-082acfa854e1.png)|![IMG_0525.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/aaeee56d-174f-8cb8-80c2-16796b6c656a.png)|

### 例2
つぎは海外カンファレンスなどのイベント系アプリを考えてみます。
同じく会場の場所を示すのに住所と Map を掲載するとします。
世界各国のデベロッパが会場のある街(都市，国)に訪れます。
ユーザが気になるのは会場近くのレストラン，ホテル，駅なのではないでしょうか？
あるいは観光する人もいるかもしれません。

```swift
let category: [MKPointOfInterestCategory]
    = [.hotel,
       .restaurant, .cafe,
       .airport, .publicTransport,
       .beach, .amusementPark, .nightlife]
let filter = MKPointOfInterestFilter(including: category)
mapView.pointOfInterestFilter = filter
```

|フィルタリング前|フィルタリング後|
|:--:|:--:|
|![IMG_0527.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/29f13df6-af37-7b0f-4d33-9b66effc8349.png)|![IMG_0528.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/08269c5d-a7f1-0965-1986-1f7ea6b6175a.png)|

うーんイマイチかな。マップ拡大しないと恩恵少ない。
WWDC は会場から近いホテルがおすすめです。(私はフェアモント派)
VTA ライトレール使えるので駅近もいいですね。
現地では，体調第一でいろいろ決めると良いです。時差もありますしね。
来年も行きたいなぁ🤩

## SwiftUI でも使える

SwiftUI でも MapKit を使っているので，書き方はほぼ同じです。
SwiftUI での MapView の使い方の復習と
POIフィルタリングを記事にしようと思っていたのですが計画狂っちゃいました。

```swift:ContentView.swift
import SwiftUI
struct ContentView: View {
    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                // MapView Part
                MapView()
                    .frame(height: 300.0)
                // 省略
            }
        }
    }
}
```

UIViewRepresentable に準拠させます。
`makeUIView` 関数内で frame のコード書いても無視されるっぽくて
初期化する View で設定するとちゃんと意図通りに動くようです。

```swift:MapView.swift
import SwiftUI
import MapKit    // これが必須

struct MapView: UIViewRepresentable {

    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView(frame: .zero)
        return mapView
    }

    // Required
    func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
        let center = CLLocationCoordinate2DMake(緯度, 経度)
        let span = MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002)
        let region = MKCoordinateRegion(center: center, span: span)
        uiView.setRegion(region, animated: true)
        
        // POIフィルタリング(UIKitと使い方は変わらない)
        let category: [MKPointOfInterestCategory] = [.parking, .publicTransport]
        let filter = MKPointOfInterestFilter(including: category)
        uiView.pointOfInterestFilter = filter
 
        // アノテーションを表示
        let annotation = MKPointAnnotation()
        annotation.coordinate = center
        annotation.title = "アノテーションタイトル"
        uiView.addAnnotation(annotation)
    }
}
```

[Enjoy SwiftUI vol2](https://yumemi.connpass.com/event/151594/) でライブコーディングした
サンプルアプリのイベント詳細画面を更新してみました。
フィルタリングは `[.parking, .publicTransport]` にしています。
駐車場や駅の情報が確認できますね。

![RPReplay_Final1577104328.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/ee15bad4-8c7e-2b72-7967-7c47af902455.gif)

サンプルコードはこちらです。
[https://github.com/MilanistaDev/StudyGroupEventFetcherForSwiftUI](https://github.com/MilanistaDev/StudyGroupEventFetcherForSwiftUI)

connpass の API 叩いてリストに表示する部分の記事もよろしければご覧ください。
**【SwiftUI】外部APIを叩いて取得した結果をListに表示する**
[https://qiita.com/MilanistaDev/items/64dca8c9d5099a19529e](https://qiita.com/MilanistaDev/items/64dca8c9d5099a19529e)

## おわりに

今回は，iOS 13 の Mapkit に新しく追加された
POI のフィルタリングについて書きました。

頭の片隅に置いて，これからは Map を実装する際は，
Map 上の POI のことを少し意識してみてはいかがでしょうか？
ユーザの目線で考えることが大事です。

ご覧いただきありがとうございました！

今年も多くの方に支えられて楽しく1年過ごすことができました。
来年も引き続きアウトプット駆動で精進していこうと思っておリます！