swiftswiftui

How convert a path from one coordinate system to another?


I have this simple code which draw path in view like this:

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        
        GeometryReader { proxy in
            
            Color.yellow
            
            let proxyCGRect: CGRect = proxy.frame(in: .local)
            
            let dy = rectPath1.boundingRect.midY
            
            // way 1 with offsetBy:
            rectPath1
                .offsetBy(dx: .zero, dy: .zero)
                .fill(Color.blue)
                
            
            // way 2 with position:
            rectPath2
                .fill(Color.red)
                .position(x: 0, y: 0)
            
            
            // way 3 with offset:
            rectPath3
                .fill(Color.green)
                .offset(x: 0, y: 0)
            
        }
        
    }
}


func createRectPath(for rect: CGRect) -> Path {
    var path = Path()
    path.addRect(rect)
    return path
}

let rectPath1: Path = createRectPath(for: CGRect(x: 50, y: 50, width: 50, height: 100))
let rectPath2: Path = createRectPath(for: CGRect(x: 200, y: 100, width: 100, height: 100))
let rectPath3: Path = createRectPath(for: CGRect(x: 400, y: 300, width: 100, height: 100))

In view we get the path to draw, I want change SwiftUI coordinate system like in photo from black to blue. In x axis it would be same but y axis is going change. I want make a func which use the path information to draw the same thing but in other coordinate system, how we can do this? just one thing to know we do have access to path not to cgrect of it! Because this path could be any shape so it is not fixed to addRect, it code be any path, but we have access to path, and we can use it.

enter image description here


Solution

  • When I try your example code on an iPhone 16 simulator running iOS 18.1, I only see a blue rectangle with a yellow background covering the area of the screen inside the safe area insets.

    It's perhaps worth trying to understand, why the red and green rectangles are not being shown. Let's try a different test using just the red rectangle:

    var body: some View {
        rectPath2
            .fill(.red)
            .border(.blue)
            .overlay { Circle().frame(width: 10, height: 10) }
    }
    

    This shows the following:

    Screenshot

    What we see is:

    In your original example, you were setting .position(x: 0, y: 0) on the red rectangle. This moves the center of the view to the new coordinates in the parent's coordinate space. This means, the black dot will move to the position shown by the top-left corner of the blue frame and the red rectangle disappears off the screen.


    Getting back to your original question, if you want the y-coordinates to work from the bottom of the coordinate space instead of from the top, you just need to flip the whole shape by applying .scale(y: -1).

    Btw, .scale is a function of Shape, which returns a ScaledShape - see scale(x:y:anchor:). Since a ScaledShape is also a Shape, you can still apply other shape operations to the result, such as .fill. This would not be possible if the View modifier .scaleEffect would be used instead, because the view modifier returns some View.

    Trying this on the same example above:

    rectPath2
        .scale(y: -1)
        // ...other modifiers as before
    

    Screenshot

    So is this what you were after, or did you have some other kind of transformation in mind?