Home > Back-end >  How to rotate SwiftUI Path around its center?
How to rotate SwiftUI Path around its center?

Time:09-19

I have the following simple SwiftUI Path of an arrow which I wish to rotate around it's center:

@State var heading: Double = 0.0
@State var xOffset = 0.0
@State var yOffset = 0.0
var body: some View {
    TabView {
        NavigationStack {
            Path { path in
                path.move(to: CGPoint(x: xOffset, y: yOffset))
                path.addLine(to: CGPoint(x: xOffset   0, y: yOffset   20))
                path.addLine(to: CGPoint(x: xOffset   50, y: yOffset   0))
                path.addLine(to: CGPoint(x: xOffset   0, y: yOffset - 20))
                path.addLine(to: CGPoint(x: xOffset   0, y: yOffset   0))
            }
            .stroke(lineWidth: 3)
            .foregroundColor(.red)
            .rotationEffect(Angle(degrees: heading), anchor: .center)
            .transformEffect(CGAffineTransform(translationX: 80, y: 80))

            Slider(value: $heading, in: 0...360, step: 30)
        }
    }
}

At 0º

It does however not rotate around its center:

At 30º

How can I rotate a Path around its center (or around any other relative point on its bounds)?

CodePudding user response:

The Path view greedily takes up all of the space available to it, so the view is much larger than you think, and its center is far away. So when you rotate the path, it moves off the screen. You can see this by adding .background(Color.yellow) to the Path view.

It's easier to manage the rotation of the arrow if you make it an .overlay of another View (Color.clear) and then rotate that View. You can make the view visible by using Color.yellow while tuning, and then position the arrow relative to its parent view. The nice thing about this is that when you rotate the parent view, the arrow will stick to it and rotate predictably.

struct ContentView: View {
    @State var heading: Double = 0.0

    var body: some View {
        TabView {
            NavigationStack {
                Color.clear
                    .frame(width: 60, height: 60)
                    .overlay (
                        Path { path in
                            path.move(to: CGPoint(x: 0, y: 0))
                            path.addLine(to: CGPoint(x: 0, y: 20))
                            path.addLine(to: CGPoint(x: 50, y: 0))
                            path.addLine(to: CGPoint(x: 0, y: -20))
                            path.addLine(to: CGPoint(x: 0, y: 0))
                        }
                        .stroke(lineWidth: 3)
                        .foregroundColor(.red)
                        .offset(x: 5, y: 30)
                    )
                    .rotationEffect(Angle(degrees: heading), anchor: .center)
                
                Slider(value: $heading, in: 0...360, step: 30)
            }
        }
    }
}

arrow rotating in simulator


How do I make my code work?

You need to give your Path view a reasonable sized frame. Here I added .frame(width: 60, height: 60) which is big enough to hold your arrow path.

Since you have defined xOffset and yOffset, use them to move the path drawing within its view. Temporarily add a background to your path view with .background(Color.yellow) and then adjust xOffset and yOffset until your arrow is drawing inside of that view. You can then remove this .background.

This has the same effect as the overlay method presented above:

struct ContentView: View {
    @State var heading: Double = 0.0
    @State var xOffset = 5.0   // here
    @State var yOffset = 30.0  // here

    var body: some View {
        TabView {
            NavigationStack {
                Path { path in
                    path.move(to: CGPoint(x: xOffset, y: yOffset))
                    path.addLine(to: CGPoint(x: xOffset   0, y: yOffset   20))
                    path.addLine(to: CGPoint(x: xOffset   50, y: yOffset   0))
                    path.addLine(to: CGPoint(x: xOffset   0, y: yOffset - 20))
                    path.addLine(to: CGPoint(x: xOffset   0, y: yOffset   0))
                }
                .stroke(lineWidth: 3)
                .foregroundColor(.red)
                // .background(Color.yellow)
                .frame(width: 60, height: 60)  // here
                .rotationEffect(Angle(degrees: heading), anchor: .center)
                //.transformEffect(CGAffineTransform(translationX: 80, y: 80))

                Slider(value: $heading, in: 0...360, step: 30)
            }
        }
    }
}
  • Related