Search code examples
foreachswiftuiviewlocationcenter

SwiftUI: Drawing a line between two views in a list / how to determine the center location of a view?


I want to draw a line between a selected source-View and a selected target-View when I press the "connect" button. As far as I understand I need the (center?) CGPoint of these views... How can I determine the center CGPoint of a view inside a forEach-HStack-list?

Elements from both lists will be removed/added.

enter image description here

struct ContentView: View {
    @State private var selectedTarget: Int = 0
    @State private var selectedSource: Int = 3
    @State private var isConnected = false
    
    
    var body: some View {
       
        ZStack {
            VStack {
                HStack {
                    ForEach(0..<6) { index in
                        Text("Target \(index)")
                            .padding()
                            .foregroundColor(.white)
                            .background(selectedTarget == index ? .red : .black)
                            .onTapGesture {
                                selectedTarget = index
                            }
                    }
                }
                
                List {
                    EmptyView()
                }
                .frame(width: 400, height: 400, alignment: .center)
                .border(.black, width: 2)
        
                HStack {
                    ForEach(0..<6) { index in
                        Text("Source \(index)")
                            .padding()
                            .foregroundColor(.white)
                            .background(selectedSource == index ? .orange : .black)
                            .onTapGesture {
                                selectedSource = index
                            }
                    }
                }
                Button(isConnected ? "CUT" : "CONNECT") {
                    isConnected.toggle()
                }
                .padding()
            }
        }
        
    }
    
}

Solution

  • Here is the rough demo code and take an idea from this code. You can achieve this by first finding the position of the Text by GeometryReader and draw the path between those points.

    struct ContentView: View {
        @State private var selectedTarget: Int = 0
        @State private var selectedSource: Int = 3
        @State private var isConnected = false
        
        @State private var targetCGPoint: CGPoint = .zero
        @State private var sourceCGPoint: CGPoint = .zero
        
        var body: some View {
            
            ZStack {
                if isConnected {
                    getPath()
                }
                VStack {
                    HStack {
                        ForEach(0..<6) { index in
                            GeometryReader { geo in
                            Text("Target \(index)")
                                .padding()
                                .foregroundColor(.white)
                                .background(selectedTarget == index ? Color.red : Color.black)
                                .onTapGesture {
                                    let size = geo.size
                                    targetCGPoint = CGPoint(x: geo.frame(in: .global).origin.x + (size.width / 2), y: geo.frame(in: .global).origin.y)
                                    print("Target Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
                                    selectedTarget = index
                                }
                            }
                        }
                    }
                    
                    // Removed list for demo
                    Spacer()
                    
                    HStack {
                        ForEach(0..<6) { index in
                            GeometryReader { geo in
                                Text("Source \(index)")
                                    .padding()
                                    .foregroundColor(.white)
                                    .background(selectedSource == index ? Color.orange : Color.black)
                                    .onTapGesture {
                                        let size = geo.size
                                        sourceCGPoint = CGPoint(x: geo.frame(in: .global).origin.x + (size.width / 2), y: geo.frame(in: .global).origin.y)
                                        print("Source Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
                                        selectedSource = index
                                    }
                            }
                            
                        }
                    }
                    Button(isConnected ? "CUT" : "CONNECT") {
                        isConnected.toggle()
                    }
                    .padding()
                }
            }
            
        }
        
        func getPath() -> some View {
            Path { path in
                path.move(to: sourceCGPoint)
                path.addLine(to: targetCGPoint)
            }
            .stroke(Color.blue, lineWidth: 10)
        }
    }
    

    enter image description here