Google Maps SDK for iOS の実戦テクニック(Swift 4対応)


先日、Google Maps SDK for iOSを使ったアプリをリリースしました。
このアプリで使用したGoogle Maps SDK for iOSの実戦テクニックを紹介します。

私が使ってみて良いと感じたものですので、Google Maps SDK for iOSの機能を網羅的に紹介する記事ではありません。
網羅的に知りたい方は公式サイトを確認ください。
https://cloud.google.com/maps-platform/

環境

swift4
xcode9.41
GoogleMaps 2.7.0

マーカーにふわっと表示されるアニメーションをつける

マーカーのアニメーションといえば、上から降ってくるものや、跳ねるものがありますが、
iOSの場合は、popというアニメーションのみ用意されています。
しかし、私がやりたかったのは、単純にフワッと表示するアニメーションです。
このアニメーションはSDKに用意されていないので自分で実装する必要があります。

実は、マーカーのクラスであるGMSMarkerはCALayerをスーパークラスとするGMSMarkerLayerのプロパティを持っています。ですので、このlayerプロパティにCoreAnimationで作成したアニメーションを仕込むことができます。

私が実際に使っている実装がこちら。

let marker = GMSMarker()
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = 0.0
opacityAnimation.toValue = 1.0
opacityAnimation.duration = 0.5
opacityAnimation.isRemovedOnCompletion = false
opacityAnimation.fillMode = kCAFillModeForwards
opacityAnimation.delegate = self
opacityAnimation.setValue(marker.layer, forKey: "marker_opacity1")
marker.layer.add(opacityAnimation, forKey: "marker_opacity2")

opacityが0.0から1.0に変化するアニメーションを作成して、マーカーのlayerにaddしています。
これでマーカーがマップにふわっと表示されます。UIViewなどへのアニメーションの実装と同じです。

ついでに、SDKで用意されているpopアニメーションの実装も記載しておきます。

let marker = GMSMarker()
marker.appearAnimation = .pop

iconとiconViewの使い分け

マーカーのiconプロパティはUIImageです。iconViewプロパティはUIViewです。
iconプロパティは画像を準備する必要がありますが、iconViewプロパティはその必要がなく
UIViewを実装して、UILabelをaddしたり、角丸にしたりできます。
ただし、iconViewはマーカーの数が多くなると、描画処理が重くなり表示に時間がかかってしまいます。

私の考える使い分けの判断は下記になります。

1. 大量に表示するmarkerはiconプロパティを使用
2. 動的にUIが変化するマーカーは数を限定し、iconViewプロパティを使用

大量に存在するマーカーのデザインを全て異なるものにするというUIにしてしまうと、画像Assetsが多くなりすぎてマーカー表示が遅くなったり、xcodeの挙動がおかしくなったりします。
かといって全てiconViewプロパティを使用すると、上記で述べたようにマーカー表示が遅くなります。
このことを考慮してUIを決める必要がありますので、注意が必要です。

実装も載せておきましょう。まずはiconプロパティを使用した実装です。

let marker = GMSMarker()
marker.icon = UIImage(named:"画像ファイル名")

Assetsの画像ファイルをセットしています。

次はiconViewプロパティを使用した実装です。

let marker = GMSMarker()
let label = UILabel(frame: CGRect(x:0.0, y:0.0, width:60.0, height:60.0))
label.text = "sample"
label.font = UIFont.systemFont(ofSize: 20.0)
label.textAlignment = .center
label.textColor = .white
let markerView = UIView(frame: CGRect(x:0.0, y:0.0, width:60.0, height:60.0))
markerView.layer.cornerRadius = 30.0
markerView.backgroundColor = .red
markerView.addSubview(label)
marker?.iconView = markerView

UILabelをUIViewにaddできますし、文字、背景色、viewの形も自由に設定できます。

マップにアングルをつける

マップにアングルをつけると見た目の良さが随分変わります。
アングルをつけたマップとそうでないマップを見比べてみましょう。

左がアングルなし、右がアングルありです。
  
アングルありの方がマップに奥行きがあり、かっこいいですよね?
ズームするとかっこよさの差が顕著になります。

左がアングルなし、右がアングルありです。
  
ねっ、アングルありの方がかっこいいでしょ?

実装は簡単で、GMSMapViewのインスタンスメソッドで傾きを指定するだけです。

let map = GMSMapView()
map.animate(toViewingAngle: 45)

マーカーのフラット化

マップにアングルをつけた場合に、マーカーをマップと同じ角度に傾けることをフラット化といいます。
画像で見比べてみましょう。

左がフラットなし、右がフラットありです。
  
マーカーの角度が違うのがわかると思います。フラットにしないとマーカーがマップ上に浮いたように見えて違和感を感じます。フラットにするとマップに沿ってマーカーが表示されるので違和感を感じません。
私は、基本的にフラットに表示させ、選択されたマーカーだけフラットなしで表示させました。
こうすることで、普段は違和感のない表示で、注目したい情報だけを目立たせることができます。

アプリではこんな表示になります。

「2」と表示されているマーカーはフラットにしないことで他のマーカーより目立つ存在になっています。

実装は次のようになります。

let marker = GMSMarker()
marker.isFlat = true

GMSMarkerのインスタンスのisFlatプロパティにtrueを指定するだけです。デフォルトはfalseですので、フラットにしたくないマーカーには何も指定する必要はありません。

マップの移動

マップの中心を移動することでユーザ体験をよりよくすることができます。
私のアプリでは、都内23区を選択して、マップを表示することができます。
その際に、選択した区をマップの中心に表示した方がユーザにとっては使いやすいはずです。

実装では、都内23区のどれかをユーザが選択するたびに、中心にしたい緯度経度を指定し、マップの中心地を変更しています。

var map = GMSMapView()
let camera = GMSCameraPosition.camera(withLatitude:35.6837193, longitude: 139.7602022, zoom:13.0)
map = GMSMapView.map(withFrame: CGRect.zero, camera: camera)

GMSCameraPositionのcameraメソッドに緯度経度を指定し、GMSMapViewのmapメソッドにセットします。

マップのズームインとズームアウト

マップのズームイン、ズームアウトはUIをダイナミックに変更でき、ユーザに与えるインパクトが大きいので是非取り入れることをお勧めします。
私のアプリでは、現在地から他のマーカへの経路を表示する際にズームインします。そして、戻るボタンタップでズームアウトして元のマップに戻ります。

ズームインした画像です。

では実装を見てみましょう。

var map = GMSMapView()
//皇居の緯度経度
let koukyo = CLLocationCoordinate2D(latitude:35.685175, longitude:139.7527995) 
//東京駅の緯度経度
let tokyo = CLLocationCoordinate2D(latitude:35.6837193, longitude:139.7602022)
let bounds = GMSCoordinateBounds(coordinate:koukyo, coordinate:tokyo)        
let vancouverCam = GMSCameraUpdate.fit(bounds, with: UIEdgeInsets(top: 80.0, left: 30.0, bottom: 200.0, right: 30.0))
map.animate(with: vancouverCam)

ズームイン以外にもやっていることがあります。まず、GMSCoordinateBoundsインスタンスでユーザの見える範囲に必ず入れるべき二点を指定します。上記例では、皇居と東京駅は必ず表示させるようにしています。
また、UIEdgeInsetsインスタンスではマップのpaddingを指定しています。paddingしたとこはマップへのアクションができなくなり、その代わりに別のオブジェクトを置くことができます。私のアプリの場合、現在値からの距離や、そこに行くための必要時間を表示するUIViewを置いています。
GMSCameraUpdateのfitメソッドでGMSCoordinateBoundsインスタンスとUIEdgeInsetsインスタンスをセットし、最後にマップインスタンスにGMSCameraUpdateインスタンスをセットします。
これで、アニメーション付きでズームインしてくれます。

次に、ズームアウトの実装です。

var map = GMSMapView()
let camera = GMSCameraPosition.camera(withLatitude:35.685175, longitude:139.7527995, zoom:13.0)
let vancouverCam = GMSCameraUpdate.setCamera(camera)
map.animate(with: vancouverCam)

私のアプリではzoom13を基本としているので、GMSCameraPositionのcameraメソッドに中心とする緯度経度とzoom値13.0を指定して、GMSCameraUpdateのsetCameraメソッドにGMSCameraPositionインスタンスを指定します。最後にマップインスタンスにGMSCameraUpdateインスタンスをセットします。
これで、アニメーション付きでズームアウトしてくれます。

マップにtableViewを重ねて表示

今回、私が一番こだわったUIです。
観光地などの紙パンフレットは観光名所の位置関係と詳細情報を一枚の紙で表現できています。それをアプリでも実現したかったのです。実装としては、マップの上に詳細情報を表示するtableViewを重ねました。

アプリの画像をみてみましょう。

左側がマップで、右側がtableViewです。

マップのviewにtableViewをaddSubviewして、tableViewのposisionを右にずらしているだけですが、マーカーの位置関係とマーカーの詳細情報を同時に把握することができます。

この実装の注意点として、iconViewプロパティをセットした大量のマーカー表示すると、tableViewをスクロールした時に表示がガタつきます。これを避けるために私はiconViewプロパティではなくiconプロパティを使用しました。

その他お役立ち情報

・ 同じ緯度経度のマーカーを複数同時にマップに表示すると、マーカーが点滅してしまい、他のUIの挙動をおかしくしていまします。私の場合はtableViewをスクロールした際のガタつきの原因の1つでした。

・ マップのスタイルをこだわりたい方はhttps://snazzymaps.com/ を参照ください。様々なスタイルのマップを見ることができます。また、気に入ったスタイルのjsonもDLできます。

・ ヒートマップやマーカーのクラスタリングをしたい方はhttps://github.com/googlemaps/google-maps-ios-utils を参照ください。ライブラリが公開されています。

私の作ったアプリはこちらです。実際に動きを確かめたい方はインストールしてみてください。
https://itunes.apple.com/jp/app/nursery/id1412333145?l=en&mt=8