【SwiftUI】View を ドラッグ して移動 する

View をドラッグして動かします。


struct TestDragGesture: View {
  @State private var location = CGPoint(x: 150, y: 150)

  var body: some View {
    ZStack {
      VStack(alignment: .trailing) {
        Text("location.x: \(location.x)")
        Text("location.y: \(location.y)")
      }
      .monospaced()

      Circle()
        .fill(.red.opacity(0.5))
        .frame(width: 100)
        .position(location)
        .gesture(
          DragGesture()
            .onChanged { value in
              location = value.location
            }
        )
    }
  }
}

ドラッグ位置が変化するたびに View の位置を更新できます。

var location: CGPoint
The location of the drag gesture’s current event.

👉 DragGesture.Value | Apple Developer Documentation hatena-bookmark

Circle の position は Viewの中心

であることに対して

DragGesture().onChanged() で取得したドラッグ位置

とずれがあるため、

タップ位置を動かした瞬間に中心位置にジャンプしてしまいます。

なんか違和感がありますね !

 

👩🏼‍💻 startLocation と translation

計算方法を変えます。

以下を使います。

var startLocation: CGPoint
The location of the drag gesture’s first event.

var translation: CGSize
The total translation from the start of the drag gesture to the current event of the drag gesture.

👉 DragGesture.Value | Apple Developer Documentation hatena-bookmark


Circle()
  .fill(.red.opacity(0.5))
  .frame(width: 100)
  .position(location)
  .gesture(
    DragGesture()
      .onChanged { value in
        var newLocation = startLocation ?? location
        newLocation.x += value.translation.width
        newLocation.y += value.translation.height
        location = newLocation
      }
      .updating($startLocation) { _, startLocation, _ in
        startLocation = startLocation ?? location
      }
   )

Circle の中心位置に対して、ドラッグの移動量を増減することで、なめらかに違和感なく動くようになりました !



簡単なコードに見えますが、updating を使った位置情報の入れ替えとか、よくみると深い。

👉 updating(_:body:) | Apple Developer Documentation hatena-bookmark
👉 Move your view around with Drag Gesture in SwiftUI | Sarunw hatena-bookmark

しかし、なんか見通しが悪い。

@GestureState と updating() は使いづらい。

 

👩🏼‍💻 まとめ

これでいきます。



キモは、

「ドラッグの移動量を View のポジションに適用」

「ドラッグ開始時のドラッグ位置とView中心のズレ」



【SwiftUI】.contentTransition(.symbolEffect(.replace))



これよく見かけるのでやってみます。


.contentTransition(.symbolEffect(.replace))

まず、Button の Label のアイコンを、

ある値を見ながら切り替えます。

よくあるパターンなので想像はつくでしょう。


@State private var value = false


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: value ? "heart.fill" : "heart")
}

次に .symbolVariant() を使います。

挙動は同じだと思われます。


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: "heart")
    .symbolVariant(value ? .fill : .none)
}

ここで、

.contentTransition(.symbolEffect(.replace))

付けます。


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: "heart")
    .symbolVariant(value ? .fill : .none)
    .contentTransition(.symbolEffect(.replace))
}

Apple 公式サンプルコードでは、

以下のようにさらに細かく調整されていました。


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: "heart")
    .symbolVariant(value ? .fill : .none)
    .contentTransition(
      .symbolEffect(value ? .replace.upUp : .replace.downUp)
    )
}

以上4つを並べて確認します。

微妙にエフェクト具合が違うけど、

どれがいいとはいいづらい。

👉 【SwiftUI】市松模様を背景にする - Checkered Pattern Background 🏁 hatena-bookmark

 

🧑🏻‍💻 まとめ

タイトルどおりの


.contentTransition(.symbolEffect(.replace))

ぐらいがシンプルな記述のわりに見栄えが良いように思います。

みんなが良く使ってる意味が分かりました !


【SwiftUI】#Preview with @Binding arguments

#Preview@Binding の引数を持つ View をどう書くか。

サンプルとして以前のコードを使います。


.constant() を使うか、@State + return を使うか。


#Preview(".constant(\"\")") {
  TestSearchTextField(text: .constant(""))
    .frame(width: 300)
}

#Preview(".constant(\"dog\")") {
  TestSearchTextField(text: .constant("dog"))
    .frame(width: 300)
}

#Preview("return") {
  @State var text = "dog"
  return TestSearchTextField(text: $text)
    .frame(width: 300)
}

View パーツの確認をすばやく #Preview で確認できるようになりました !