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.
@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) {
....
}
}
}
}