ジェスチャーを処理
利用開始の例からわかるように、マップ ビューでは、ピンチやダブルタップでズームインなど、一般的なすべてのマップジェスチャがデフォルトでサポートされています。 次の表に、使用可能なジェスチャと、マップ上の対応するデフォルトの動作の概要を示します。
| 1 本の指で画面をタップします。 このジェスチャには、定義済みのマップアクションがありません。 |
| マップを一定の量だけ拡大 するには、 1 本の指で画面を 2 回タップします。 |
| 1 本の指を押して画面に移動します。 このジェスチャには、定義済みのマップアクションがありません。 |
| マップを移動 するには、 1 本の指を画面に合わせて押し続け、任意の方向に動かします。 指を持ち上げると、マップが少し勢いをつけて動き続けます。 |
| マップを傾けるに は、 2 本の指を画面に合わせて押し続け、垂直に動かします。 他の方向の動作は事前に定義されていません。 |
| 一定の量だけズームアウトするに は、 2 本の指で画面をタップします。 |
| 連続的にズームインまたはズームアウトする には、 2 本の指を押したまま画面に移動し、その間の距離を増減します。 マップを連続的に回転させるに は、 2 本の指を画面に合わせて押し続け、両方を回転するか、いずれかを動かして、マップの角度を変更します。 |
HERE SDK for iOS では、次のジェスチャがサポートされています。
- タップ :
TapDelegate
- ダブルタップ :
DoubleTapDelegate
- 長押し :
LongPressDelegate
- パン :
PanDelegate
- 2 本指でパン :
TwoFingerPanDelegate
- 2 本指でタップ :
TwoFingerTapDelegate
- ピンチ回転 :
PinchRotateDelegate
各デリゲートには、ユーザーが検出可能なアクション(特定のジェスチャの開始時や終了時など)を実行するたびに通知する専用のコールバックがあります。 通常は、ジェスチャが検出された後に、マップ マーカーを長押しした後に配置するなど、特定の動作をアプリケーションに追加します。
同じジェスチャには、一度に 1つのデリゲートのみを設定できます。
マップ操作を制御
デリゲートを設定しても、ジェスチャのデフォルトのマップ動作には影響がありません。 個別に制御できます。 既定では、マップをダブルタップしたときの拡大など、すべての標準的な動作が有効になっています。
たとえば、次のように、ダブルタップ ( ズームイン ) および 2 本指タップ ( ズームアウト ) のデフォルトのマップジェスチャの動作を無効にできます。
mapView.gestures.disableDefaultAction(forGesture: .doubleTap)
mapView.gestures.disableDefaultAction(forGesture: .twoFingerTap)
既定のマップアクションを無効にしても、ジェスチャイベントをリッスンできます。 これは、例えば独自のズーム動作を実装するために、ジェスチャのデフォルトのアクションをオフにする場合に役立ちます。 タップと長押しを除くすべてのジェスチャで、デフォルトのマップアクションが提供されます。 詳細については、上の概要を参照してください。
デフォルトのマップジェスチャの動作を戻すには、次のように呼び出します。
mapView.gestures.enableDefaultAction(forGesture: .doubleTap)
mapView.gestures.enableDefaultAction(forGesture: .twoFingerTap)
ジェスチャーデリゲートを添付
ジェスチャデリゲートをマップ ビューに添付する方法の例を見てみましょう。 デリゲートを設定するとすぐに、そのジェスチャに関連するすべてのイベントが専用のコールバック(onTap()
TapDelegate
の場合)経由で受信されます。 このプロトコルに準拠するクラスは、デリゲートとして機能します。
func onTap(origin: Point2D) {
let geoCoordinates = mapView.viewToGeoCoordinates(viewCoordinates: origin)
print("Tap at: \(String(describing: geoCoordinates))")
}
最後に、タップタッチイベントについてクラスに通知をもらい、リスニングを開始することをマップ ビューに伝えます。
mapView.gestures.tapDelegate = self
デリゲートを設定するとすぐに、ジェスチャが検出されたことを知らせる通知が届きます。
touchPoint
は 、ジェスチャが発生したMapView
座標を指定します。 mapView.viewToGeoCoordinates(viewCoordinates: origin)
を呼び出すことで 、ピクセルを地理座標に変換できます ( 上図を参照 ) 。
同様に、リスニングを停止するには、以下をコールします。
mapView.gestures.tapDelegate = nil
連続したジェスチャ ( 長押し、ピンチ、パン、 2 本の指のパンなど ) の場合 、ジェスチャの状態begin
はジェスチャが検出されたことを示します。 指がディスプレイに触れている間は、end
状態が指が離されたことを示すか、cancel
状態がジェスチャ検出がキャンセルされたことを示すまで、update
状態を受け取ることがあります。
func onLongPress(state: GestureState, origin: Point2D) {
if (state == .begin) {
let geoCoordinates = mapView.viewToGeoCoordinates(viewCoordinates: origin)
print("LongPress detected at: \(String(describing: geoCoordinates))")
}
if (state == .update) {
let geoCoordinates = mapView.viewToGeoCoordinates(viewCoordinates: origin)
print("LongPress update at: \(String(describing: geoCoordinates))")
}
if (state == .end) {
let geoCoordinates = mapView.viewToGeoCoordinates(viewCoordinates: origin)
print("LongPress finger lifted at: \(String(describing: geoCoordinates))")
}
if (state == .cancel) {
print("Map view lost focus. Maybe a modal dialog is shown or the app is sent to background.")
}
}
たとえば、長押しのイベントが検出された後も、ユーザーが指を画面に置いたままにしたり、移動したりすることがあります。 ただし begin
、長押しジェスチャが検出された時点でマークされるのはイベントのみです。
マップ マーカーをマップ上に配置するには、長押しのジェスチャが便利です。 この例については、 アプリの検索例を参照してください。
連続していないジェスチャ ( タップ、ダブルタップ、 2 本の指タップなど ) の GestureState
場合は、ジェスチャを処理する必要はありません。
注
パンジェスチャを使用すると、マップをスワイプして移動することがあります。これにより、ジェスチャはすでに終了しており、すべての指が持ち上げられているにもかかわらず、移動が継続されます。 マップの移動がいつ終了したかを検出するには、MapIdleListener
を使用します。 インスタンスHereMap
に追加でき、インスタンスMapView
から取得できます。
GitHub では 、ジェスチャ のサンプルアプリの一部として、コードスニペットおよびその他の使用例を利用できます。
チュートリアル - マップアクションをカスタマイズ
すでに確認したように、既定ではダブルタップジェスチャによって、都市レベルから通りのレベルに近い場所など、マップが個別のステップでズームされます。 このようなデフォルトのマップジェスチャアクションを無効にして独自のビヘイビアーを実装することも、既存のビヘイビアーに必要なアクションを追加することもできます。
必要に応じて、プラットフォームジェスチャアクションと HERE SDK ジェスチャの検出を組み合わせることもできます。 HERE SDK では、利便性のために、一般的なマップジェスチャに重点を置いているため、すべての種類の低レベルのジェスチャイベントを提供するわけではありません。 より詳細な制御が必要な場合は、 HERE SDK で利用できるジェスチャ処理とネイティブのジェスチャ検出をいつでも組み合わせることができます。
このチュートリアルで は、カスタムズームアニメーションを有効にする方法を示します。 ユーザー がダブルタップまたは 2 本の指でタップした後、マップが徐々に拡大または縮小されます。
カスタムズーム動作を追加
アニメーションから始めましょう。 このために、 Apple の CADisplayLink を使用して、アニメーションをディスプレイの更新レートと同期させることができます。
便宜上、GestureMapAnimator
という名前の新しいクラスを作成します。このクラスは、ジェスチャに関連するすべてのアニメーションを処理する必要があります。 マップをカメラでズームする必要があるため、マップのMapCamera
インスタンスへの参照が必要です。
GestureMapAnimator
の内側には、ズームアニメーションへの参照があります。 CADisplayLink
インスタンスの初期化に時間がかかることがあります。 対応するループメソッド animatorLoopZoom
も宣言します( 後で説明します ) 。
private lazy var displayLinkZoom = CADisplayLink(target: self,
selector: #selector(animatorLoopZoom))
これで、対応するインスタンスCADisplayLink
の進行中のすべてのアニメーションを一時停止するメソッド stopAnimations()
をすでに実装できました。
func stopAnimations() {
displayLinkZoom.isPaused = true
}
既定では、マップは、途中のステップを使わずに、指がマップに触れる位置で、 1 つの個別のステップでズームイン / ズームアウトします。
必要なジェスチャイベントをフックしてみましょう。
mapView.gestures.disableDefaultAction(forGesture: .doubleTap)
mapView.gestures.disableDefaultAction(forGesture: .twoFingerTap)
func onDoubleTap(origin: Point2D) {
gestureMapAnimator.zoomIn(origin)
}
func onTwoFingerTap(origin: Point2D) {
gestureMapAnimator.zoomOut(origin)
}
ここにはマジックはありません。2 つのジェスチャイベントをリッスンするだけです。 既定のズーム動作が事前に無効になっていることを確認する必要があります。 上記のzoomIn()
メソッドおよびzoomOut()
メソッドは、以下に示すGestureMapAnimator
の新しいメソッドにつながります。
また 、ズームアニメーションの開始値startZoomVelocity
と 現在の値zoomVelocity
が時間の経過とともに減少する量zoomDelta
を決定するには、 2 つの定数を定義する必要があります。 startZoomAnimation()
このメソッドは、実行ループの開始を処理します。
private let startZoomVelocity: Double = 0.1
private let zoomDelta: Double = 0.005
func zoomIn(_ origin: Point2D) {
zoomOrigin = origin
isZoomIn = true
startZoomAnimation()
}
func zoomOut(_ origin: Point2D) {
zoomOrigin = origin
isZoomIn = false
startZoomAnimation()
}
private func startZoomAnimation() {
stopAnimations()
zoomVelocity = startZoomVelocity
displayLinkZoom.isPaused = false
displayLinkZoom.add(to: .current, forMode: .common)
}
zoomOrigin
を保存して、拡大または縮小するマップ上の場所を確認します。
フラグisZoomIn
を使用すると、 1 つの方法でズームコードを処理できます。 アニメーションを開始する には、displayLinkZoom
ループを一時停止解除し、対応するセレクタメソッドanimatorLoopZoom()
でズーム値を更新します。 このメソッドは、前に確認した stopAnimations()
メソッドを呼び出して、 displayLinkZoom
が再び一時停止するまで定期的に呼び出されます。
@objc private func animatorLoopZoom() {
var zoomFactor: Double = 1
zoomFactor = isZoomIn ? zoomFactor + zoomVelocity : zoomFactor - zoomVelocity;
camera.zoomBy(zoomFactor, around: zoomOrigin)
zoomVelocity = zoomVelocity - zoomDelta
if (zoomVelocity <= 0) {
stopAnimations()
}
}
上記の単純なアルゴリズムは、0.1 から 0 に近い値zoomVelocity
を補間します。zoomVelocity
は、マップを徐々にズームするために使用できるアニメーション値です。開始値は startZoomVelocity
0.1 で定義されています。 zoomVelocity
ズーム倍率を設定するには、引数としてを使用します。 zoomVelocity
が、ゆっくりと 0 に達すると、ズームステップのサイズが小さくなります。
上の図では、保存したタッチの起点zoomOrigin
を使用して、指が画面に触れたポイントにズームインします。 ズームアウトする場合は、 2 本の指でタップするジェスチャの間のポイントになります。 カメラ のメソッドzoomBy()
は、指定した場所で離散的なズームステップを実行するように処理します。
ブール型フラグ isZoomIn
は、マップを拡大または縮小する必要があるかどうかを示します。 これにより、両方のズームケースでコードを使用できます。 唯一の違い は、アニメーション化された値zoomVelocity
が現在のズーム係数に加算または減算されることです。
ズーム倍率では、マップのズームインまたはズームアウトする距離を指定します。 ズーム係数 1 を指定しても、現在のズーム レベルは変更されません。
これで、画面遷移が終了します。上記のコードスニペットは、ご自身のニーズに合わせて自由に変更できます。 たとえば、さまざまな補間法、さまざまなアニメーション値、または異なるアニメーション期間を使用して再生を開始します。