[iOSアプリ開発]GoogleMaps SDK for iOS 逆引き辞書 

##はじめに

アプリ開発を行った際、GoogleMapsを使用しましたが記事が少なく時間がかかった為備忘録として記録


##目次
- インストール方法
 - GoogleMapsを表示
- マーカーを表示(クラスター化)
 - クラスタの画像を変更
 - クラスタタップ時の処理
 - マーカーの画像を変更
 - マーカーの吹き出しを表示
 - マーカーを消去
- マーカーを表示(単体表示)
- 現在地をマップに表示
 - 現在地に遷移
- 縮尺度によって処理を行う
- GoogleMapsの地図の種類を変更

以下更新予定

- ストリートビューを実装
- ナイトモード実装
- フィチャータイプ
- パディング
- ポリゴン
- Jsonファイルの読み取り




##環境
日付 :　2020年5月
MacOS : Catalina
Xcode Version : 11.41
cocoapods Version : 1.9.0.beta.3

##インストール方法
####step.1 新規プロジェクトを作成

プロジェクト名をGoogleMapsTest　とした

####step.2 ココアポッドをインストール(インストール済ならしなくてよい)
ターミナルに以下のコマンドを入力

```Akidon.terminal
sudo gem install cocoapods
```


```Akidon.terminal
pod setup
```


------memo------

```Akidon.terminal
pod --version
```
でバージョンの確認ができる

####step.4 Podfile作成
ターミナルを開きファイルまでのパスを通す

```Akidon.terminal
cd <ファイルのパス>
```
podfileを作成

```Akidon.terminal
pod init
```
podfileが作成されたので、
以下２つのコードを追加

```GoogleMapsTest.podfile
pod 'GoogleMaps'
pod 'GooglePlaces'
```
![スクリーンショット 2020-05-05 18.53.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/32ca7c6d-1b25-5291-3266-ac35891bfffd.png)

入力後以下のコードをターミナルで入力

```Akidon.terminal
pod install
```
一度Xcodeを終了させる。
finderから<プロジェクト名>.xcworkspaceを開く

こうなればOK
![スクリーンショット 2020-05-05 18.07.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/52a1b5dc-be82-34a6-ac66-b45a7a7e62da.png)

------memo------
これで簡単にパスを通すことができる
![スクリーンショット 2020-05-05 17.49.25（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/905c1019-a9d6-f79c-df3e-7ebc8bb3631d.png)

**cd <ファイルのパス>　の<>は入力しなくて良い**



###GoogleMapsを表示
####step.1 APIの設定
[ここからGet Startedを選択](https://developers.google.com/maps/documentation/ios-sdk/get-api-key)

メニューバー(ハンバーガーメニュー)からAPIとサービス -> ライブラリ -> Maps SDK for iOSを選択
APIを有効にする
**ここ書いてる記事少なくてつまづいた**

次に
APIとサービス -> ライブラリ -> 認証情報から
認証情報を作成をタップするとAPIキーが作成され、キー制限をタップする

アプリケーションの制限からiOSアプリを選択
プロジェクトのバンドルIDを入力する。

------memo------
バンドルIDの見方
<img width="1280" alt="スクリーンショット 2020-05-05 18.24.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/687131b5-b2cf-fccd-4054-532847e59ecd.png">

####step.2 マップ表示
XcodeのAppDelegate.swiftに以下のコードを追加

```AppDelegate.swift
import UIKit
import GoogleMaps  //追加
import GooglePlaces//追加

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    //ios12で動かす際に必須　場合に応じて入れてください
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        //GoogleMapsを表示させるために認証キーを取得
        GMSServices.provideAPIKey("<APIキー>")//追加
        GMSPlacesClient.provideAPIKey("<APIキー>")//追加
        return true
    }
    ...以下省略
}
```

ViewControllerで以下のコードを入力

```ViewController.swift
import UIKit
import GoogleMaps

class ViewController: UIViewController,GMSMapViewDelegate {
    var latitude: Double = 34.077875549971  //徳島大学の緯度
    var longitude: Double = 134.56156512254 //徳島大学の経度
  
    override func loadView() {
        super.loadView()
        let camera = GMSCameraPosition.camera(withLatitude: latitude,
                                              longitude: longitude,
                                              zoom: 6.0)
        let mapView = GMSMapView.map(withFrame: view.frame,
                                     camera: camera)
        view = mapView
    }
}

```
起動すると以下の画面になります。
<img width="375" alt="Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-05 at 18.51.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/44365680-c78c-2378-f29c-b1d27365b6b2.png">

------memo------
シュミレーターの言語設定を日本語にすることで、GoogleMapsを日本語表示にできる。

参考
https://blog.codecamp.jp/iphone-app-develope-original-googlemap


##マーカーを表示(クラスター化)
podfileに以下を追加しpod installする

```GoogleMapsTest.podfile
pod 'Google-Maps-iOS-Utils'
```
ViewControllerに以下のコードを記述
markerが4つ以上からクラスター化になる

```ViewController.swift
import UIKit
import GoogleMaps
import GooglePlaces
import GoogleMapsUtils

class ViewController: UIViewController,GMSMapViewDelegate, GMUClusterManagerDelegate, GMUClusterRendererDelegate {
    //[徳島大学,徳島県庁,地方裁判所,徳島駅]
    var latitude: [Double] = [34.077875549971,34.06583,34.0708562,34.0742]  //緯度
    var longitude: [Double] = [134.56156512254,134.55944,134.5571692,134.5511] //経度
    var zoom: Float = 8.0 //縮尺
    
    var clusterManager: GMUClusterManager!
  
    override func loadView() {
        super.loadView()
        let camera = GMSCameraPosition.camera(withLatitude: latitude[0],
                                              longitude: longitude[0],
                                              zoom: zoom)
        let mapView = GMSMapView.map(withFrame: view.frame,
                                     camera: camera)
        view = mapView
        
        //クラスタの画像について
        let iconGenerator: GMUDefaultClusterIconGenerator!
        iconGenerator = GMUDefaultClusterIconGenerator()
        let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
        let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
        renderer.delegate = self
        clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
        clusterManager.cluster()
        clusterManager.setDelegate(self, mapDelegate: self)
        refresh()
        
    }
    
    func refresh(){
        //クラスター全削除
        self.clusterManager.clearItems()
        
        for i in 0..<latitude.count{
            let position = CLLocationCoordinate2DMake(latitude[i], longitude[i])
            let item = POIItem(position: position)
            self.clusterManager.add(item)
        }
    }
}

/// Point of Interest Item which implements the GMUClusterItem protocol.
class POIItem: NSObject, GMUClusterItem {
    //緯度経度を保持
    var position: CLLocationCoordinate2D
    
    init(position: CLLocationCoordinate2D) {
        self.position = position
  }
}
```

<img width="375" alt="Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-06 at 12.17.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/b8014712-3810-db46-0dc4-478ea81a67dd.png">

###クラスタの画像を変更


```ViewController.swift
override func loadView() {
    super.loadView()
        //--追加--
    //marker数[5, 10, 15, 20, 25]の順にimagesの画像が適応
    let images: [UIImage] = [UIImage(named: "Assets内の画像名")!, 
    UIImage(named: "")!, UIImage(named: "")!, UIImage(named: "")!,UIImage(named: "")!]
    iconGenerator = GMUDefaultClusterIconGenerator(buckets: [5, 10, 15, 20, 25], backgroundImages: images)
}
```
------memo------
右クリックからAddFilesを選び、画像を選択してAddする
<img width="320" alt="スクリーンショット 2020-05-06 12.03.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/efda27b6-5edc-fb44-0aa6-65facf6acfea.png">
そして、Assets内にドラッグアンドドロップする事で画像でまとめることができる
<img width="1280" alt="スクリーンショット 2020-05-06 12.32.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/c52d8f40-23bf-8f09-d56c-61eb28b832a0.png">


###クラスタがタップされた時
今回はクラスタがタップされた時、ズームするようにした。

```ViewController.swift
class ViewController: UIViewController,GMSMapViewDelegate, GMUClusterManagerDelegate, GMUClusterRendererDelegate {
    var mapView: GMSMapView!  //追加
    
    override func viewDidLoad(){
        super.viewDidLoad()
        self.mapView = mapView//追加
    }

    //クラスタをタップされた時　
    func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool{  //追加
        let newCamera = GMSCameraPosition.camera(withTarget: cluster.position, zoom: mapView.camera.zoom + 1)
        let update = GMSCameraUpdate.setCamera(newCamera)
        mapView.moveCamera(update)
        return false
    }
}
```
###マーカーの画像を変更

```ViewController.swift
func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker){
    //マーカーの位置を変更
    marker.groundAnchor = CGPoint(x: 0.5, y: 1)
    //クラスタ最下層の画像を変更
    if  let markerData = (marker.userData as? POIItem) {
       marker.icon = UIImage(named: "camera")
    }
}
```
<img width="375" alt="Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-06 at 12.45.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/efe2be0c-4c28-7c5a-73e5-2fda6709f3db.png">

###マーカーの吹き出しを表示する

新しいViewControllerとxibファイルを作成

名前は
InfoViewController.swift
InfoView.xib
とした。

xibファイルのサイズはwidth:300 height:200にした
<img width="1280" alt="スクリーンショット 2020-05-06 17.57.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/8f9b9901-ea90-fdb5-961c-5adbc19856d9.png">
吹き出し画面はこのような感じにします。

```InfoViewController.swift

import UIKit
class InfoViewController: UIView {
    var view : UIView!
    
    @IBOutlet weak var label1: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    func loadView() -> InfoViewController{
        let seismographInfoView = Bundle.main.loadNibNamed("InfoView", owner: self, options: nil)?[0] as! InfoViewController
        return seismographInfoView
    }
    func infoViewText(text1: String){
        label1.text =  text1
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
```
以下ViewControllerのコード全文です。
見辛くてすみません

```ViewController.swift

import UIKit
import GoogleMaps
import GooglePlaces
import GoogleMapsUtils

class ViewController: UIViewController,GMSMapViewDelegate, GMUClusterManagerDelegate, GMUClusterRendererDelegate {
    //[徳島大学,徳島県庁,地方裁判所,徳島駅]
    var latitudes: [Double] = [34.077875549971,34.06583,34.0708562,34.0742]  //緯度
    var longitudes: [Double] = [134.56156512254,134.55944,134.5571692,134.5511] //経度
    var texts:[String] = ["徳島大学","徳島県庁","地方裁判所","徳島駅"]
    var zoom: Float = 13.0 //縮尺
    
    var mapView: GMSMapView!
    var clusterManager: GMUClusterManager!
    var marker: GMSMarker?                       //追加
    var infoViewController : InfoViewController? //追加
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let camera = GMSCameraPosition.camera(withLatitude: latitudes[0],
                                              longitude: longitudes[0],
                                              zoom: zoom)
        let mapView = GMSMapView.map(withFrame: view.frame,
                                     camera: camera)
        self.view = mapView
        self.mapView = mapView
        mapView.delegate = self
        //クラスタの画像について
        let iconGenerator: GMUDefaultClusterIconGenerator!
        iconGenerator = GMUDefaultClusterIconGenerator()
        let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
        let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
        renderer.delegate = self
        clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
        clusterManager.cluster()
        clusterManager.setDelegate(self, mapDelegate: self)
        refresh()
        self.infoViewController = InfoViewController().loadView()  //追加
        
    }
    
    func refresh(){
        //クラスター全削除
        self.clusterManager.clearItems()
        
        for i in 0..<latitudes.count{
            let position = CLLocationCoordinate2DMake(latitudes[i], longitudes[i])
            let item = POIItem(position: position)
            item.text = texts[i]              //追加
            self.clusterManager.add(item)
            
        }
    }

    
    func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker){
        //マーカーの位置を変更
        marker.groundAnchor = CGPoint(x: 0.5, y: 1)
        //クラスタ最下層の画像を変更
        if  (marker.userData as? POIItem) != nil {
            marker.icon = UIImage(named: "camera")
        }
    }

    //クラスタをタップされた時
    func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool{
        let newCamera = GMSCameraPosition.camera(withTarget: cluster.position, zoom: mapView.camera.zoom + 1)
        let update = GMSCameraUpdate.setCamera(newCamera)
        mapView.moveCamera(update)
        return false
    }
    
//  -------追加-------
    //クラスタかmarkerをタップする時最初に呼ばれる
    func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
        self.marker = marker
        //markerをタップしたとき
        guard let markerData = (marker.userData as? POIItem),
              let infoViewController = infoViewController else {
            return false
        }
        self.marker = marker
        //透過度
        infoViewController.layer.backgroundColor = UIColor(white: 1, alpha: 0.85).cgColor
        //吹き出しの角を丸くする
        infoViewController.layer.cornerRadius = 8
        //吹き出しをピンの真上に出す
        infoViewController.center = mapView.projection.point(for: marker.position)
        infoViewController.center.y -= 140
        infoViewController.infoViewText(text1: markerData.text)
        mapView.addSubview(infoViewController)
        return false
     }
    
//   -------追加-------
    //画面変化時毎回呼ばれる
    func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
        guard let marker = marker,
              let infoViewController = infoViewController else{
            return
        }
        infoViewController.center = mapView.projection.point(for: marker.position)
        infoViewController.center.y -= 140
    }
//  -------追加-------
    //吹き出しを消した時呼ばれる
    func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
        infoViewController?.removeFromSuperview()
    }
}
/// Point of Interest Item which implements the GMUClusterItem protocol.
class POIItem: NSObject, GMUClusterItem {
    //緯度経度を保持
    var position: CLLocationCoordinate2D
    var text: String = ""                   //追加
    
    init(position: CLLocationCoordinate2D) {
        self.position = position
  }
}

```


------memo------
新しいViewControllerとxibファイルの作り方はNewFileより
<img width="385" alt="スクリーンショット 2020-05-06 17.51.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/eedf78df-9f0b-471f-609e-36c41670d93a.png">

<img width="1280" alt="スクリーンショット 2020-05-06 17.51.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/75a4a6a7-a570-1e40-e898-8dacfc0c7359.png">

ViewControllerならCocoaTouchClass,xibファイルはViewを選択する



xibファイルのサイズ変更はFreeformにすることで変更可能

<img width="1280" alt="スクリーンショット 2020-05-06 17.45.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/8318db9e-0656-28af-e2b9-318adc345304.png">
<img width="221" alt="スクリーンショット 2020-05-06 17.44.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/22eca61e-032d-f5e0-5fac-43f97e5e8bf0.png">

**guard**を使うことでオプショナル記号(!,?)を無くし美しいコードになる
またエラーが発見しやすい場合もしばしば。。。

参考
https://qiita.com/akidon0000/items/bf67f052179c1f704699

###マーカーを消去
上のコードで記述済なのでそちらを見てちょ

```ViewController.swift
self.clusterManager.clearItems()
```




##マーカーを表示(単体表示)
https://qiita.com/akidon0000/items/bf67f052179c1f704699
このサイトを参照してください
昔に書いた記事なので稚拙なコードですが動くと思います
自分的には縮小した時の見やすさからクラスタ化がおすすめです。

##現在地をマップに表示
Info.plistを編集します
今回はアプリ使用中のみ位置情報を取得するため、 
Privacy - Location When In Use Usage Description を記述しました

常に位置情報を取得したい場合は 
Privacy - Location Always and When In Use Usage Description を使用します

<img width="1280" alt="スクリーンショット 2020-05-06 21.19.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/bf195428-fc1e-c6aa-1278-817d97bdb7e3.png">

<img width="450" alt="スクリーンショット 2020-05-06 21.23.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/857ca985-f489-d293-0a80-dc3e5f99c9d5.png">

次にViewControllerに以下のコードを入力

```ViewController.swift
    var locationManager = CLLocationManager() // 追記

    override func viewDidLoad() {
        requestLoacion() // 追記
        mapView.isMyLocationEnabled = true // 追記
    }

    // 以下、追記
    private func requestLoacion() {
        // ユーザにアプリ使用中のみ位置情報取得の許可を求めるダイアログを表示
        locationManager.requestWhenInUseAuthorization()
        // 常に取得したい場合はこちら↓
        // locationManager.requestAlwaysAuthorization()
    }
```
ビルドしてみると
<img width="375" alt="Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-06 at 21.43.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/e5faee09-543b-d23e-da54-e2f60d68d621.png">
通知が来るので承諾

シュミレータは位置情報を変更することができるので
今回は徳島中央公園に緯度経度を設定した
緯度:34.0755
経度:134.553
![スクリーンショット 2020-05-06 21.52.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/b6d98e34-a8f0-8de8-c073-54c9118678ba.png)


------memo------
日本の緯度経度は
緯度値を +にすると北（上）へ、
経度値を +にすると東（右）へ移動する

参考
https://qiita.com/nwatabou/items/38f4240582d70a4d84a8

###現在地に遷移
GoogleMapsの機能で既にあるため、それを利用します。

```ViewController.swift
  override func viewDidLoad() {
     super.viewDidLoad()
     mapView.settings.myLocationButton = true //追加
  }
```
<img width="375" alt="Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-06 at 22.05.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517291/6fa3c282-4a17-1f91-6d26-3bc3d44e8a95.png">

下にタブバーなどがある場合はマップをpaddingすることで解消できる

```ViewController.swift
  override func viewDidLoad() {
     super.viewDidLoad()
     mapView.padding = UIEdgeInsets(top: 55, left: 0, bottom: 49, right: 0) //追加
     mapView.settings.myLocationButton = true 
  }
```

##縮尺によって表示を変更
確認はしてないですが、以下のコードでいけると思います。

```ViewController.swift
var markers:[GMUClusterItem] = []  //追加

func refresh(){
        //クラスター全削除
     self.clusterManager.clearItems()

     for i in 0..<latitudes.count{
         let position = CLLocationCoordinate2DMake(latitudes[i], longitudes[i])
         let item = POIItem(position: position)
         item.text = texts[i]              
         self.markers.append(item)  //追加
         self.clusterManager.add(item)
     }
}

func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
     let mapView = self.view as! GMSMapView
     // 配列を取り出す
     for(marker) in markers{
         //GoogleMapsの拡大縮小値を判断
         if 9 <= mapView.camera.zoom{
             //GoogleMapsの画面表示領域内に含まれるか
             if(mapView.projection.contains(marker.position)) {
                  // 含まれる場合
             } else {
                  // 含まれない場合
             }
         }else{
             self.mapView.clear()
         }
     }
}
```

##GoogleMapsの種類を変更
4種類のマップが存在する

| Swift     |       地図のタイプ               |
|:----------|:------------------------------:|
| normal    | 通常の地図                       | 
| satellite | 航空写真                        | 
| hybrid    | 通常の地図＋航空写真のハイブリッド   |    
| terrain   | 地形や樹木などの地形的特徴を持つ地図 | 

記述方法は以下の通りです。

```ViewController.swift
mapView.mapType = .satellite
```

##ストリートビューを実装
更新予定

##ナイトモード実装
更新予定

##フィチャータイプ
更新予定

##パディング
更新予定

##ポリゴン
更新予定

##Jsonファイルの読み取り
SwiftyJsonを使用します
####step.1 swiftyJson追加
podfileに

```GoogleMapsTest.podfile
pod "SwiftyJSON"
```
を追加

------memo------
Jsonファイルとは



メモ(備忘録)

```
//画面変化時毎回呼ばれる
    func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
        guard let marker = marker else{
            return
        }
    }
    
    //吹き出しをmarkerの上部に表示
    func infoViewCenter(_position:CLLocationCoordinate2D){
    }

//    //吹き出しを消した時呼ばれる
    func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
    }
```

更新予定

参考
https://blog.codecamp.jp/programming-swift-json