swiftswiftuifrontendmeasurement

Vector with text drawing on image in SwiftUI


I want to draw Vectors that have a text in the middle on images, and be able to keep the text centered in the Vector

Example

Is there any easy way to do it with SwiftUI? Or any helpful libraries for something similar?


Solution

  • You can use a Shape to build the vector and then show it behind a Text label. Something like this:

    struct Vector: Shape {
        let arrowWidth = CGFloat(15)
        let arrowHeight = CGFloat(12)
        let lineWidth = CGFloat(2)
    
        func path(in rect: CGRect) -> Path {
            var path = Path()
    
            // The line at the top
            path.move(to: CGPoint(x: rect.midX - (arrowWidth / 2), y: rect.minY + (lineWidth / 2)))
            path.addLine(to: CGPoint(x: rect.midX + (arrowWidth / 2) , y: rect.minY + (lineWidth / 2)))
    
            // The arrow at the top
            path.move(to: CGPoint(x: rect.midX, y: rect.minY + lineWidth))
            path.addLine(to: CGPoint(x: rect.midX + (arrowWidth / 2), y: rect.minY + arrowHeight))
            path.addLine(to: CGPoint(x: rect.midX - (arrowWidth / 2), y: rect.minY + arrowHeight))
            path.addLine(to: CGPoint(x: rect.midX, y: rect.minY + lineWidth))
            path.closeSubpath()
    
            // The central line
            path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY - lineWidth))
    
            // The arrow at the bottom
            path.move(to: CGPoint(x: rect.midX, y: rect.maxY - lineWidth))
            path.addLine(to: CGPoint(x: rect.midX + (arrowWidth / 2), y: rect.maxY - arrowHeight))
            path.addLine(to: CGPoint(x: rect.midX - (arrowWidth / 2), y: rect.maxY - arrowHeight))
            path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY - lineWidth))
            path.closeSubpath()
    
            // The line at the bottom
            path.move(to: CGPoint(x: rect.midX - (arrowWidth / 2), y: rect.maxY - (lineWidth / 2)))
            path.addLine(to: CGPoint(x: rect.midX + (arrowWidth / 2), y: rect.maxY - (lineWidth / 2)))
            return path
        }
    }
    
    struct ContentView: View {
    
        var body: some View {
            Color.yellow
                .frame(width: 300, height: 300)
                .overlay(alignment: .leading) {
                    Text("400mm ± 5")
                        .padding(.vertical, 4)
                        .background(Color.yellow)
                        .frame(maxHeight: .infinity)
                        .background {
                            Vector()
                                .stroke(lineWidth: 2)
                                .overlay(
                                    Vector().fill()
                                )
                        }
                        .padding(.leading, 10)
                }
        }
    }
    

    Vector