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

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