【Swift】Map Kit Viewの使い方。ピンの移動をアニメーションさせてリアル感を出す。(Swift 2.1、XCode 7.2)
ピンのアニメーション
アニメーションを設定せずに地図にピンを刺したり、ソースコードからピンの座標を変えると指定した座標にいきなり現れる。もう少しピンにリアル感を出したいと思うことがある。そこで本記事ではピンの移動にアニメーションを使う方法を説明する。
以降の手順は「Map Kit Viewの使い方」の続きから行うので、実装を試してみる人は先に読んでおくことをお勧めする。
ピンを刺すときのアニメーション
デバイス画面にLong Press Gesture Recognizerを配置する(下図赤矢印)。紫枠のアシスタントエディタボタンを押してViewController.swiftを開く。Ctrlキーを押しながら黄緑枠のジェスチャーリコグナイザーをドラッグ&ドロップでソースコードまで運んで吹き出しの設定画面を表示させる。
Connectionに「Action」、Nameに「pressMap」、Typeに「UILongPressGestureRecognizer」を設定しConnectボタンを押す。これで地図を長押ししたときのイベントをソースコードで受けれるようになった。
ViewController.swiftを以下のコードに変更する。animatesDropプロパティをtrueにすることでピンを刺すときにピンが上から降ってくるようになる。その他の作りは今までと同じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
// // ViewController.swift // import UIKit import MapKit class ViewController: UIViewController,MKMapViewDelegate { @IBOutlet weak var testMapView: MKMapView! var pinView:MKPinAnnotationView! //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() //中心座標 let center = CLLocationCoordinate2DMake(35.0, 140.0) //表示範囲 let span = MKCoordinateSpanMake(1.0, 1.0) //中心座標と表示範囲をマップに登録する。 let region = MKCoordinateRegionMake(center, span) testMapView.setRegion(region, animated:true) //デリゲート先に自分を設定する。 testMapView.delegate = self } //マップビュー長押し時の呼び出しメソッド @IBAction func pressMap(sender: UILongPressGestureRecognizer) { if (sender.state == UIGestureRecognizerState.Ended){ //マップビュー内のタップした位置を取得する。 let location:CGPoint = sender.locationInView(testMapView) //タップした位置を緯度、経度の座標に変換する。 let mapPoint:CLLocationCoordinate2D = testMapView.convertPoint(location, toCoordinateFromView: testMapView) let annotation = MKPointAnnotation() //ピンを作成してマップビューに登録する。 annotation.coordinate = CLLocationCoordinate2DMake(mapPoint.latitude, mapPoint.longitude) annotation.title = "ピン" testMapView.addAnnotation(annotation) } } //アノテーションビューを返すメソッド func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? { //アノテーションビューを作成する。 pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "testPin") //吹き出しを表示可能にする。 pinView.canShowCallout = true //ピンのアニメーションをONにする。 pinView.animatesDrop = true return pinView } } |
以下は実際のプレイ動画
ちなみに、上記のコードに以下のコードを追加すると、ピンを刺すのに失敗したようなアニメーションになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//アノテーションビューの追加終了時の呼び出しメソッド func mapView(mapView: MKMapView, didAddAnnotationViews views: [MKAnnotationView]) { //アニメーションで90度回転させる UIView.animateWithDuration(1.0, animations: { () -> Void in self.pinView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI/2.0)) }, completion: { (finished: Bool) in //回転が終わったらピンの画像を変える。 self.pinView.image = UIImage(named: "pin_img.png") } ) } |
以下は実際のプレイ動画
ピンを移動するときのアニメーション
刺したピンをドラッグ&ドロップで別の場所に移動するときは、以下の動画のようにピンを抜いて刺しなおすようなアニメーションになる。これは標準の仕様。
一方、ソースコードでピンを移動するにはcoordinateプロパティの値を変更すればいいが、そのまま座標を変更すると移動先の座標に瞬間移動する。
以下のソースコードのようにアニメーションを利用すれば、移動する雰囲気を演出できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
// // ViewController.swift // import UIKit import MapKit class ViewController: UIViewController,MKMapViewDelegate { @IBOutlet weak var testMapView: MKMapView! var pinView:MKPinAnnotationView! var annotation:MKPointAnnotation! //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() //中心座標 let center = CLLocationCoordinate2DMake(35.0, 140.0) //表示範囲 let span = MKCoordinateSpanMake(1.0, 1.0) //中心座標と表示範囲をマップに登録する。 let region = MKCoordinateRegionMake(center, span) testMapView.setRegion(region, animated:true) //デリゲート先に自分を設定する。 testMapView.delegate = self } //マップビュー長押し時の呼び出しメソッド @IBAction func pressMap(sender: UILongPressGestureRecognizer) { if (sender.state == UIGestureRecognizerState.Ended){ //マップビュー内のタップした位置を取得する。 let location:CGPoint = sender.locationInView(testMapView) //タップした位置を緯度、経度の座標に変換する。 let mapPoint:CLLocationCoordinate2D = testMapView.convertPoint(location, toCoordinateFromView: testMapView) if (annotation == nil) { //ピンが無いときは作成してマップビューに登録する。 annotation = MKPointAnnotation() annotation.coordinate = CLLocationCoordinate2DMake(mapPoint.latitude, mapPoint.longitude) annotation.title = "ピン" testMapView.addAnnotation(annotation) } else { //長押しした座標までアニメーションを使って移動する。 UIView.animateWithDuration(1.0, animations: { () -> Void in self.annotation.coordinate = CLLocationCoordinate2DMake(mapPoint.latitude, mapPoint.longitude) }, completion:nil) } } } //アノテーションビューを返すメソッド func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? { //アノテーションビューを作成する。 pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "testPin") //吹き出しを表示可能にする。 pinView.canShowCallout = true //ピンのアニメーションをONにする。 pinView.animatesDrop = true return pinView } } |
以下は実際のプレイ動画
測地線に沿ってピンを動かす
測地線とは、2地点の最短距離を表す線のことをいう。飛行機を使って地球を移動するときは、測地線に従って移動すると最短時間で目的地に到着できるというイメージだ。
例えば、下図のような緯線と経線が垂直に交わる世界地図で、日本とロンドンの最短距離を表す線はどのようになるか。見た目で考えると日本からロンドンに向かって引いた黄色い直線が最短距離になるように感じるが、実際は紫線のような曲線になる。
なぜなら、地球は球体であり、下図は球体を引き延ばして表現された地図のためである。テレビや映画でたまに見る世界地図の上をジャンボジェットの模型が曲線上に移動しているのは、飛行機が地球の上空を飛んでいる雰囲気を演出しているのではなく、測地線に沿って移動させた結果、そのように見えているのだ。
MKGeodesicPolyline(以下、GEOライン)を使うと、2地点の座標だけで測地線を地図上に描くことができる。では、日本の羽田空港とロンドンのヒースロー空港との間に測地線を引き、測地線の上をピンが移動するアニメーションを作ってみよう。
2点間の直線上を移動させる場合は始点と終点さえ把握できればいいが、曲線上を移動させる場合は曲線に沿って座標がどのように変化するかを知る必要がある。
Geoラインは始点から終点までの座標を順番に保存した凄い要素数の配列を持っているので、その配列の順番にピンの座標を更新していくことでピンを曲線に沿って移動させる。
具体的には、ViewController.swfitを以下のコードに変更する。「ピンを移動するメソッド」を定周期で呼び出し、座標の配列を順番に取り出してピンの座標に設定している。
for文でループさせるとメインスレッドの処理を離さずにマップが固まってしまうので、performSelectorメソッドを用いてメインスレッドの処理を逐次手放しながら定周期でメソッドを呼び出すようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
// // ViewController.swift // import UIKit import MapKit class ViewController: UIViewController,MKMapViewDelegate { @IBOutlet weak var testMapView: MKMapView! var pinView:MKPinAnnotationView! var annotation:MKPointAnnotation! var geoLine: MKGeodesicPolyline! var pinPosition:Int = 0 //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() //日本とロンドンの空港の座標 var coordinate = [CLLocationCoordinate2D(latitude: 35.549393, longitude: 139.779839), CLLocationCoordinate2D(latitude: 51.470022, longitude: 0.454296)] //測地線をマップに追加する。 geoLine = MKGeodesicPolyline(coordinates: &coordinate, count: 2) testMapView.addOverlay(geoLine) print(geoLine.pointCount) //中心座標 let center = CLLocationCoordinate2DMake(35.0, 139.0) //表示範囲 let span = MKCoordinateSpanMake(5.0, 5.0) //中心座標と表示範囲をマップに登録する。 let region = MKCoordinateRegionMake(center, span) testMapView.setRegion(region, animated:true) //ピンをマップビューに追加する。 annotation = MKPointAnnotation() testMapView.addAnnotation(annotation) //デリゲート先に自分を設定する。 testMapView.delegate = self //ピンの移動を開始する。 movePin() } //描画メソッド実行時の呼び出しメソッド func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer { let renderer = MKPolylineRenderer(overlay: overlay) renderer.lineWidth = 3.0 renderer.strokeColor = UIColor.blueColor() return renderer } //ピンを移動するメソッド func movePin() { //終点に到着したら移動を終了する。 if pinPosition > geoLine.pointCount { return } //測地線上の座標の配列を取得する。 let geoPoints = geoLine.points() //移動先の座標をピンに設定する。 annotation.coordinate = MKCoordinateForMapPoint(geoPoints[pinPosition]) pinPosition += 20 //メソッドを再度呼び出す。 performSelector("movePin", withObject:nil, afterDelay:0.05) } } |
以下は実際のプレイ動画