Send a Closure from NSViewRepresentable (or UIViewRepresentable) to ContentView?

I'm using NSTableView with NSViewRepresentable in my SwiftUI ContentView. I'm letting the user right-click on a table row such that the application will recognize the row number, which is achieved. The following is what I have so far.

struct ContentView: View {
    @State private var rowSelection = -1
    
	VStack {
		TableView(tableData: someData, selectedRow: $rowSelection)
	}
}

struct TableView: NSViewRepresentable {
    @Binding var tableData: [Dictionary<String, String>]
    @Binding var selectedRow: Int
    
    func makeNSView(context: Context) -> NSScrollView {
        let scrollView = NSScrollView(frame: .zero)
        let tableView = NSTableView()
        tableView.delegate = context.coordinator
        tableView.dataSource = context.coordinator
        
        let contextMenu = NSMenu()
        let copyRowMenuItem = NSMenuItem(title: "Copy Row", action: #selector(Coordinator.tableRowAction(_:)), keyEquivalent: "")
        contextMenu.addItem(copyRowMenuItem)
        copyRowMenuItem.target = context.coordinator
        tableView.menu = contextMenu
        
        scrollView.documentView = tableView
        scrollView.hasVerticalScroller = true
        scrollView.hasHorizontalScroller = true
        scrollView.autohidesScrollers = true
        return scrollView
    }
    
    func updateNSView(_ nsView: NSScrollView, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(tableData: $tableData, tableInfo: $tableInfo, selectedRow: $selectedRow, rowSelected: $rowSelected)
    }
    
    class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
        @Binding var tableData: [Dictionary<String, String>]
        @Binding var selectedRow: Int
        
        init(tableData: Binding<[Dictionary<String, String>]>, tableInfo: Binding<[PragmaModel]>, selectedRow: Binding<Int>, rowSelected: Binding<Bool>) {
            self._tableData = tableData
            self._rowSelected = rowSelected
        }
        
        func numberOfRows(in tableView: NSTableView) -> Int {
            return tableData.count
        }
        
        func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
            for i in 0..<tableInfo.count {
                let col = NSString(format: "%i", i) as String
                let identifier = NSString(format: "Column%i", i) as String
                if ((tableColumn?.identifier)!.rawValue == identifier) {
                    let data = tableData[row][col]
                    return data
                }
            }
            return nil
        }
        
        func tableViewSelectionDidChange(_ notification: Notification) {
            let tv = notification.object as! NSTableView
            if tv.selectedRow >= 0 {
                selectedRow = tv.selectedRow
            }
        }
        
        @objc func tableRowAction(_ sender: Any) {
            // closure //
        }
    }
}

The contextual menu works. Yet, the application needs to know when a row is clicked on. So I want to send a closure back to ContentView. How can I do that, por favor? Muchos thankos.

Answered by DTS Engineer in 798247022

@Tomato You could pass a closure from ContentView to your NSViewRepresentable and call it within the Coordinator. For example:

struct TableView: NSViewRepresentable {
    @Binding var tableData: [Dictionary<String, String>]
    @Binding var selectedRow: Int
    var tableRowAction: (() -> Void)
    
    func makeNSView(context: Context) -> NSScrollView {
        ...
    }
    
    func updateNSView(_ nsView: NSScrollView, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(tableData: $tableData, selectedRow: $selectedRow, tableRowAction: tableRowAction)
    }
    
    class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
        @Binding var tableData: [Dictionary<String, String>]
        @Binding var selectedRow: Int
        var tableRowAction: (() -> Void)
        
        init(tableData: Binding<[Dictionary<String, String>]>, selectedRow: Binding<Int>, tableRowAction: @escaping (() -> Void)) {
            self._tableData = tableData
            self._selectedRow = selectedRow
            self.tableRowAction = tableRowAction
        }
        
        ...
        
        @objc func tableRowAction(_ sender: Any) {
            tableRowAction()
        }
    }
} 

and within your SwiftUI View:

struct ContentView: View {
    @State private var rowSelection = -1
    
    var body: some View {
        VStack {
            TableView(tableData: someData, selectedRow: $rowSelection) {
                 ....
            }
        }
    }
}
Accepted Answer

@Tomato You could pass a closure from ContentView to your NSViewRepresentable and call it within the Coordinator. For example:

struct TableView: NSViewRepresentable {
    @Binding var tableData: [Dictionary<String, String>]
    @Binding var selectedRow: Int
    var tableRowAction: (() -> Void)
    
    func makeNSView(context: Context) -> NSScrollView {
        ...
    }
    
    func updateNSView(_ nsView: NSScrollView, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(tableData: $tableData, selectedRow: $selectedRow, tableRowAction: tableRowAction)
    }
    
    class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
        @Binding var tableData: [Dictionary<String, String>]
        @Binding var selectedRow: Int
        var tableRowAction: (() -> Void)
        
        init(tableData: Binding<[Dictionary<String, String>]>, selectedRow: Binding<Int>, tableRowAction: @escaping (() -> Void)) {
            self._tableData = tableData
            self._selectedRow = selectedRow
            self.tableRowAction = tableRowAction
        }
        
        ...
        
        @objc func tableRowAction(_ sender: Any) {
            tableRowAction()
        }
    }
} 

and within your SwiftUI View:

struct ContentView: View {
    @State private var rowSelection = -1
    
    var body: some View {
        VStack {
            TableView(tableData: someData, selectedRow: $rowSelection) {
                 ....
            }
        }
    }
}
Send a Closure from NSViewRepresentable (or UIViewRepresentable) to ContentView?
 
 
Q