I have a NSTableView with 2 columns bound with a custom type (SelectedFiles
) array as File Name
and File Path
, after clicking the header, I want it to sort the data in ascending / descending order, I tried these codes with NSSortDescriptor
:
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
tableView.tableColumns[0].sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
let selectedFilesArray = NSMutableArray(array: selectedFiles)
selectedFilesArray.sort(using: tableView.sortDescriptors) // Signal SIGABRT
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
My custom collection for the data in table view:
struct SelectedFiles: CustomStringConvertible {
let fileName: String
let filePath: String
var description: String {
return "\(fileName) at path \(filePath)"
}
}
var selectedFiles: [SelectedFiles] = []
It turns out it doesn't work at all, IDK if its anything wrong with my code or I'm missing something.
So, I came up with this awkward solution:
var tableViewSortingOrder = ComparisonResult.orderedAscending
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
switch tableViewSortingOrder {
case .orderedAscending:
tableViewSortingOrder = .orderedDescending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
default:
tableViewSortingOrder = .orderedAscending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
tableView.reloadData()
}
}
After I changed to this solution, it worked perfectly as it switches swiftly between ascending / descending order. But, when it comes to deleting objects in the collection, it throws Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
when I'm trying to delete multiple objects from both collection and table view with some specific files.
So, I'm thinking if I should change a way of achieving this header sorting thing by using NSSortDescriptor
(use the old-fashioned way by correcting my first method) in order to get away from this issue, I have to admit that my second way is a bit of awkward (is more like a plan C).
I've red through multiple StackOverflow posts on this topic and I tried all of their ways, especially this one, I am not using CoreData which its solutions does not work for my situation.
Anyone can help point out the way please? 😊
I red the guide to NSTableView from Apple Developer Site and few other StackOverflow posts, I found myself a workable solution for Swift 4:
I set the sortDescriptorPrototype
to fileNameSortDescriptor
in viewDidLoad()
under ViewController
class.
class ViewController: NSViewController {
override func viewDidLoad()
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare))
let tableColumn = tableView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileNameColumn"))!
tableColumn.sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
And then I added an inheritance from NSObject
and inserted @objcMembers
to prevent warning: Object <#object#> of class '<#class#>' does not implement methodSignatureForSelector: -- trouble ahead
from occurring and then cause Signal SIGABRT
while calling selectedFiles.sort(using: tableView.sortDescriptors)
(Reference: Object X of class Y does not implement methodSignatureForSelector in Swift).
@objcMembers class SelectedFiles: NSObject {
let fileName: String
let filePath: String
override var description: String {
return "\(fileName) at path \(filePath)"
init(fileName: String, filePath: String) {
self.fileName = fileName
self.filePath = filePath
}
}
Here's the code for tableView(_:sortDescriptorsDidChange:)
in NSTableViewDataSource
:
extension ViewController: NSTableViewDataSource {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
var selectedFilesArray = NSArray(array: selectedFiles)
selectedFilesArray = selectedFilesArray.sortedArray(using: tableView.sortDescriptors) as NSArray
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
Now, everything works perfectly fine.