I'm working on a SwiftUI view that should show images loaded from disk with arrows between them. The structure of my view is basically this:
ScrollView
- HStack
-- ForEach
--- GeometryReader
---- Image
My thought process is that I can just use the GeometryReader to find the current position of each image in the ScrollView and then draw a line between two Images using a Path. Am I at least on the right track? I've also noticed using GeometryReader kills performance in a major way and do not think this is the correct way to get the position of each Image unless I'm just using it incorrectly.
Here's the code that I have so far:
import SwiftUI
struct ScreenshotView: View {
let screenshots = getScreenshots()
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(screenshots, id: \.self) { screenshot in
GeometryReader { geo in
// have to use NSImage to load from disk
if let nsImage = NSImage(
contentsOf: screenshot.path) {
Button(action: {
print("Trying to open: ", screenshot.path)
NSWorkspace.shared.open(screenshot.path)
}) {
Image(nsImage: nsImage)
.resizable()
.frame(width: 50, height: 100)
.aspectRatio(contentMode: .fit)
.padding()
.coordinateSpace(name: "image")
}
.buttonStyle(PlainButtonStyle())
.help("\(geo.frame(in: .global).midX)")
}
}
.frame(width: 50, height: 100)
.padding()
}
}
}
.padding()
}
}
Thank you for your help!
CodePudding user response:
You don't need GeometryReader
. Just put the arrow between the images in the HStack
. If you don't want spacing, adjust it in the HStack
, e.g. spacing: 0
.
The Shape
is a single view in itself, which does not need absolute position points, but sizes itself in regards to the parent view, or like in this example in the size given by .frame
.
struct ContentView: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<10) { _ in
Rectangle().fill(.gray)
.frame(width: 50, height: 50)
ArrowShape()
.stroke(lineWidth: 2)
.frame(width: 50, height: 20)
}
}
}
.padding()
}
}
struct ArrowShape: Shape{
func path(in rect: CGRect) -> Path {
let delta = rect.height / 2 // height of arrow tip legs
return Path{ path in
path.move(to: CGPoint(x: 0, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
path.move(to: CGPoint(x: rect.maxX - delta, y: rect.midY - delta))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX - delta, y: rect.midY delta))
}
}
}