Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit drag to circular bounds in SwiftUI

Tags:

ios

swiftui

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?

like image 672
codewithfeeling Avatar asked Jan 20 '20 12:01

codewithfeeling


1 Answers

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
                    }
                  })
              )
        }
    }
}
like image 193
Aspid Avatar answered Nov 09 '22 00:11

Aspid