5

I would like to implement circular dragging in SwiftUI, but am not sure of the best way to go about it.

Here is the basic dragging code - there is one small draggable circle which I would like to limit to the bounds of the bigger one during the updating phase of the DragGesture. At the moment the black circle is draggable across the entire view.

import SwiftUI

struct ContentView: View {

  @State private var position = CGSize.zero
  @GestureState var dragOffset: CGSize = .zero

  private var dragRadius: CGFloat = 200.0

    var body: some View {
      ZStack {
        Circle()
          .fill(Color.red)
          .frame(width: dragRadius, height: dragRadius)

        Circle()
          .fill(Color.black)
          .frame(width: dragRadius / 4, height: dragRadius / 4)
          .offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
          .gesture(
            DragGesture()
              .updating($dragOffset, body: { (value, state, transaction) in
                // Need to clamp to circular bounds here??
                state = value.translation
              })
              .onEnded({ (value) in
                self.position.height += value.translation.height
                self.position.width += value.translation.width
              })
          )
      }
    }
}

I wonder whether it's a case of using trigonometry and polar co-ordinates to calculate the distance from the centre and limit to the radius in the direction of the dragged circle, or is there an easier way to get SwiftUI to "see" the circular bounds of a view?

codewithfeeling
  • 6,236
  • 6
  • 41
  • 53

1 Answers1

13

its not so much code to implement that. I just calculate distance between points (made an extension for that like in that question) and use that coefficient to make actual distance shorter. Is that what you want to reach?

enter image description here

import SwiftUI

extension CGPoint {
    func distance(to point: CGPoint) -> CGFloat {
        return sqrt(pow((point.x - x), 2) + pow((point.y - y), 2))
    }
}

struct ContentView: View {

    @State private var position = CGPoint(x: 100, y: 100)
    private var dragDiametr: CGFloat = 200.0
    var body: some View {

    return
        VStack{
            Text("current position = (x: \(Int(position.x)), y: \(Int(position.y)))")
            Circle()
              .fill(Color.red)
              .frame(width: dragDiametr, height: dragDiametr)
              .overlay(
                Circle()
                  .fill(Color.black)
                  .frame(width: dragDiametr / 4, height: dragDiametr / 4)
                  .position(x: position.x, y: position.y)
                  .gesture(DragGesture()
                  .onChanged(){value in
                    let currentLocation = value.location
                    let center = CGPoint(x: self.dragDiametr/2, y: self.dragDiametr/2)
                    let distance = center.distance(to:currentLocation)
                    if distance > self.dragDiametr / 2 {
                        let k = (self.dragDiametr / 2) / distance
                        let newLocationX = (currentLocation.x - center.x) * k+center.x
                        let newLocationY = (currentLocation.y - center.y) * k+center.y
                        self.position = CGPoint(x: newLocationX, y: newLocationY)
                    }else{
                        self.position = value.location
                    }
                  })
              )
        }
    }
}
Aspid
  • 629
  • 6
  • 20
  • This is a very nice solution. Thanks very much for taking the time to look at it. – codewithfeeling Jan 20 '20 at 17:46
  • 1
    Would love to see how this would need to change to support a rectangle in a rectangle. I am trying to add a MagnificationGesture that allows you to Drag to pan, however I want to prevent from being able to pan outside of the edge of your View. I feel like this is close to what I am looking for. – Andres F Garcia Jan 20 '23 at 19:35