Home > database >  Drawing arrows between images macOS
Drawing arrows between images macOS

Time:04-06

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.

enter image description here

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))
        }
    }
}
  • Related