iOS Development

ios – How do I draw a line on a picture in SwiftUI

Draw line on image

The next code lets one draw a line path on high of the picture as proven above. The factors of the road are normalized to the picture in order that the road might be re-drawn accurately even after the picture dimension adjustments akin to might occur after machine rotation.

Methods concerned embody drawing the road in an overlay. Code additionally exhibits getting picture dimension in pixels and picture view dimension in factors. These sizes are helpful in changing between totally different views.

import SwiftUI

struct ContentView: View {
    @State personal var line: [CGPoint] = []     // Line is an array of factors

    @State personal var pixelSize = PixelSize()
    @State personal var viewSize = CGSize.zero

    @State personal var startNewSegment = true
    @State personal var pixelPath = ""

    var physique: some View {
        VStack {
            lineView() // Magic occurs right here
            buttons()
            data()
        }
        .padding()
        .onAppear {
            // Remark out subsequent line when now not wanted for debugging
            line = [CGPoint(x: 0, y: 0), CGPoint(x: 0.25, y: 0.25), CGPoint(x: 0.5, y: 0)]
        }
    }

    @ViewBuilder
    func lineView() -> some View {
        let largeConfig = UIImage.SymbolConfiguration(pointSize: 100, weight: .daring, scale: .giant)
        let picture = UIImage(systemName: "bolt.sq.", withConfiguration: largeConfig)! // Your picture right here

        Picture(uiImage: picture)
            .resizable()
            .aspectRatio(contentMode: .match)
            .saveSize(in: $viewSize) // NOTE: that is the display dimension for the picture!
            .overlay { // Draw line in overlay.  You actually wish to do that.
                if line.depend > 1 {
                    Path { path in
                        path.transfer(to: screenPoint(line[0]))
                        for i in 1..<line.depend {
                            path.addLine(to: screenPoint(line[i]))
                        }
                    }
                    .stroke(.blue, lineWidth: 5)
                }
            }
            .onAppear {
                pixelSize = picture.pixelSize
            }
        // Construct up line by including factors in straight line segments.
        // Enable level to be added by faucet
            .onTapGesture { location in
                line.append(limitPoint(location))
            }
        // Or enable new level to be added from drag
            .gesture(
                DragGesture()
                    .onChanged { worth in
                        if line.depend < 1 {
                            // If no factors, add "startLocation" level (extra correct than merely location for first level)
                            line.append(limitPoint(worth.startLocation))
                        } else if line.depend < 2 || startNewSegment {
                            // Add level at present place
                            line.append(limitPoint(worth.location))
                            startNewSegment = false
                        } else {
                            // Be aware: Now in mode the place we're changing the final level
                            line.removeLast()
                            line.append(limitPoint(worth.location))
                            startNewSegment = false
                        }
                    }
                    .onEnded { worth in
                        line.removeLast()
                        line.append(limitPoint(worth.location))
                        startNewSegment = true
                    }
            )
    }

    func screenPoint(_ level: CGPoint) -> CGPoint {
        // Convert 0->1 to view's coordinates
        let vw = viewSize.width
        let vh = viewSize.top
        let nextX = min(1, max(0, level.x)) * vw
        let nextY = min(1, max(0, level.y)) * vh
        return CGPoint(x: nextX, y: nextY)
    }

    func limitPoint(_ level: CGPoint) -> CGPoint {
        // Convert view coordinate to normalized 0->1 vary
        let vw = max(viewSize.width, 1)
        let vh = max(viewSize.top, 1)

        // Hold in bounds - even when dragging exterior of bounds
        let nextX = min(1, max(0, level.x / vw))
        let nextY = min(1, max(0, level.y / vh))
        return CGPoint(x: nextX, y: nextY)
    }

    @ViewBuilder
    func buttons() -> some View {
        HStack {
            Button {
                line.removeAll()
                pixelPath = ""
            } label: {
                Textual content("Clear")
                    .padding()
            }
            Button {
                // Present line factors in "Pixel" models
                let vw = viewSize.width
                let vh = viewSize.top
                if vw > 0 && vh > 0 {
                    let pixelWidth = CGFloat(pixelSize.width)
                    let pixelHeight = CGFloat(pixelSize.top)
                    let pixelPoints = line.map { CGPoint(x: pixelWidth * $0.x, y: pixelHeight * $0.y)}
                    pixelPath = "(pixelPoints)"
                }
            } label: {
                Textual content("Pixel Path")
                    .padding()
            }
        }
    }

    @ViewBuilder
    func data() -> some View {
        Textual content("Picture WxL: (pixelSize.width) x (pixelSize.top)")
        Textual content("View  WxL: (viewSize.width) x (viewSize.top)")
        Textual content("Line factors: (line.depend)")
        if pixelPath != "" {
            Textual content("Pixel Path: (pixelPath)")
        }
    }
}

// Auxiliary definitions and capabilities

struct PixelSize {
    var width: Int = 0
    var top: Int = 0
}

extension UIImage {
    var pixelSize: PixelSize {
        if let cgImage = cgImage {
            return PixelSize(width: cgImage.width, top: cgImage.top)
        }
        return PixelSize()
    }
}

// SizeCalculator from: https://stackoverflow.com/questions/57577462/get-width-of-a-view-using-in-swiftui

struct SizeCalculator: ViewModifier {

    @Binding var dimension: CGSize

    func physique(content material: Content material) -> some View {
        content material
            .background(
                GeometryReader { proxy in
                    Shade.clear // we simply need the reader to get triggered, so let's use an empty colour
                        .onAppear {
                            dimension = proxy.dimension
                        }
                        .onChange(of: proxy.dimension) { // Added to deal with machine rotation
                            dimension = proxy.dimension
                        }
                }
            )
    }
}

extension View {
    func saveSize(in dimension: Binding<CGSize>) -> some View {
        modifier(SizeCalculator(dimension: dimension))
    }
}

Credit: www.ismmailgsm.com

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button