Parse Data into NSTableView
Yes, tableviews with Cocoa are... complicated.
The absolute basics for getting content in the tableview is to set its datasource, which has to adopt NSTableViewDataSource
protocol. Then you have to differentiate between a view based and a cell based tableview. In the view based you can use a view you designed in Interface Builder, the cell based are older and simpler. You understand the numberOfRowsInTableView
function, so I proceed with the more complicated functions.
Cell based TableVies
For cell based tableViews, the second essential function## Heading ## is
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject!
When you just got it, it's pretty easy. As first parameter you get the tableView (only interesting if you use the same dataSource for multiple tableViews).
The second specifies the tableColumn. You can identify a column by using its identifier. You set this identifier in the InterfaceBuilder. Click on your tableview until a column is selected. Then set in the sidebar the Restoration ID.
As last parameter you get your row. You return an object of type AnyObject. I normally return a string, I don't know whether NSNumber is also valid.
A simple example implementation is the following:
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject!{
var result = ""
var columnIdentifier = tableColumn.identifier
if columnIdentifier == "number" {
result = "\(row+1)"
}
if columnIdentifier == "name" {
result = model.persons[row].name
}
if columnIdentifier == "lastName" {
result = model.persons[row].lastName
}
return result
}
When you want to set values, use
func tableView(tableView: NSTableView!, setObjectValue object: AnyObject!, forTableColumn tableColumn: NSTableColumn!, row: Int)
Object represents the new value you should transfer to your data model.
View based TableViews
In view based tableViews, the things lays different.
Here you have to design a custom view in a separate nib file. Then, you register the nib in your initializer.
let nib = NSNib(nibNamed: "Example", bundle: NSBundle.mainBundle())
self.tableView.registerNib(nib, forIdentifier: "Example")
This allows you to create instances of your view using makeViewWithIdentifier:owner:
Then you can configure your view (I like to subclasses NSView for this) and pass it back as result of
func tableView(tableView: NSTableView!, viewForTableColumn tableColumn: NSTableColumn!, row: Int) -> NSView!
Example implementation:
func tableView(tableView: NSTableView!, viewForTableColumn tableColumn: NSTableColumn!, row: Int) -> NSView!{
let view = tableView.makeViewWithIdentifier("Example", owner: self) as MyCustomView
view.field1.stringValue = model.persons[row].name
view.field2.stringValue = model.persons[row].lastName
view.field3.stringValue = "\(row+1)"
return view
}
Getting array objects into NSTableView
The important part of the code you need is the DataSource delegate functions:
extension ViewController : NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return devices.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
// 1 - get the device for this row
guard let device = devices[row] else {
return nil
}
// 2 - configure the cell with the device data
return nil
}
There is an example here on StackOverflow that should give a better example
Reloading NSTableView after downloading data
PFQuery
works asynchronously, the data is returned much later - in terms of computer speed – after viewDidLoad
exits.
Reload the table view in the block right after the array has been populated on the main thread.
var teachers = [Teacher]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.setDelegate(self)
tableView.setDataSource(self)
let query = PFQuery(className: "TeacherList")
query.findObjectsInBackgroundWithBlock { [unowned self] (objects, error) in
if let objects = objects {
for object in objects {
let teacher = Teacher()
teacher.name = object["name"] as! String
teacher.email = object["email"] as! String
teacher.subjectsTaught = object["subjectsTaught"] as! [String: String]
teacher.category = object["category"] as! String
teacher.uniqueID = object.objectId!
self.teachers.append(teacher)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
print(self.teachers)
}
}
Crash by adding data to NSTableView
As you are using Cocoa Bindings which is based on Key-Value Observing you have to put any line which affects the UI into a DispatchQueue.main
block
DispatchQueue.main.async {
self.Series = []
for (_,subJson):(String, JSON) in theJSONContent {
let serie = Serie(
theID: subJson["id"].stringValue,
serieName: subJson["seriesName"].stringValue,
serieAired: subJson["firstAired"].stringValue,
serieNetwork: subJson["network"].stringValue,
serieStatus: subJson["status"].stringValue
)
self.Series.append(serie)
}
self.serieTabla.reloadData()
}
Illegal NSTableView data source
The tutorial is written in Swift 2. Your code seems to be Swift 3.
The signature of numberOfRowsInTableView
is wrong. In Swift 3 it's
func numberOfRows(in tableView: NSTableView) -> Int {
return data.count
}
PS: It is preferable to connect delegate and data source in Interface Builder rather than in code.
How do I load an XML document into an NSTableView?
The answer given by shreyasva is close but somewhat misleading.
First, parsing the XML into an easily-managed Cocoa data structure is perfectly correct. For performance reasons, you shouldn't be tying your table's datasource directly to the XML. yan.kun's suggestion is certainly possible but if you have "more than a little" data, you very well could run into performance problems. I highly recommend just parsing the data into an NSArray of NSDictionary objects for longer data sets.
Second, Core Data is a bit overkill if you don't plan to persist the XML document in some other way or if you only have a handful of objects. Overkill by a long shot. It's also not necessary (and often not reasonable) to shoehorn every data structure in your app into Core Data without good reason. An NSDictionary instance will work just fine for caching the parsed data for consumption by a table view.
Third, there is no -tableView:cellForRowAtIndexPath: method. This seems to be confusing NSTableView with UITableView. Since you specified the Mac tag, look into the NSTableViewDataSource protocol. Cocoa Bindings is not "better than" or a "replacement for" the data source protocol. It's an "alternative to". You can either load your parsed data into an NSArrayController (an array of dictionaries, one per "record", for example) and bind the table columns to it (each column is bound to a key in the dictionaries in the array controller's arrangedObjects) or just use the (easy) table data source protocol that takes literally two minutes of copy/paste from the docs to get up and running.
Related Topics
Conflicting Definition of Swift Struct and Array
Swiftui Views with a Custom Init
Should Iboutlet Be Weak or Strong Var
Why Are Iboutlets Optionals After Swift 5 Migration
Swift: How Can String.Join() Work Custom Types
How to Filter Events Created for the Current Date in the Realm Swift
What Does the Swift 'Mutating' Keyword Mean
Create PDF of Dynamic Size with Typography Using Uiview Template(S)
Scenekit -- How to Get Animations for a .Dae Model
Enable + Disable Auto-Layout Constraints
Using Environmentobject in Watchos
Realitykit - Set Text Programmatically of an Entity of Reality Composer
Can't Create an Array of Types Conforming to a Protocol in Swift
Swift, Avaudiorecorder: Error 317: Ca_Debug_String: Inpropertydata == Null
Swift: How to Disable User Interaction While Touch Action Is Being Carried Out