[SwiftUI] クリックできる領域を明示的に指定する方法

内側が透明な要素を作って、それをクリックするための方法をメモ。

課題

SwiftUIでは、透明色を持つ部分は、その位置がその要素の描画領域内であっても、Gestureの対象になりません。

例えば、以下のような矩形です。
透明な中身を持つフレーム

矩形を中身を透明にして描画するコード

Rectangle()
  .fill(Color.gray.opacity(0))
  .border(Color.red, width: 3)

このような表示要素に対して、ドラッグ等のGestureを紐づけたとします。(おおよそ、先日のPostからコードを持ってきています)

矩形にドラッグ操作を追加したコード

Rectangle()
  .fill(Color.gray.opacity(0))
  .border(Color.red, width: 3)
  .frame(width: frameSize.width, height: frameSize.height)
  .offset(frameOffset)
  .gesture(DragGesture()
     .onChanged { gesture in
       if self.isDragging == false {
         self.dragStartPosition = CGPoint(x: self.frameOffset.width, y: self.frameOffset.height)
         self.isDragging = true
       }
       self.frameOffset =  CGSize(width: gesture.translation.width + self.dragStartPosition.x,
                                  height: gesture.translation.height + self.dragStartPosition.y)
     }
     .onEnded { gesture in
       self.isDragging = false
     })

動かしてみるとわかるのですが、borderとして描画された細い線の部分をつかまないとドラッグできません。

これが、冒頭に書いた「SwiftUIでは、透明色を持つ部分は、その位置がその要素の描画領域内であっても、Gestureの対象になりません。」という意味です。

透明色を持つ部分に対してもGestureできるようにする方法

.contentShape という modifier が用意されています。この modifer で操作領域を定義することができます。極端な話、矩形/Rectangleにたいして、円形/Circleを操作領域として設定するということもできます。

例えば、今回は、少し外れてもドラッグできるようしたかったので、少し大きめのRectangleを指定するようにしました。
少し大きめ もしくは 少し小さめ を指定するためには、.inset という modifier が用意されています。

最終的なコード

import SwiftUI

struct FrameView: View {
    @State private var frameOffset: CGSize = CGSize.zero
    @State private var frameSize: CGSize = CGSize(width: 300, height: 200)
    @State var dragStartPosition: CGPoint = CGPoint.zero
    @State var isDragging:Bool = false

    var body: some View {
        Rectangle()
            .fill(Color.gray.opacity(0))
            .border(Color.red, width: 3)
            .frame(width: frameSize.width, height: frameSize.height)
            .offset(frameOffset)
            .contentShape(Rectangle().inset(by: -10))                    // <-  ポイント
            .gesture(DragGesture()
                .onChanged { gesture in
                    if self.isDragging == false {
                        self.dragStartPosition = CGPoint(x: self.frameOffset.width, y: self.frameOffset.height)
                        self.isDragging = true
                    }
                    self.frameOffset =  CGSize(width: gesture.translation.width + self.dragStartPosition.x,
                                             height: gesture.translation.height + self.dragStartPosition.y)
                }
                .onEnded { gesture in
                    self.isDragging = false
            })
        
    }
}

struct FrameView_Previews: PreviewProvider {
    static var previews: some View {
        FrameView()
    }
}

上記のコードでは、Rectangle の内側(と少し外側)まで、クリックしてドラッグすることができますが、
”ポイント”と書かれた行をコメントアウトすると、Rectangle の border (この例では赤い線)を きっちりとクリックしないとドラッグすることができません。

MacOS上で、マウスを使っても、詳細なクリックを期待することは難しいと思いますので、この .contentShape modifier を適切に使うと良いUIが作れるはずです。

1 COMMENT

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です