はじめに (対象読者・この記事でわかること)

この記事は、SwiftでiOSアプリを開発している中級者の方を対象にしています。特に、タッチイベントを処理するtouchesMovedメソッドで取得した座標値を、他のメソッドで活用したい方に向けて書いています。

この記事を読むことで、touchesMovedで取得したタッチ座標を、他のメソッドで効率的に使用する方法がわかります。また、値の受け渡しに関するベストプラクティスや、よくある落とし穴についても解説します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Swiftの基本的な文法 - iOSアプリ開発の基礎知識 - UIResponderクラスの基本的な理解

touchesMovedの基本的な仕組みと課題

touchesMovedは、ユーザーが画面を指でなぞっている間、連続的に呼び出されるメソッドです。このメソッド内で取得できるタッチ座標は、指の動きにリアルタイムで追従するため、ゲームやドローイングアプリなどで非常に重要な情報となります。

しかし、このメソッドはUIResponderのメソッドであり、通常は特定のビュー内でしか呼ばれません。そのため、取得した座標を他のメソッドやクラスで使用しようとすると、「スコープの問題」や「タイミングの問題」に遭遇することがあります。

touchesMovedの値を他のメソッドで使用する実装方法

それでは、具体的な実装方法を見ていきましょう。主に3つの方法があります。

方法1: プロパティを使用した値の保持

最もシンプルな方法は、クラス内にプロパティを用意して、touchesMovedで取得した値を保持する方法です。

Swift
class TouchViewController: UIViewController { // タッチ座標を保持するプロパティ private var currentTouchPoint: CGPoint? override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let location = touch.location(in: self.view) // プロパティに値を保持 currentTouchPoint = location // デバッグ用 print("タッチ座標: \(location)") } // 他のメソッドで使用する例 func processTouchData() { guard let point = currentTouchPoint else { print("タッチされていません") return } // ここでpointを使用した処理 print("現在のタッチ座標: \(point)") } }

この方法の利点は、実装がシンプルで理解しやすいことです。しかし、注意点もあります。touchesMovedは指が動いていない時は呼ばれないため、currentTouchPointがnilになる可能性があります。

方法2: デリゲートパターンを使用した値の受け渡し

より高度な方法として、デリゲートパターンを使用する方法があります。これは、値を受け取る側のクラスが複数ある場合や、値の更新をリアルタイムで通知したい場合に有効です。

まず、デリゲートプロトコルを定義します:

Swift
protocol TouchEventDelegate: AnyObject { func touchDidMove(to point: CGPoint) func touchDidEnd() } class TouchViewController: UIViewController { weak var delegate: TouchEventDelegate? override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let location = touch.location(in: self.view) // デリゲートに通知 delegate?.touchDidMove(to: location) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { delegate?.touchDidEnd() } } // 使用側のクラス class ProcessingViewController: UIViewController, TouchEventDelegate { private var touchViewController: TouchViewController? override func viewDidLoad() { super.viewDidLoad() setupTouchHandling() } func setupTouchHandling() { touchViewController = TouchViewController() touchViewController?.delegate = self } // TouchEventDelegateの実装 func touchDidMove(to point: CGPoint) { // 受け取った座標で処理 print("デリゲートで受信: \(point)") updateUI(with: point) } func touchDidEnd() { print("タッチが終了しました") } private func updateUI(with point: CGPoint) { // UIの更新処理 } }

方法3: 通知センターを使用した値の共有

アプリ全体でタッチイベントを共有したい場合は、NotificationCenterを使用する方法があります。

Swift
extension Notification.Name { static let touchDidMove = Notification.Name("touchDidMove") } class TouchViewController: UIViewController { override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let location = touch.location(in: self.view) // 通知を送信 NotificationCenter.default.post( name: .touchDidMove, object: nil, userInfo: ["location": location] ) } } // 任意の場所で受信 class SomeOtherClass { init() { setupObserver() } private func setupObserver() { NotificationCenter.default.addObserver( self, selector: #selector(handleTouchMove(_:)), name: .touchDidMove, object: nil ) } @objc private func handleTouchMove(_ notification: Notification) { guard let location = notification.userInfo?["location"] as? CGPoint else { return } print("通知で受信: \(location)") } deinit { NotificationCenter.default.removeObserver(self) } }

ハマった点やエラー解決

実装中によくある問題点をいくつか紹介します。

問題1: 値が更新されない

Swift
// 悪い例 class BadExample { var touchPoint: CGPoint? func process() { // touchesMovedで更新されるはずのtouchPointを使用 if let point = touchPoint { // この処理が実行されないことがある } } }

原因と解決策 touchesMovedは指が動いている間しか呼ばれないため、指を止めた瞬間から値が更新されなくなります。解決策としては、最後のタッチ座標を保持しておくか、タッチ終了時にnilを設定するようにします。

Swift
class GoodExample { var lastTouchPoint: CGPoint? override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } lastTouchPoint = touch.location(in: self.view) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { lastTouchPoint = nil // 明示的にクリア } }

問題2: メモリリーク デリゲートやクロージャを使用する際に、循環参照が発生することがあります。

Swift
// 悪い例(循環参照) class MemoryLeakExample { var touchHandler: ((CGPoint) -> Void)? func setup() { touchHandler = { [weak self] point in // selfを強参照していると循環参照になる self?.processPoint(point) } } }

解決策

適切に[weak self]や[unowned self]を使用して、循環参照を回避します。

Swift
class GoodMemoryExample { var touchHandler: ((CGPoint) -> Void)? func setup() { touchHandler = { [weak self] point in self?.processPoint(point) } } }

まとめ

本記事では、SwiftのtouchesMovedで取得したタッチ座標を他のメソッドで使用するための3つの方法を解説しました。

  • プロパティを使用した値の保持: シンプルで実装しやすい
  • デリゲートパターン: 複数のクラスに通知したい場合に最適
  • 通知センター: アプリ全体で共有したい場合に有効

この記事を通して、タッチイベントの値を効率的に管理し、他のコンポーネントと連携する方法が理解できたでしょう。それぞれの方法には利点と欠点があるため、アプリの要件に応じて適切な方法を選択することが重要です。

今後は、より高度なジェスチャー認識や、アニメーションとの連携についても記事にする予定です。

参考資料