SwiftUIでオブジェクトの移動

これは備忘録メモ
SwiftUIでアノテーションツールを作ろうかと思ったらオブジェクトのドラッグ＆ドロップのやり方がよくわからなくてハマる。
次に値を取ってこようとしたら、慣れていないのでBindingの書き方がわからなくて値を取ってこれない。
ちょっとしたことも慣れてないと難しい。

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/9a37c9cc-e97b-8016-6471-418e5463bc62.gif)


```swift
import SwiftUI

extension View {
  func draggable(into binding: Binding<CGPoint>) -> some View {
    self.modifier(DraggableModifier(offset: binding))
  }
}

struct DraggableModifier: ViewModifier {
  @Binding var offset: CGPoint
  @State private var previousTranslation: CGSize?

  func body(content: Content) -> some View {
    
    content
      .offset(x: offset.x, y: offset.y)
      .gesture(DragGesture().onChanged { value in
        if let translation = previousTranslation {
          // Drag changed
          let delta = CGSize(width: value.translation.width - translation.width,
                             height: value.translation.height - translation.height)
          offset.x += delta.width
          offset.y += delta.height
          previousTranslation = value.translation
        } else {
          // Drag started
          previousTranslation = value.translation
        }
      }
      .onEnded { _ in
        // Drag ended
        previousTranslation = nil
      })
  }
}

struct Shape {
  var offset: CGPoint = .zero
  var view: AnyView
  init(_ view: AnyView){
    self.view = view
  }
}

struct ContentView: View {
  @State private var location: CGPoint = CGPoint(x: 0, y: 0)
  @State var offset: CGPoint = .zero
  @State private var shapes: [Shape] = [
    Shape(
      AnyView (
        Rectangle().fill(Color.red)
          .frame(width: 100, height: 100)
      )),
    Shape(
      AnyView (
        Circle().fill(Color.blue)
          .frame(width: 100, height: 100)
      ))
  ]

  var body: some View {
    ZStack {
      Rectangle()
        .fill(Color.clear)
      
        VStack {
          ForEach(shapes.indices, id: \.self) {
            shapes[$0].view.draggable(into: $shapes[$0].offset)
            Text("Offset: \(shapes[$0].offset.x)")
          }
        }
    }.contentShape(Rectangle())
    }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
```

## 修正版

前回のoffset版では、タップした位置の座標は返すがオブジェクトの座標を返していなかったので修正

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/d5546ca4-32f5-1215-c3c7-d34ace0d5576.gif)

```swift
import SwiftUI

extension View {
  func draggable(into binding: Binding<CGPoint>) -> some View {
    self.modifier(DraggableModifier(location: binding))
  }
}

struct DraggableModifier: ViewModifier {
  @Binding var location: CGPoint
  @GestureState private var startLocation: CGPoint? = nil

  func body(content: Content) -> some View {
    
    content
      .position(x: location.x, y: location.y)
      .gesture(
        DragGesture()
          .onChanged { value in
            var newLocation = startLocation ?? location
            newLocation.x += value.translation.width
            newLocation.y += value.translation.height
            self.location = newLocation
          }
          .updating($startLocation) { (value, startLocation, transaction) in
            startLocation = startLocation ?? location
          }
      )
  }
}

struct Shape {
  var location: CGPoint = .zero
  var view: AnyView
  init(_ view: AnyView, location: CGPoint){
    self.view = view
    self.location = location
  }
}

struct ContentView: View {
  @State private var shapes: [Shape] = [
    Shape(
      AnyView (
        Rectangle().fill(Color.red)
          .frame(width: 50, height: 50).offset(x: 25,y: 25)
      ), location: CGPoint(x: 0, y: 0)),
    Shape(
      AnyView (
        Circle().fill(Color.blue)
          .frame(width: 50, height: 50).offset(x: 25,y: 25)
      ), location: CGPoint(x: 100, y: 100))
  ]

  var body: some View {
    ZStack {
      Rectangle()
        .fill(Color.clear)
      
        ForEach(shapes.indices, id: \.self) {
          shapes[$0].view.draggable(into: $shapes[$0].location)
        }
        VStack {
          ForEach(shapes.indices, id: \.self) {
            Text("Location: \(shapes[$0].location.x), \(shapes[$0].location.y)")
          }
        }
    }.contentShape(Rectangle())
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
```


## 参考サイト
- Drag - Using SwiftUI Gestures - Draw SwiftUI Part 1
    - https://www.youtube.com/watch?v=Iko5db7WlgA
    - https://github.com/codingWithTom/Draw-SwiftUI/tree/FirstVideo
- Binding
    - https://stackoverflow.com/questions/65752883/swiftui-passing-viewmodifier-as-parameter
- Move your view around with Drag Gesture in SwiftUI
    - https://sarunw.com/posts/move-view-around-with-drag-gesture-in-swiftui/