[SwiftUI] shift キーで動作の変わる マウス操作

SwiftUI

macOS アプリで キーボードとの組み合わて動くマウス操作の作り方を説明します

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.2.2
  • Xcode 12.4

キーボードと組み合わせて動くマウス操作

macOS アプリでは、キーボードと組み合わせることのできるマウス操作があります。

例えば、Finder 上で、ファイルをドラッグすると移動になりますが、⌘ キーを押しながらドラッグすると、(移動ではなく)コピーになります。

SwiftUI でこのような振る舞いをどのように実装するのかを説明します。

SwiftUI でのマウス操作の実装

.gesture を使って、実装していくことになります。

例えばテキストをドラッグして移動するような操作は、以下のような実装となります。

DragGesture Example

struct ContentView: View {
    @State private var textOffset: CGSize = CGSize.zero
    @State var dragStartPosition: CGPoint = CGPoint.zero
    @State var isDragging:Bool = false

    var body: some View {
        ZStack {
            Color.white.frame(width: 800, height: 800)
            Text("Hello, World!")
                .background(Color.white)
                .offset(textOffset)
                // (1)
                .gesture(DragGesture()
                    // (2)
                    .onChanged { gesture in
                        if self.isDragging == false {
                            self.dragStartPosition = CGPoint(x: self.textOffset.width, y: self.textOffset.height)
                            self.isDragging = true
                        }
                        self.textOffset =  CGSize(width: gesture.translation.width + self.dragStartPosition.x,
                                                 height: gesture.translation.height + self.dragStartPosition.y)
                    }
                    // (3)
                    .onEnded { gesture in
                        self.isDragging = false
                    })
        }
    }
}
コード解説
  1. .gesture modifier を使って、ビューにマウス操作を定義することができます。ここでは、DragGesture を使用してドラッグ操作を定義しています。
  2. ドラッグ中には、onChanged が呼ばれます。ここでは .offset と組み合わせて、テキストの表示位置を移動させています。
  3. ドラッグ終了時には、onEnded が呼ばれます。

shift キーと組み合わせる

グラフィック系のアプリケーションで、shift キーを押すと、グリッドに吸着するという操作になるアプリケーションをみたことがあります。
ここでは、100 px ごとにグリッドがあるという想定で、shift キーを押されているときは、そのグリッド上にしか移動できない操作を実装してみます。

※ shift が押されていなければ、自由に移動できます。

DragGesture with shift

struct ContentView: View {
    @State private var textOffset: CGSize = CGSize.zero
    @State var dragStartPosition: CGPoint = CGPoint.zero
    @State var isDragging:Bool = false

    var body: some View {
        ZStack {
            Color.white.frame(width: 800, height: 800)
            Text("Hello, World!")
                .background(Color.white)
                .offset(textOffset)
                // (1), (2)
                .gesture(DragGesture().modifiers(.shift)
                    .onChanged { gesture in
                        if self.isDragging == false {
                            self.dragStartPosition = CGPoint(x: self.textOffset.width, y: self.textOffset.height)
                            self.isDragging = true
                        }
                        let newOffset =  CGSize(width: gesture.translation.width + self.dragStartPosition.x,
                                                 height: gesture.translation.height + self.dragStartPosition.y)
                        // (3)
                        self.textOffset = CGSize(width: CGFloat(Int(newOffset.width/100) * 100), height: CGFloat(Int(newOffset.height/100)*100))
                    }
                    .onEnded { gesture in
                        self.isDragging = false
                    })
                // (2')
                .gesture(DragGesture()
                    .onChanged { gesture in
                        if self.isDragging == false {
                            self.dragStartPosition = CGPoint(x: self.textOffset.width, y: self.textOffset.height)
                            self.isDragging = true
                        }
                        self.textOffset =  CGSize(width: gesture.translation.width + self.dragStartPosition.x,
                                                 height: gesture.translation.height + self.dragStartPosition.y)
                    }
                    .onEnded { gesture in
                        self.isDragging = false
                    })
        }
    }
}
コード解説
  1. .modifier を指定することで、DragGesture 時の修飾キーを指定することができます
  2. 同じ Gesture (ここでは DragGesture)を指定するときは、指定の順番に気をつける必要があります。最初にマッチした Gesture になってしまうので、shift アリ/ナシ で分ける時には、shift アリを最初に記述する必要があります。記述の順番を逆にすると、常に shift ナシ が動作します。
  3. 100 px のグリッド上に来るように計算しています。

注意点

上記の指定方法は、Gesture 開始時に条件がマッチしている必要があります。上記の例であれば、ドラッグ開始後に、shift キーを押下する / 離す ということをしても、動作中の Gesture が切り替わるわけではありません。

Gesture 中のキー切り替えで振る舞いが変わる Gesture を作るためには、工夫が必要となります。

まとめ:キーボードと組み合わせたマウス操作

キーボードと組み合わせたマウス操作
  • Gesture に、.modifier を指定することで、キーボードと組み合わせて特定の Gesture を開始させることができる
  • 条件を満たす最初の Gesture が開始されるため、記述の順番に気をつける必要がある
  • 注意:Gesture 途中で、キーボードを操作しても、Gesture は切り替わらない

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

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