[SOLVED] How to rotate SwiftUI Path around its center?

Issue

This Content is from Stack Overflow. Question asked by meaning-matters

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

            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 it’s center (or around any other relative point on it’s view)?



Solution

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)
            }
        }
    }
}


This Question was asked in StackOverflow by meaning-matters and Answered by vacawama It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?