【SwiftUI】Gestureで縦横スクロールを共存させる

CoordinateSpace, Gesture, iOS, Simultaneous, Swift, SwiftUI

こんにちは!iOSアプリを開発している澁谷太智です!

業務内で、楽曲プレイヤー画面を作成していました。
下記のような要件の画面を作成していました。

  • 縦スクロールで楽曲プレイヤーを閉じることができる
  • 画面上部に、横スクロールで曲送り戻しのできるジャケット写真を表示する
  • 再生位置を表示・調整できるスライダーを表示する

この画面を作成する際に、縦スクロール/横スクロールを SwiftUI の Gesture を用いて実装しました。
この2つの Gesture を共存させるのに苦労しました。
今回はその方法について解説していこうと思います!

ニッチな内容かとは思いますが、お付き合いください!

目次

  1. 下準備
  2. 問題
  3. 解決策
    1. 全ての子Viewで親ViewのGestureと共に動作させる
    2. 特定の子Viewでのみ親ViewのGestureと共に動作させる
  4. 最後に

下準備

まず、下のGIFのように動作するViewを作成します。
prepare

View構成

最下層から順に下記のようになっています。

  • ZStack
    • Rectangle(色:indigo)
    • VStack
    • HorizontalMoveView
      • Rectangle(色:orange)
    • HorizontalMoveView
      • Rectangle(色:mint)

コード

親View

  • ZStack を画面いっぱいに作成
  • 適当な大きさの Rectangle() を ZStack 内に配置
  • Rectangle の上に VStack を配置
  • VStack 内に後ほど作成する HorizontalMoveView() を大きさ違いで2つ作成
  • 縦方向にドラッグできるジェスチャーを作成
  • ZStack に対して作成したジェスチャーを設定

image-20240930161702332

ContentView.swift

子View(HorizontalMoveView)

  • 適当な大きさの Rectangle() を作成する
  • 横方向にドラッグできるジェスチャーを Rectangle に対して設定する

image-20240930161848038

HorizontalMoveView.swift

問題

触ってみるとわかるのですが、親Viewの縦方向の DragGesture が子View上では動作しません。

今回の期待値である、

  • 全ての子Viewで親Viewの Gesture と共に動作させる
  • 特定の子Viewでのみ親Viewの Gesture と共に動作させる

が満たせていないことがわかります。

作成した画面の動き 期待値 1 期待値 2
view_made expectation_1 expectation_2
子Viewで親ViewのGestureが発火しない 全ての子Viewで親ViewのGestureが発火する 特定の子Viewのみ親ViewのGestureが発火する

作成したコードを改修していきます。

解決策

全ての子Viewで親ViewのGestureと共に動作させる

下記1点の変更を加えることで解決します。

  • ZStack に設定していた、 .gesture().simultaneousGesture() へ変更する

image-20240930162022545

ContentView.swift

特定の子Viewでのみ親ViewのGestureと共に動作させる

下記2点の変更を加えることで解決します。

  • 親View内の HorizontalMoveView() に対して、 .simultaneousGesture() で親Viewの Gesture を設定する
  • 親Viewの Gesture のイニシャライズを、 DragGesture(coordinateSpace: .global) に変更する

image-20240930162220141

ContentView.swift

coordinateSpace のデフォルト値は .local です。

local のまま HorizontalMoveView に対して Gesture を設定すると、HorizontalMoveView の 座標を基準として計算を行なってしまいます。親Viewと同じ計算座標にしたいので、 .global で画面全体の座標を基準として計算させるようにしています。

公式ドキュメント:CoordinateSpace | Apple Developer Documentation

.simultaneousGesture()

複数のジェスチャーを同時に認識させることができるモディファイア。

公式ドキュメント:simultaneousGesture(_:including:) | Apple Developer Documentation

最後に

いかがだったでしょうか。

そもそも今回の場合、DragGesture と ScrollView(.horizontal) を利用すれば解決なのでは?となるかと思います。

ScrollView の持っている機能だけで開発要件を満たせるのであれば、ScrollView を利用する形で問題ないと思います。(ジェスチャー方向の制御等複雑な処理を書かなくて済むので)

しかしながら、機能要件によっては ScrollView の持っている機能だけでは制御しきれない場合もあります。

そのような場合に、DragGesture を利用して Offset を調節して ScrollView のように見せる実装が一つの手段として考えられます。同じ状況に置かれた方の助けになればと思います。

最後まで読んでいただき、ありがとうございました!

また別の記事でお会いしましょう。