重要なポイント
スワイプとは、画面上で指を素早く滑らせ、指を離して終了する動作です。スワイプは短く(要素をめくる)も長く(カーテンを開く)も可能ですが、重要な特徴は離散性です。システムはこれを連続的な座標のシーケンスではなく、単一のイベントとして認識します。
モバイルOSは、最小距離(しきい値)、最小速度(ベロシティ)、方向の3つのパラメータに基づいてスワイプを検出します。指の動きが遅い、または距離が不十分な場合、システムはそのジェスチャーをパンニングまたはタップとして解釈します。しきい値はプラットフォームによって異なります。iOSはUISwipeGestureRecognizerに固定しきい値の定数を使用し、AndroidはViewConfiguration.getScaledTouchSlop()を使用して、画面密度に応じて約8〜16dpを返します。
Android Developers Documentationによると、GestureDetectorのonFling(スワイプ)は100 px/s以上の速度、50px以上の距離で発火します。IT Sectrでは、ナビゲーション(ギャラリー、カードインターフェース、オンボーディング)にスワイプを使用しています。このジェスチャーはあらゆる年齢のユーザーにとって直感的です。
スワイプとパンニング(pan/drag)は、どちらも画面上で指を動かすことを含みますが、異なるタイプのジェスチャーです。スワイプは離散的で「1つのアクションを実行する」ことを意味し、パンニングは継続的で「指が画面にある間はオブジェクトを移動する」ことを意味します。
| パラメータ | スワイプ(Swipe) | パン/ドラッグ(Pan/Drag) |
|---|---|---|
| タイプ | 離散的 | 継続的 |
| イベント | 単一(認識/非認識) | 複数(.began → .changed → .ended) |
| 速度 | 高速(素早い動き) | 任意(ゆっくりした動き) |
| iOSの例 | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Androidの例 | GestureDetector.onFling() | GestureDetector.onScroll() |
| シナリオ | メールの削除、ページをめくる | マップの移動、ファイルのドラッグ |
| 座標追跡 | なし(方向のみ) | あり(継続的な座標更新) |
スワイプとパンニングの選択はタスクによって異なります。要素に対するアクション(削除、アーカイブ、メニューを開く)にはスワイプを使用します。直接操作(移動、拡大縮小)にはパンニングを使用します。同じ要素でジェスチャーを混在させるには、require(toFail:)またはカスタムロジックが必要です。
UISwipeGestureRecognizerはUIGestureRecognizerのサブクラスで、4方向のいずれかでの離散的なスワイプを認識します。UIPanGestureRecognizerとは異なり、中間の.changedイベントを生成しません。認識されると、アクションは正確に1回呼び出されます。
directionプロパティ(UISwipeGestureRecognizer.Direction)は追跡する方向(.left、.right、.up、.down)を設定します。マスクを使用して方向を組み合わせることもできます(水平スワイプの場合は[.left, .right])。numberOfTouchesRequiredプロパティは指の数を指定します(デフォルトは1)。UISwipeGestureRecognizerには設定可能な距離しきい値はありません。iOSは約20pxの組み込み値を使用しており、信頼性の高い認識に十分です。
UISwipeGestureRecognizerは離散的な認識器であるため、UIPanGestureRecognizerと競合しません。ゆっくりした動きはpanを、素早い動きはswipeをトリガーします。ただし、同じビューにUITapGestureRecognizerが共存する場合、誤ったタップ認識を防ぐためにrequire(toFail:)が必要になることがあります。
Androidでスワイプ認識のための主要なAPIは、OnGestureListenerコールバックを持つGestureDetector、具体的にはonFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)メソッドです。GestureDetectorは指の初期位置と最終位置、軸に沿った移動速度、距離を分析し、しきい値を超えた場合にonFlingを呼び出します。
最小しきい値はViewConfigurationを介して設定可能です。VelocityTracker.getMinVelocity()(約100 px/s)およびViewConfiguration.getScaledTouchSlop()(約8〜16dp)です。カスタムのOnSwipeTouchListenerは、APIを簡素化する一般的なパターンで、開発者が手動で方向と速度を計算する必要がありません。Android Support Libraryには組み込みの「SwipeDetector」は含まれていないため、カスタムラッパーが標準的な解決策です。
リストでのスワイプには、GoogleはRecyclerViewのItemTouchHelper.SimpleCallbackを推奨しています。これにより、ジェスチャー検出、アニメーション、onSwiped()コールバックが抽象化されます。IT Sectrでは、ViewPager2やカードインターフェースにカスタムのSwipeListenerを使用しています。これにより、パフォーマンスを犠牲にすることなくスワイプ感度を制御できます。
リスト(UITableView、RecyclerView)向けに、AppleとGoogleは専用のAPIを提供しています。これらは手動でUISwipeGestureRecognizerを追加する代わりに使用します。これらのAPIはアクションボタン、カスタムアニメーション、「スワイプして削除」ジェスチャーをサポートしており、モバイルアプリケーションの標準的なパターンです。
iOS(11+)では、UISwipeActionsConfigurationを使用します。tableView(_:leadingSwipeActionsConfigurationForRowAt:)メソッドはUIContextualActionの配列(タイトル、スタイル(destructive、normal)、ハンドラーを持つボタン)を返します。たとえば、「削除」は赤い背景の.destructive、「アーカイブ」はグレーの背景の.normalです。システムはボタンの表示をアニメーション化し、タップ後にcompletionブロックを呼び出します。
Androidでは、ItemTouchHelper.SimpleCallbackをItemTouchHelper.attachToRecyclerView()を介してRecyclerViewにアタッチします。パラメータには方向フラグ(start、end、up、down)とonSwiped(viewHolder、direction)コールバックが含まれます。onSwiped内では、アニメーション付きでアイテムを削除するためにadapter.notifyItemRemoved(position)が呼び出されます。ItemTouchHelperは、ジェスチャー、スクロールとの競合、スワイプが完了しなかった場合のアイテムの復帰を自動的に処理します。Google I/O 2024によると、リストにスワイプアクションがあるAndroidアプリの70%でItemTouchHelperが使用されています。
ビューにUISwipeGestureRecognizerを追加して、前の画面に戻ります。コードは.left方向での最小限の認識器設定を示しています。
import UIKit
class CardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeLeft(_:))
)
swipeLeft.direction = .left
swipeLeft.numberOfTouchesRequired = 1
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipeLeft(_: UISwipeGestureRecognizer) {
guard let nav = navigationController else { return }
if nav.viewControllers.count > 1 {
nav.popViewController(animated: true)
} else {
nav.dismiss(animated: true)
}
}
}
認識器はコントローラーのルートビューにアタッチされます。navigationController.viewControllers.countのチェックにより、スタックに前の画面がある場合にのみpopが実行されることを保証します。モーダルコントローラーの場合はdismissが使用されます。このコードは状態チェックを必要としません。UISwipeGestureRecognizerは離散的であり、認識に成功した場合にのみアクションを呼び出します。
スワイプの方向を判定し、onSwipeLeft/Rightコールバックを呼び出すシンプルなGestureDetectorラッパーを実装します。
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
open class OnSwipeTouchListener(context: Context) : View.OnTouchListener {
private val gestureDetector = GestureDetector(context, GestureListener())
override fun onTouch(v: View, event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
companion object {
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1!!.x
val diffY = e2.y - e1.y
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffX > 0) onSwipeRight()
else onSwipeLeft()
return true
}
} else {
if (Math.abs(diffY) > SWIPE_THRESHOLD
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffY > 0) onSwipeDown()
else onSwipeUp()
return true
}
}
return false
}
}
open fun onSwipeLeft() {}
open fun onSwipeRight() {}
open fun onSwipeUp() {}
open fun onSwipeDown() {}
}
OnSwipeTouchListenerはSimpleOnGestureListenerを拡張し、onFlingを実装します。diffX/diffYが方向を決定し、SWIPE_THRESHOLD(100px)とSWIPE_VELOCITY_THRESHOLD(100 px/s)がしきい値です。使用法:view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } })。
テーブルセルを左にスワイプしたときに削除ボタンを追加します。手動のジェスチャー認識なしで、組み込みのiOS 11+ APIを使用します。
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "削除"
) { _, _, completionHandler in
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
deleteAction.backgroundColor = .systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = true
return config
}
メソッドはUIContextualActionの配列を含むUISwipeActionsConfigurationを返します。style: .destructiveは自動的に赤い背景を設定します。performsFirstActionWithFullSwipe = trueにより、追加のタップなしで完全なスワイプでアクションを実行できます。IT Sectrでは、アクション付きのすべてのリストにこのAPIを使用しています。標準化されており、VoiceOverをサポートし、カスタムのGestureRecognizerは必要ありません。
よくある質問
スワイプは離散的なジェスチャーで、素早い動きと指の離脱を伴います。パンニングは座標追跡を伴う継続的なドラッグです。UISwipeGestureRecognizerは1回発火し、UIPanGestureRecognizerは.began → .changed → .endedを生成します。選択はシナリオによって異なります。スワイプはアクション(削除、めくる)用、panは移動(マップ、スライダー)用です。
ジェスチャーのロジックは同じですが、APIは異なります。iOSはUISwipeGestureRecognizerをdirectionと共に使用します。Androidはカスタムしきい値でGestureDetector.onFling()を使用します。結果は異なる実装で同じUXです。クロスプラットフォームフレームワーク(Flutter、React Native)は、統一されたスワイプAPIによって違いを抽象化します。
iOS(11+)では、UITableViewDelegateでUISwipeActionsConfigurationを使用します。Androidでは、RecyclerViewにItemTouchHelper.SimpleCallbackを使用します。これらのAPIは手動のGestureRecognizerを置き換え、アクションボタンとアニメーションをサポートし、スクロールとの競合を適切に処理し、プラットフォームのHIGに適応します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。