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 will be re-drawn appropriately even after the picture measurement adjustments comparable to might occur after gadget rotation.
Methods concerned embrace drawing the road in an overlay. Code additionally reveals getting picture measurement in pixels and picture view measurement in factors. These sizes are helpful in changing between completely different views.
import SwiftUI
struct ContentView: View {
@State non-public var line: [CGPoint] = [] // Line is an array of factors
@State non-public var pixelSize = PixelSize()
@State non-public var viewSize = CGSize.zero
@State non-public var startNewSegment = true
@State non-public var pixelPath = ""
var physique: some View {
VStack {
lineView() // Magic occurs right here
buttons()
information()
}
.padding()
.onAppear {
// Remark out subsequent line when 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 measurement 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 {
// Word: 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.peak
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.peak, 1)
// Preserve 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" items
let vw = viewSize.width
let vh = viewSize.peak
if vw > 0 && vh > 0 {
let pixelWidth = CGFloat(pixelSize.width)
let pixelHeight = CGFloat(pixelSize.peak)
let pixelPoints = line.map { CGPoint(x: pixelWidth * $0.x, y: pixelHeight * $0.y)}
pixelPath = "(pixelPoints)"
}
} label: {
Textual content("Pixel Path")
.padding()
}
}
}
@ViewBuilder
func information() -> some View {
Textual content("Picture WxL: (pixelSize.width) x (pixelSize.peak)")
Textual content("View WxL: (viewSize.width) x (viewSize.peak)")
Textual content("Line factors: (line.depend)")
if pixelPath != "" {
Textual content("Pixel Path: (pixelPath)")
}
}
}
// Auxiliary definitions and capabilities
struct PixelSize {
var width: Int = 0
var peak: Int = 0
}
extension UIImage {
var pixelSize: PixelSize {
if let cgImage = cgImage {
return PixelSize(width: cgImage.width, peak: cgImage.peak)
}
return PixelSize()
}
}
// SizeCalculator from: https://stackoverflow.com/questions/57577462/get-width-of-a-view-using-in-swiftui
struct SizeCalculator: ViewModifier {
@Binding var measurement: 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 {
measurement = proxy.measurement
}
.onChange(of: proxy.measurement) { // Added to deal with gadget rotation
measurement = proxy.measurement
}
}
)
}
}
extension View {
func saveSize(in measurement: Binding<CGSize>) -> some View {
modifier(SizeCalculator(measurement: measurement))
}
}