Home > front end >  SwiftUI Frame Sizes For ZStacked Views, and Rotation
SwiftUI Frame Sizes For ZStacked Views, and Rotation

Time:05-28

I am trying to arrange a set of small pointers to highlight areas of interest on an image. My approach has been to ZStack the image and the pointers on top, which works fine at first. However, I would like to rotate the pointer graphics to avoid obscuring certain areas of the image, and this is where I hit a snag I can't seem to resolve.

Note in the first image below, the pointer is drawn such that its point hits the center of the frame, so I can easily position them from a parent view. The frame is represented as the black rectangle. However, even with the small frame sizes specified, rotation seems to apply to the entire geometry of the ZStack's space rather than just the frame size provided to the pointer. You can see in the second image that both items rotate relative to the center of the larger view size rather than the provided frame size. This ends up altering the pointed position, and wasn't what I expected since the Pointer correctly renders within the bounds of the small frame rect. The pointer view seems perfectly aware of those constraints.

Is there a way I can force the rotation operation to happen relative to the center of the provided .frame for the Pointer? Or am I doomed to writing out some complicated trig based on a geometry reader? Thanks for any help!

Image 1 is the pointer rendered without the rotations. Image 2 is after the rotations are applied.

struct ContentView: View {
    let origin: CGPoint = CGPoint(x: 100, y: 100)
    
    var body: some View {
        ZStack {
            Rectangle() //draw the frame the arrow is rendered within
                .frame(width: 50, height: 18, alignment: .center)
                .position(x: origin.x, y: origin.y)
                .border(.brown, width: 5)
                //.rotationEffect(Angle(degrees: 45))
                
            Circle() //Just to show the target of the arrow in the frame
                .foregroundColor(.white)
                .frame(width: 3, height: 3, alignment: .center)
                .position(x: origin.x, y: origin.y)
                //.rotationEffect(Angle(degrees: 45))
            
            Pointer()
                .foregroundColor(.red)
                .frame(width: 50, height: 18, alignment: .center)
                .position(x: origin.x, y: origin.y)
                .border(.green, width: 3)
                //.rotationEffect(Angle(degrees: 45))
        }
        .frame(width: 250, height: 450, alignment: .center)
    }
}
struct Pointer: Shape {
    func path(in rect: CGRect) -> Path {
        let X = rect.width
        let Y = rect.height
        < ... Path instructions to render the arrow ... > 
        return path
    }
}

No rotations applied, arrow pointing at the "origin" pointer just fine. After rotating the arrow (frame and white circle kept for illustration.

CodePudding user response:

the .border on the single elements leads to the problem, as it adopts the size of the outer frame. Skip it and you're fine. In my opinion you also don't need the first Rectangle, comment it out and the result is the same.

enter image description here

    var body: some View {
        ZStack {
            // you dont really need this Rect ...
            Rectangle() //draw the frame the arrow is rendered within
                .frame(width: 50, height: 18, alignment: .center)
                .rotationEffect(Angle(degrees: 45)) // here
               .position(x: origin.x, y: origin.y)
                
            Circle() //Just to show the target of the arrow in the frame
                .foregroundColor(.white)
                .frame(width: 3, height: 3, alignment: .center)
                .position(x: origin.x, y: origin.y)
            
            Image(systemName: "arrow.right")
                .resizable()
                .foregroundColor(.red)
                .frame(width: 25, height: 18)
                .frame(width: 50, alignment: .leading)
                .rotationEffect(Angle(degrees: 45)) // here
                .position(x: origin.x, y: origin.y)
        }
        .frame(width: 250, height: 450, alignment: .center)
        .border(.brown, width: 5)
    }
  • Related