skip to Main Content

I was trying to create a custom View with rotation effects in SwiftUI. Instead of using rotation gestures, I was trying to use the Drag Gesture to rotate the Parent by adding a separate view to the parent. Dragging the child (The Blue Dot) would rotate its parent (ZStack). But I’ve faced difficulty while dragging the blue dot. The drag gesture is not ending. Can you guys help me to find out what I did wrong?

import SwiftUI

struct EditingHolder: View {

    /* For Rotation Circle Drag */
    @State private var currDragRect : CGSize = .zero
    @State private var prevDragRect : CGSize = .zero
    var rectRadius : CGFloat {
        let sum : CGFloat = 2*pow(150, 2)
        return sqrt(sum/4)
    }

    var dragRadius : CGFloat {
        let height = prevDragRect.height + currDragRect.height
        let width = prevDragRect.width + currDragRect.width
        let sum = pow(height, 2) + pow(width, 2)
        return sqrt(sum/4)
    }

    var rotateAngle : CGFloat{
        let angle = asin(dragRadius/rectRadius)
        print("🍄 Angle Produced = ", angle)
        return angle
    }

    var body: some View {
        /* **** Gestures **** */
        let rotateDrag = DragGesture()
            .onChanged({ value in
                print("🏵 Rotate Circle Drag Started ...")
                currDragRect = value.translation

            }).onEnded({ _ in
                print("🏵 Rotate Circle Drag Ended ✅")
                prevDragRect.height += currDragRect.height
                prevDragRect.width += currDragRect.width
                currDragRect = .zero
            })

        //************* Views *******************

        GeometryReader { geo in
            ZStack(alignment: .center) {
                Rectangle()
                    .padding()
                    .foregroundColor(Color.yellow)

                ///Rotate Circle `top`
                Circle()
                    .foregroundColor(Color.blue)
                    .frame(width: 20, height: 20)
                    .position(x: 150 - 3, y: 3)
                    .gesture(rotateDrag)

            }.frame(width:150, height: 150, alignment: .center)
                .border(.green, width: 3)
                .position(x: geo.size.width/2, y: geo.size.height/2)
                .rotationEffect(.radians(rotateAngle))

        }

        //************* Views *******************
    }
}

struct EditingHolder_Previews: PreviewProvider {
    static var previews: some View {
        EditingHolder()
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    To provide high priority to a specific gesture there is a modifier called .highPriorityGesture(). Have a look here for a better explanation How to use gestures in SwiftUI.

    I've updated the angle calculations,

        /* **** Gestures **** */
        let rotateDrag = DragGesture()
            .onChanged({ value in
                print("🏵 Rotate Circle Drag Started ...")
                
                let difY = center.y - value.location.y
                let difX = center.x - value.location.x
                
                //Initial Angle when the drag started..
                if deltaAngle == 0{
                    deltaAngle = atan2(difY, difX)
                }else{
                    angle = atan2(difY, difX) - deltaAngle
                }
    
            }).onEnded({ _ in
                print("🏵 Rotate Circle Drag Ended ✅")
                withAnimation {
                    angle = 0
                    deltaAngle = 0
                }
            })
    
        //************* Views *******************
    

    Now to add .highPriorityGesture(rotateDrag) to ZStack.

    .onTapGesture() is added to get the center for angle calculation. Tap on the view and then rotate by dragging the blue dot. Here is the final implementation,

        struct EditingHolder: View {
    
        /* For Rotation Circle Drag */
        @State private var center : CGPoint = .zero
        @State private var angle : CGFloat = 0
        @State private var deltaAngle : CGFloat = 0
    
        var body: some View {
            /* **** Gestures **** */
            let rotateDrag = DragGesture()
                .onChanged({ value in
                    print("🏵 Rotate Circle Drag Started ...")
                    
                    let difY = center.y - value.location.y
                    let difX = center.x - value.location.x
                    
                    //Initial Angle when the drag started..
                    if deltaAngle == 0{
                        deltaAngle = atan2(difY, difX)
                    }else{
                        angle = atan2(difY, difX) - deltaAngle
                    }
    
                }).onEnded({ _ in
                    print("🏵 Rotate Circle Drag Ended ✅")
                    withAnimation {
                        angle = 0
                        deltaAngle = 0
                    }
                })
    
            //************* Views *******************
    
            GeometryReader { geo in
                ZStack(alignment: .center) {
                    Rectangle()
                        .padding()
                        .foregroundColor(Color.yellow)
    
                    ///Rotate Circle `top`
                    Circle()
                        .foregroundColor(Color.blue)
                        .frame(width: 20, height: 20)
                        .position(x: 150 - 3, y: 3)
                        .gesture(rotateDrag)
    
                }.frame(width:150, height: 150, alignment: .center)
                    .border(.green, width: 3)
                    .position(x: geo.size.width/2, y: geo.size.height/2)
                    .rotationEffect(Angle(radians: angle))
                /* You have make the gesture a high priority */
                    .highPriorityGesture(rotateDrag)
                    .onTapGesture {
                        print("☘️ Center assigned..")
                        center = CGPoint(x: geo.frame(in: .global).size.width/2, y: geo.frame(in: .global).size.height/2)
                        
                    }
    
            }
    
            //************* Views *******************
        }
    }
    
    struct EditingHolder_Previews: PreviewProvider {
        static var previews: some View {
            EditingHolder()
        }
    }
    

  2. The sequence of drag gesture and rotation is important, otherwise SwiftUI looses context of the dragged view (which is changing by drag).

    Also you don’t need GeometryReader. Here is an example that works in regards to the dragging, the angle calculation needs some more work.

    struct ContentView: View {
        
        /* For Rotation Circle Drag */
        @State private var currDragRect : CGSize = .zero
        @State private var prevDragRect : CGSize = .zero
        let rectRadius : CGFloat = 75
        
        var dragRadius : CGFloat {
            let height = prevDragRect.height + currDragRect.height
            let width = prevDragRect.width + currDragRect.width
            let sum = pow(height, 2) + pow(width, 2)
            return sqrt(sum/4)
        }
        
        var rotateAngle : CGFloat{
            let x = min(1, max(-1, dragRadius/rectRadius)) // asin only defined in -1...1
            let angle = asin(x)
            print("🍄 Angle Produced = ", angle)
            return angle
        }
        
        var body: some View {
            
            /* **** Gestures **** */
            let rotateDrag = DragGesture()
                .onChanged { value in
                    print("🏵 Rotate Circle Drag Started ...")
                    currDragRect = value.translation
                    
                }
                .onEnded { _ in
                    print("🏵 Rotate Circle Drag Ended ✅")
                    prevDragRect.height += currDragRect.height
                    prevDragRect.width += currDragRect.width
                    currDragRect = .zero
                }
            
            //************* Views *******************
            
            ZStack(alignment: .topTrailing) {
                Rectangle()
                    .padding()
                    .foregroundColor(Color.yellow)
                    .border(.green, width: 3)
                
                ///Rotate Circle `top`
                Circle()
                    .foregroundColor(Color.blue)
                    .frame(width: 20, height: 20)
                    .offset(x: 8, y: -8)
                
            }
            .rotationEffect(.radians(rotateAngle))  // rotation here
            .gesture(rotateDrag)                    // drag here
            .frame(width:150, height: 150, alignment: .center)
            
            //************* Views *******************
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search