NSTableView: dynamic columns

I need to display a tableView without knowing all of the columns at compile time.

The names, formats (and of course values) of some columns are given by a query during executing time.


So I thought I have to override the make function of tableView as follow:

override func make (withIdentifier identifier: String, owner: Any?) -> NSView? {

if identifier != "ligne24" &&{ // "ligne24" is one of the identifier(s column I can't build on compile time

return super.make(withIdentifier: identifier, owner: owner)

}

let view = NSTableCellView()

let txt = myNewTextField()

txt.identifier = identifier

view.subviews.append(txt)

return view

}


Of course, in another place of my code, I have added this:

column = NSTableColumn (identifier: "ligne24")

column.title = "some title"

tableView.addTableColumn(column)


and in

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?

I put the correct value in the textfield


at executing time, table is ok, I can see the column named ligne24, but this column is empty even if while debugging I ca see I put the correct value in the cell


Any Idea?

I can think of two possible reasons why this might happen:


1. You subclass NSTableView to override "makeView (withIdentifier:owner:)", but you didn't actually cause an instance of the subclass to be created. Can you verify that your custom "makeView" method is actually executed?


Note that the method is "makeView", not "make", but I assume that's just a typo in your post.


2. You create a NSTableCellView, and you add a text field subview, but you have no code to position the text field within the cell, nor any code to set the size of the text view. If its size is zero, or it's positioned outside the parent view, the column will look empty.


Your technique for creating these cells is not a good one. Instead, you should do the following:


— Create a XIB file that contains a prototype NSTableCellView for the "ligne24" column.


— For the File's Owner pseudo-object in the XIB, specify the class of your NSTableViewDelegate object (e.g. a view controller class).


— Set the cell view's identifier to "ligne24".


— Add a text view control to the cell, and position/size it using autolayout constraints (or however you choose to lay out your column cells).


— Register the name of the XIB using NSTableView's "register(_:forIdentifier:)" method. (Actually, it's the name of the NIB file built from the XIB source.)


If you set up the XIB properly, you don't need to override "makeView (withIdentifier:owner:)". The table view will create and manage the cell views for you, just like it does for the other columns.

the name of the method is make, it's not a typo. If I use makeView, Xcode writes

'makeView(withIdentifier:owner:)' has been renamed to 'make(withIdentifier:owner:)'


You was right when you told me that I had no code to position the text field within the cell. So width and height were nul and it did'nt appeared. So problem is solved.


But I wanted to tell you that I can't use a XIB file fro the columns of this NSTableView. My colum called ligne24 was just an example. I really don't know neither the names of the columns, nor how many columns will be displayed by this table.That's why I needed to override the make method.


for all my other tables, I use a XIB file, of course.


Thank's

>> problem is solved

Good to hear.


>> the name of the method is make, it's not a typo


In Swift 4, the name of the method is "makeView", so perhaps you are using Swift 3? Anyway, it's not important, since you're clearly overriding the correct method.


>> for all my other tables, I use a XIB file

>> I can't use a XIB file fro the columns of this NSTableView


I think we are talking about two different things. I was talking about using a XIB file for just the NSTableCellViews, not the entire table. In principle, a cell view XIB can supply cells for one column or multiple columns, and one column can use multiple cell views.


Your code to create the cells programmatically will (does!) work, but with the limitation that cells aren't automatically re-used like normal cells. This will have some impact on performance, but if the table is fairly small this may not matter to you.

Oh I begin to understand.

But how can I use a XIB File jst for the NSTableCellViews?

So, back to what I said before. When you have a cell in a XIB file, you must register the name of the XIB using NSTableView's "register(_:forIdentifier:)" method before is populated with data. This is typically done in some "viewDidLoad" method.


In your particular scenario, the difficulty comes from managing the cell "identifier" property, which controls the reuse of existing cells. In your "tableView(_:viewFor:row:)" method, it doesn't actually matter what your table column identifier is, so long as you use it to choose a cell with a suitable cell identifier. The default is to have them match, because it's easier to keep track of them that way, but it doesn't have to be so.


However, in that delegate method, when you invoke "makeView(withIdentifier:owner:)" specifying a cell identifier that has your XIB file registered against it, the actual cell must have that same identifier. (Otherwise, the documentation says, "makeView(withIdentifier:owner:)" will return nil.) In case you're keeping track, we're now up to 3 flavors of identifier:


1. The column identifier.

2. The cell identifier under which a XIB is registered.

3. The cell identifier in the cell inside the registered XIB.


It's that complicated, I guess, because your XIB file could contain multiple cell prototypes, and be registered mutliple times, so it uses the identifiers of the cells inside the XIB to figure out which one you want.


This is going to be a problem for you if you really have an unlimited number of extra table columns with an unlimited number of extra cell types, because the above mechanism requires the existence of prototype cells with all identifiers that you're going to use. (That can be one XIB file with all the extra cells, or multiple XIB files with one cell each, or anything in between.) If this is true for you, you can't use XIB files, and you will have to create your cells via code (like you showed in your original post), but you should set "view.identifier", not "txt.identifier". Setting view.identifier should let the table view manage and reuse those cells. (If the cells need reconfiguring each time they're reused, you can override "prepareForReuse" in your cell subclass. BTW, setting txt.identifier does nothing useful.)


OK, if you brain hasn't shut down yet, there's more:


— You don't really have to use separate XIB files at all. You can put all your prototypes (assuming the total number of cell types is fixed) in the table itself, using IB. Just make sure they each have a unique identifier. The trick here is to use the table column identifer to choose the cell identifier, rather than to be the cell identifer, in your "tableView(_:viewFor:row:)" method.


— Whether you have prototype cells in the table (in IB) or in separate XIB files, you can use the same cell type (including the same identifier) for as many columns as you want, if the appearance of the cell is appropriate for the column. You don't need different prototypes with different identifiers for each column. Again, you just choose the correct cell identifier for each table column in your "tableView(_:viewFor:row:)" method. This means, BTW, that the same cell instance might be re-used for different columns at different times in your table's lifetime.

I think it"s OK noew, but it was a big amount of work

First I convert to Swift4, and it was not a peace of cake.

Then, lucky me, I don't know the number of columns of my NSTableView, but tthey are all of the same type.

So I put a prototype of this NSTextfielCell in the window's XIB, and subclass the makeView method to call the "extra columns cells" with the proper cell identifier written in the XIB

My brain brain hasn't shut down, but it was close


Thank's

Happy New Year


So I built this "special NSTableView", with columns using a XIB File. I can fill the values of the column, I also can enter values in the cells in order to Write in my Database.


But when I have the table in a non editing state and I click on a row, I have the following error:


Assertion failure in -[NSTableRowView viewAtColumn:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1561.20.106/TableView.subproj/NSTableRowView.m:2243

2018-01-01 06:47:59.404600+0100 testinput[35250:3816628] [General] An uncaught exception was raised

2018-01-01 06:47:59.404620+0100 testinput[35250:3816628] [General] Invalid parameter not satisfying: column >= 0 && column < _columnCount


I tried everything (in the limit of my knowledge)

I even override the func func view(atColumn column: Int, row: Int, makeIfNecessary: Bool), but it's never called.


Any Idea?

You should be able to catch the exception by setting an exception breakpoint in your project (Command-8 in Xcode, use the "+" at the bottom left). What you're primarily interested in is the backtrace (which should also be logged after the above assertion messages, even without the breakpoint), to see if it's something triggered by your code.


But it's a strange error to get. Have any columns ever been removed by the time you get to this point?

NSTableView: dynamic columns
 
 
Q