[SwiftUI] modifier の順番は本当に大事

Modifier の順番の大切さに気付いたのでメモ

気づき

ここでつくった、ドラッグできるフレームに対して、マウスの位置に合わせてカーソルを変更しようと思ったのがきっかけでした。

マウスの位置の変更に対する動作として、.onHover を使用することができます。

この .onHover を使って、入ったとき、出たときにカーソルを変えるようにしました。

onHover部分

.onHover(perform: { isIn in
  isIn ? NSCursor.pointingHand.set() : NSCursor.arrow.set()
})

MoveCursorInArea

良い感じです。

全体で見るとこんな感じでした。

コード

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)
            .contentShape(Rectangle().inset(by: -10))
        .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
            })
       .onHover(perform: { isIn in
            _ = isIn ? NSCursor.pointingHand.set() : NSCursor.arrow.set()
          })
    }
}

.onHover の位置は特に考えずに、なんとなく(汗)最後に追加していたんですが、問題が発生しました。

一度 Rectangle をドラッグで移動させた後に、マウスを Hover 位置に移動させても、期待と違う場所でマウスカーソルが変わるんです。

期待通りに変更されていないとき

期待通りに変化していないケース

期待してないのに変更されているとき

期待してないのに変更されるケース

問題分析

よ〜くみてみると、Rectangleを移動した後も、どうやら、Rectangleの初期位置に対して、マウスカーソルが制御・変更されているようだと気づきました。

で、コードを確認してみると、そうです、onHover が一番最後にあり、offsetで移動された後のRectangle(というかborder)に対して処理しているのでした。

解決策: onHover を適用する順番大事

問題点はわかったので、解決策は簡単です。onHover を offset の前に移動させて解決。

onHover位置を調整したコード

Rectangle()
    .fill(Color.gray.opacity(0))
    .border(Color.red, width: 3)
    .contentShape(Rectangle().inset(by: -10))
    .frame(width: frameSize.width, height: frameSize.height)
    .onHover(perform: { isIn in
      isIn ? NSCursor.pointingHand.set() : NSCursor.arrow.set()
    })
    .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
            NSCursor.pointingHand.set()
        }
        self.frameOffset =  CGSize(width: gesture.translation.width + self.dragStartPosition.x,
                                 height: gesture.translation.height + self.dragStartPosition.y)
    }
    .onEnded { gesture in
        self.isDragging = false
    NSCursor.arrow.set()
})
# ついでに、ドラッグ中のカーソルも調整してます。

期待通りのマウスカーソル

まとめ:位置に関係する modifier は、offset 等の modifier との適用順番に注意することが必要

Modifier の適用順序で background や fill で色が塗られる位置が変わることは知っていましたが、改めて再認識ました。

ということのメモでした。

コメントを残す

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