How to set data dynamically in NSTableView mac? [duplicate] - swift

I want to show data in NSTableView. The number of columns is unspecified (could be any from 1 to ?? depending on the data), so I can't use Interface Builder.
So I initialize (with IB) my table to 1 column, and thereafter add new columns as required (then remove the no-longer needed 0-th column). To each added column I provide a unique identifier. So far so good.
I implement the tableView(-:viewForTableColumn:row) function, as shown below, but the makeViewWithIdentifier returns nil. What's the matter ?
If I detect the nil return, I create an instance of NSTableCellView with the proper identifier. But the data do not show in the table. What could be wrong ?
The code is below (with unnecessary lines removed) :
import Cocoa
class MyViewController: NSViewController {
#IBOutlet weak var dataTable: NSTableView!
}
var donnees: DataFrame = DataFrame() // Some table-like data with unspecified number of columns
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initData() // get the actual data
// Make the ad-hoc number of columns
if donnees.nbCol > 0 {
for k in 0..<donnees.nbCol {
let newColumn = NSTableColumn(identifier: idArray[k]) // idArray : [String] of unique identifiers
dataTable.addTableColumn(newColumn)
}
}
dataTable.removeTableColumn(dataTable.tableColumns[0]) // remove the original column, now unnecessary
}
}
extension MyViewController : NSTableViewDataSource {
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return self.donnees.nbRow
}
}
extension MyViewController : NSTableViewDelegate {
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let columnID = tableColumn!.identifier
var cellView: NSTableCellView
let cellViewTmp = tableView.makeViewWithIdentifier(columnID, owner: self)
if cellViewTmp == nil { // then create a new NSTableCellView instance
cellView = NSTableCellView(frame: NSRect(x: 0, y: 0, width: (tableColumn?.width)!, height: 20))
cellView.identifier = columnID
print("CellView créé pour id \(columnID) au rang \(row)")
} else {
cellView = cellViewTmp as! NSTableCellView
}
cellView.textField?.stringValue = "AAA"
return cellView
}
}

Bingo ! Thanks to Willeke I rewrote my code as follows :
var donnees: DataFrame = DataFrame() // Some table-like data with unspecified number of columns
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initData() // get the actual data
self.setTableColumns() // and prepare the columns accordingly
}
func setTableColumns() {
// In Interface Builder I've prepared a cell view in the 0-th tableColumn and set the identifier of this NSTableColumn to "ModelCellView"
let myCellViewNib = dataTable.registeredNibsByIdentifier!["ModelCellView"] // I save the cellView's Nib
// Make the ad-hoc number of columns
if donnees.nbCol > 0 {
for k in 0..<donnees.nbCol {
let newColumn = NSTableColumn(identifier: idArray[k]) // idArray : [String] of unique identifiers
dataTable.addTableColumn(newColumn)
dataTable.registerNib(myCellViewNib, forIdentifier: newColumn.identifier) // I register the above Nib for the newly added tableColumn
}
dataTable.removeTableColumn(dataTable.tableColumns[0]) // remove the original column, now unnecessary
}
}
extension MyViewController : NSTableViewDataSource {
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return self.donnees.nbRow
}
extension MyViewController : NSTableViewDelegate {
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let columnID = tableColumn!.identifier
let cellView = tableView.makeViewWithIdentifier(columnID, owner: self) as! NSTableCellView
cellView.textField?.stringValue = "theActualCellData"
return cellView
}
And it works perfectly as intended. Again, thanks to Willeke.

Related

Set the outlet of the item in TableCellView within the .xib file to the custom NSTableCellView subclass

I want to fill my NSTableView with content. Per table-cell-row are 3 items (2 NSTextFields and 1 NSImageView). For that I created a custom NSTableCellView where I want to set the #IBOutlets of the 3 Items, to set there the value for them. But when I try to set the referencing outlets, the only option is to create an action.
When I try to write #IBOutlet weak var personName: NSTextfield and then set the references, I can't because "xcode cannot locate the class in the current workspace"
When I create the NSTableViewinside a main.storyboard, I'm able to set the outlet references. So what is the different behavior between .storyboard and .xib?
When I try to connect the #IBOutlet with the Item "Person Name"
My NSViewController (owner of the .xib)
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView! //ref to tableView in xib
var persons:[Person] = [] //content to fill tableview
override func viewDidLoad() {
super.viewDidLoad()
persons.append(Person(name: "John", age: 23, piRef: "/Users/xy/Desktop/profilePic.png"))
persons.append(Person(name: "Marie", age: 26, piRef: "/Users/xy/Desktop/profilePic.png"))
tableView.delegate = self
tableView.dataSource = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return persons.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let tableCellView:personTableCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "defaultRow"), owner: self) as! personTableCell
//NSTableColumn in xib has id "defaulRow"
if let person:Person = persons[row] {
tableCellView.setPerson(person: person) //call method inside NSTableCellView-subclass to set item values
}
return tableCellView
}
}
The custom NSTableCellView subclass ("personTableCell")
class personTableCell: NSTableCellView {
var person:Person! = nil
//here should be:
//#IBOutlet weak var personName: NSTextField!
//#IBOutlet weak var personAge: NSTextField!
//#IBOutlet weak var personImg: NSImageView!
func setPerson(person: Person) {
self.person = person
self.personName = person.name
self.personAge = person.age
self.personImg = NSImage(byReferencingFile: person.profileImgRef)
}
}
I want to be able to add the item outlet references to my NSTableCellView-subclass.
It appears to me you're making this harder than it needs to be. makeView is giving you a reference to the cell. Therefore you can access its members directly. No need for outlets (which is why Xcode won't make them for you.)
I can't read your screenshots well enough to tell how the textfields are defined (old eyes), so I can only give you a generic example from a working demo of a custom cell class:
class DIYTableViewDelegate: NSObject, NSTableViewDelegate {
var count = 0 // counts the number of views actually created
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let id = tableColumn!.identifier
var view = tableView.makeView(withIdentifier: id, owner: nil) as? CustomTableCellView
if view == nil {
view = createCell(id)
count += 1
}
view!.textField!.stringValue = "\(id.rawValue) \(row) \(view!.count) \(count)"
view!.count += 1
return view
}
}
Also, it's customary in Swift to capitalize the first letter of types (classes, structures, enums, protocols) and lowercase methods & properties. Doesn't affect how your code compiles, but it helps other Swifties read it.
Here's another example that may help:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let vw = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? CustomTableCellView else { return nil }
vw.textField?.stringValue = String(pictures[row].dropLast(4))
vw.imageView?.image = NSImage(named: pictures[row])
return vw
}

Cocoa: Get Notified after Text Cell (NSTextField) is Edited & Start Editing Text Cell after Adding it in NSTableView in Swift 4?

I have made a simple demo using TableView here: https://github.com/deadcoder0904/TableViewDemo
I have used Defaults module as a dependency
My project looks like
All the code is in ViewController.swift as follows -
import Cocoa
import Defaults
extension Defaults.Keys {
static let dreams = Defaults.Key<Array<String>>("dreams", default: [
"Hit the gym",
"Run daily",
"Become a millionaire",
"Become a better programmer",
"Achieve your dreams"
])
}
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var table: NSTableView!
var dreams = defaults[.dreams]
var selectedRow:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
table.dataSource = self
table.delegate = self
}
override var acceptsFirstResponder : Bool {
return true
}
override func keyDown(with theEvent: NSEvent) {
if theEvent.keyCode == 51 {
removeDream()
}
}
func tableViewSelectionDidChange(_ notification: Notification) {
let table = notification.object as! NSTableView
selectedRow = table.selectedRow
}
func numberOfRows(in tableView: NSTableView) -> Int {
return dreams.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let dream = table.makeView(withIdentifier: tableColumn!.identifier, owner: self) as! NSTableCellView
dream.textField?.stringValue = dreams[row]
return dream
}
#IBAction func addTableRow(_ sender: Any) {
addNewDream()
}
#IBAction func removeTableRow(_ sender: Any) {
removeDream()
}
func addNewDream() {
dreams.append("Double Click or Press Enter to Add Item")
table.beginUpdates()
let last = dreams.count - 1
table.insertRows(at: IndexSet(integer: last), withAnimation: .effectFade)
table.scrollRowToVisible(last)
table.selectRowIndexes([last], byExtendingSelection: false)
table.endUpdates()
saveDreams()
}
func removeDream() {
if selectedRow >= dreams.count {
selectedRow = dreams.count - 1
}
if selectedRow != -1 {
dreams.remove(at: selectedRow)
table.removeRows(at: IndexSet(integer: selectedRow), withAnimation: .effectFade)
}
saveDreams()
}
func saveDreams() {
defaults[.dreams] = dreams
}
}
I want to do 2 things -
Get notified after Text Cell is edited so that I can save the changed data using Defaults module
After adding new Data by Clicking on the plus sign it adds Double Click or Press Enter to Add Item but what I want is I want to add Empty String which I can do with "" but I also want it to be focused & be editable so user can start entering text in it without having to Double Click or Press Enter.
I also want a solution in Swift 4 & not Objective-C. How to achieve this?
Use Cocoa Bindings, it's very powerful and saves a lot of boilerplate code.
Short tutorial:
Edit: To take full advantage of KVC the data source must be an NSObject subclass with dynamic properties
Create a simple class Dream (the description property is optional)
class Dream : NSObject {
#objc dynamic var name : String
init(name : String) { self.name = name }
override var description : String { return "Dream " + name }
}
In the view controller declare the data source array
var dreams = [Dream]()
and replace var selectedRow:Int = 0 with
#objc dynamic var selectedIndexes = IndexSet()
Go to Interface Builder
Select the table view, press ⌥⌘7 to go to the Bindings Inspector.
Bind Selection Indexes to View Controller Model Key Path selectedIndexes.
Press ⌥⌘6 and connect the dataSource (by drag&drop) to the view controller () .
Select the text field File 1 in Table Cell View in the table column. The easiest way is to ⌃⇧click in the text field area.
Press ⌥⌘7 and bind Value to Table Cell View Model Key Path objectValue.name (!)
In the view controller populate the data source array in viewDidLoad ( I don't know that framework so I leave it out) and reload the table view.
override func viewDidLoad() {
super.viewDidLoad()
let dreamNames = ["Hit the gym", "Run daily", "Become a millionaire", "Become a better programmer", "Achieve your dreams"]
dreams = dreamNames.map{Dream(name: $0)}
table.reloadData()
}
Delete acceptsFirstResponder
Delete tableViewSelectionDidChange
Delete tableView:viewFor:row:
Add
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return dreams[row]
}
Replace addNewDream with
func addNewDream() {
let last = dreams.count
dreams.append(Dream(name: "Double Click or Press Enter to Add Item"))
table.insertRows(at: IndexSet(integer: last), withAnimation: .effectGap)
table.scrollRowToVisible(last)
table.selectRowIndexes([last], byExtendingSelection: false)
saveDreams()
}
Replace removeDream() with
func removeDream() {
guard let selectedRow = selectedIndexes.first else { return }
dreams.remove(at: selectedRow)
table.removeRows(at: IndexSet(integer: selectedRow), withAnimation: .effectFade)
saveDreams()
}
To save the array when the text was edited afterwards you have to implement the delegate method controlTextDidEndEditing(_:)
override func controlTextDidEndEditing(_ obj: Notification) {
saveDreams()
}
and in Interface Builder connect the delegate of the text field in the table view to the view controller.

Swift - Get row index with checkbox in NSTableView [duplicate]

This question already has answers here:
Get button's row in view based table
(5 answers)
Closed 4 years ago.
I'm learning Cocoa in Swift. I created a NSTableView with Viewbased.
Simple tableview
I also connected the checkbox action to ViewController. But when I clicked the checkbox, it printed -1 instead of the row index. I have to select the row first then click the checkbox to get the right index number. Are there anyway to get the row index with every single checkbox or button on each row? Here is my code:
import Cocoa
let data: [String] = ["Apple", "Microsoft", "IBM", "Tesla", "SpaceX",
"Boeing" , "Nasa"]
class ViewController: NSViewController, NSTableViewDelegate,
NSTableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
self.table.delegate = self
self.table.dataSource = self
self.table.reloadData()
// Do any additional setup after loading the view.
}
#IBOutlet weak var table: NSTableView!
#IBAction func CheckClicked(_ sender: Any) {
print(self.table.selectedRow)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
return data.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn:
NSTableColumn?, row: Int) -> NSView? {
if (tableColumn?.identifier)!.rawValue == "NameColumn"
{
if let cell = tableView.makeView(withIdentifier:
NSUserInterfaceItemIdentifier(rawValue: "NameColumn"), owner: self)
as? NSTableCellView
{
cell.textField?.stringValue = data[row]
return cell
}
}
else if (tableColumn?.identifier)!.rawValue == "CheckColumn"
{
if let cell = tableView.makeView(withIdentifier:
NSUserInterfaceItemIdentifier(rawValue: "CheckColumn"), owner: self)
as? NSButton
{
return cell
}
}
return nil
}
func tableViewSelectionDidChange(_ notification: Notification) {
print(self.table.selectedRow)
}
}
This is what you are looking for, but a better implementation would be to use the action with a NSTableCellView subclass.
#IBAction func CheckClicked(_ sender: NSButton) {
// print(self.table.selectedRow)
let row = table.row(for: sender)
print("Button row \(row)")
}
I can only create a subclass based on NSButton.
class myCustomView: NSButton{
#IBOutlet weak var CheckButton: NSButtonCell!
}
Although I can't change the title of these button cell.
if (tableColumn?.identifier)!.rawValue == "CheckColumn"
{
if let cell = tableView.makeView(withIdentifier:
NSUserInterfaceItemIdentifier(rawValue: "CheckColumn"), owner: self)
as? myCustomView
{
cell.CheckButton.title = data[row]
return cell
}
}
I don't know why Xcode doens't let me create a subclass based on NSTableCellView.

Columns added programmatically to NSTableView not recognised in Delegate

I may be lost in a glass of water but I can't seem to be able to add columns to a NSTableView that are then recognised in the NSTableViewDelegate. I create a table in IB with one column and give the column a string identifier. The I add the other columns in the View Controller:
override func viewDidLoad() {
super.viewDidLoad()
for columnIndex in 0..<blotter!.singleOutput[0].parameter.count {
let tmpParam = blotter!.singleOutput[0].parameter[columnIndex]
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: tmpParam.columnID))
column.title = tmpParam.label
column.width = CGFloat(80)
column.minWidth = CGFloat(40)
column.maxWidth = CGFloat(120)
blotterOutputTable.addTableColumn(column)
}
blotterOutputTable.delegate = self
blotterOutputTable.dataSource = self
blotterOutputTable.target = self
blotterOutputTable.reloadData()
}
The NSTableViewDataSource returns the correct number of rows. The problem I have is in the NSTableViewDelegate:
extension OutputsViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var text: String = ""
var cellIdentifier: String = ""
guard let item = blotter?.singleOutput[row] else { return nil }
// 1. LABELS COLUMN
// ================
if tableColumn?.identifier.rawValue == "dealColumn" {
let myParameter = item.parameter.index(where: {$0.columnID == "BBTickColumn"})
text = item.parameter[myParameter!].value as! String
cellIdentifier = "dealColumn"
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
else { return nil }
} // END OF LABLES COLUMN (FIRST ONE)
else { // THIS IS WHERE THE PROBLEM IS
let myParameter = item.parameter.index(where: {$0.columnID == tableColumn?.identifier.rawValue } )
let (_, valueAsText) = item.parameter[myParameter!].interfaceItems()
text = valueAsText
cellIdentifier = item.parameter[myParameter!].columnID
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
else { return nil } // DEBUGGER PARAMETER ARE FROM HERE
}
}
}
The first column is the one I created in IB with its identifier. That works. The problem I have is in the else statement (which does not check for a column identifier). Below are the parameters as I see them in the debugger window when I stop the program after the cell creation failed
tableView NSTableView 0x000000010ebf9df0
tableColumn NSTableColumn? 0x0000600000895770
row Int 0
self DataBaseManager.OutputsViewController 0x0000600000102b50
text String "FLAT"
cellIdentifier String "directionColumn"
item DataBaseManager.BlotterOutputs 0x000060000002c240
myParameter Array.Index? 0
valueAsText String "FLAT"
cell (null) (null) (null)
tableColumn NSTableColumn? 0x0000600000895770
tableColumn?.identifier NSUserInterfaceItemIdentifier? some
_rawValue _NSContiguousString "directionColumn" 0x000060000104d200
Swift._SwiftNativeNSString _SwiftNativeNSString
_core _StringCore
You can see that cellIdentifier and the tableColumn?.identifier.rawvalue are the same string (as it should be). I cannot understand then why the cell is not created. Any help is mostly welcome and let me know if this is not clear. Thanks
must register nibs identifiers as in this code:
import Cocoa
class MultiColumnTable: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
var list = [[String]](), header=[String]()
var tableView : NSTableView? = nil
var nColumns : Int = 0
func genID(col : Int) -> NSUserInterfaceItemIdentifier { // generate column ID
return NSUserInterfaceItemIdentifier(rawValue: String(format: "Col%d", col))
}
func setContent(header: [String], list : [[String]]) {
self.header = header
self.list = list
self.nColumns = list[0].count
if tableView != nil {
tableView?.reloadData()
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
func createColumns() {
func addColumn(col:Int, header:String) {
let tableColumn = NSTableColumn(identifier: genID(col: col))
tableColumn.headerCell.title = header
self.tableView!.addTableColumn(tableColumn)
}
// create columns and register them in NIB
// IB: tableColumn[0] identifier ( NSTableColumn to "Col0" )
if let myCellViewNib = tableView.registeredNibsByIdentifier![NSUserInterfaceItemIdentifier(rawValue: "Col0")] {
for col in 0..<nColumns { // table should have 1 col in IB w/Ident 'Col0'
addColumn(col: col, header: header[col])
tableView.register(myCellViewNib, forIdentifier: genID(col: col)) // register the above Nib for the newly added tableColumn
}
tableView.removeTableColumn(tableView.tableColumns[0]) // remove the original Col0
}
}
self.tableView = tableView
createColumns()
return list.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let column = tableView.tableColumns.firstIndex(of: tableColumn!)!
tableColumn?.headerCell.title=header[column];
if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as? NSTableCellView {
cell.textField?.stringValue = list[row][column]
cell.textField?.textColor = NSColor.blue
return cell
}
return nil
}
func tableViewSelectionDidChange(_ notification: Notification) {
}
}

Is it possible to create a view-based NSTableView purely in code?

I have successfully created a cell-based NSTableView purely in code. I would like to make the cells a little more interesting and I have read that I need to create a view-based NSTableView.
I have been tutorials like this.
The rest of my UI is entirely in code. I have been trying to do the same for this tableview without much luck.
Here is how I am defining the TableView — I need to stop registering the Nib and I am not sure how:
let nib = NSNib(nibNamed: "TransactionCellView", bundle: NSBundle.mainBundle())
tableOfTransactions.registerNib(nib!, forIdentifier: "TransactionCellView")
tableOfTransactions.headerView = nil
tableOfTransactions.setDelegate(self)
tableOfTransactions.setDataSource(self)
tableOfTransactions.reloadData()
Here is my stub code for each cell:
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?{
var testCell = NSView()
testCell.frame = NSRect(x: 0, y: 0, width: 300, height: 200)
return testCell
}
Any pointers or suggestions on how to achieve this would be much appreciated!
Your implementation of -tableView(_:viewForTableColumn:row:) should look something like this:
func tableView(tableView: NSTableView,
viewForTableColumn
tableColumn: NSTableColumn?,
row: Int) -> NSView? {
var retval: NSView?
if let spareView = tableView.makeViewWithIdentifier("CodeCreatedTableCellView",
owner: self) as? NSTableCellView {
// We can use an old cell - no need to do anything.
retval = spareView
} else {
// Create a text field for the cell
let textField = NSTextField()
textField.backgroundColor = NSColor.clearColor()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.bordered = false
textField.controlSize = NSControlSize.SmallControlSize
// Create a cell
let newCell = NSTableCellView()
newCell.identifier = "CodeCreatedTableCellView"
newCell.addSubview(textField)
newCell.textField = textField
// Constrain the text field within the cell
newCell.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("H:|[textField]|",
options: [],
metrics: nil,
views: ["textField" : textField]))
newCell.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("V:|[textField]|",
options: [],
metrics: nil,
views: ["textField" : textField]))
textField.bind(NSValueBinding,
toObject: newCell,
withKeyPath: "objectValue",
options: nil)
retval = newCell
}
return retval
}
In the case where your table contains hundreds of rows, Cocoa will attempt to reuse views that have already been created, but are no longer on screen. The first part of this snippet uses an NSTableView method to look for such a view. If none is found, you need to create one from scratch.
If you've got no reason not to, you should use an instance (or subclass) of NSTableCellView as your view. It doesn't add much to NSView, but one of its key features is that it retains a reference to the model that the view represents (set by -tableView(_:objectValueForTableColumnRow:row:)). In this example I've used this feature to set the string value of the text field using bindings.
The other thing to note is that you should give your view an identifier that matches the identifier that you gave to the NSTableColumn in which the view will sit. Doing so allows your table view to make use of the reusable-view feature discussed above.
This was quite useful in my attempt to program scrolling tables without IB & Nibs. Here's my toy version, updated to Swift 4.2.
The custom cell view subclass is only there to let me see that cells actually get re-used, which they do:
import Cocoa
class DIYTableViewDataSource: NSObject, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 25
}
}
class CustomTableCellView: NSTableCellView {
var count = 1
}
func createCell(_ id: NSUserInterfaceItemIdentifier) -> CustomTableCellView {
// Create a text field for the cell
let textField = NSTextField()
textField.backgroundColor = NSColor.clear
textField.translatesAutoresizingMaskIntoConstraints = false
textField.isBordered = false
// Create a cell
let cell = CustomTableCellView() // Doing this to see that cells get re-used
cell.identifier = id
cell.addSubview(textField)
cell.textField = textField
// Constrain the text field within the cell
textField.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
textField.heightAnchor.constraint(equalTo: cell.heightAnchor).isActive = true
textField.bind(NSBindingName.value, to: cell, withKeyPath: "objectValue", options: nil)
return cell
}
class DIYTableViewDelegate: NSObject, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let id = tableColumn!.identifier
var view = tableView.makeView(withIdentifier: id, owner: nil) as? CustomTableCellView
if view == nil {
view = createCell(id)
}
view!.textField!.stringValue = "\(id.rawValue) \(row) \(view!.count)"
view!.count += 1
return view
}
}
Swift 5
You need to implement delegate method of NSTableView. No need to register any nib files 😉
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let view = CustomView()
view.configure(with: row)
return view
}
Just create instance of you custom view (subclass of NSView).
It can looks like this:
class CustomView: NSView {
private let label = NSTextField()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init coder has not been implemented")
}
func setupView() {
self.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 8)
])
}
func configure(with row: Int) {
label.stringValue = String(describing: row + 1)
}
}