Swift NSSearchField NSArrayController No Bindings
Created By:Debasis Das (May 2016)
In this post we will create a simple cocoa application to filter the data in a NSTableView using a NSSearchField without using Bindings
The NSArrayController will maintain the backup data in the form of its content and the NSArrayController->arrangedObjects will return the filtered contents.
// AppDelegate.swift // NSSearchField_ArrayController_NoBindings // Created by Debasis Das on 26/05/16. // Copyright © 2016 Knowstack. All rights reserved. import Cocoa @NSApplicationMain //App Delegate will be the delegate and datasource for the table view class AppDelegate: NSObject, NSApplicationDelegate,NSTableViewDelegate,NSTableViewDataSource,NSSearchFieldDelegate { @IBOutlet weak var window: NSWindow! @IBOutlet weak var tableView:NSTableView! @IBOutlet weak var searchField:NSSearchField! var contacts:[Person] = [] //Create an empty Array to hold the person records var personArrayController:NSArrayController = NSArrayController() func applicationDidFinishLaunching(aNotification: NSNotification) { self.tableView.setDelegate(self) self.tableView.setDataSource(self) self.searchField.delegate = self for item in self.dataArray(){ self.contacts.append(item as! Person) //Adding person objects to the contacts property } self.personArrayController.content = contacts //Setting the contents of the array controller as the recently created contacts. //Alternatively the array controllers content must be updated from the data fetched from db self.tableView.reloadData() } override func controlTextDidChange(obj: NSNotification) { var searchString = "" searchString = ((obj.object as? NSSearchField)?.stringValue)! let predicate = NSPredicate(format: "firstName contains %@ OR lastName contains %@ ",searchString,searchString) if searchString != ""{ self.personArrayController.filterPredicate = predicate } else{ self.personArrayController.filterPredicate = nil } self.tableView.reloadData() } //Table View Delegate and datasource methods func numberOfRowsInTableView(tableView: NSTableView) -> Int { return self.personArrayController.arrangedObjects.count } func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? { if let tColIdentifier = tableColumn?.identifier{ let str = self.personArrayController.arrangedObjects[row].valueForKey(tColIdentifier) return str } else{ return "" } } func applicationWillTerminate(aNotification: NSNotification) { // Insert code here to tear down your application } func dataArray()->NSMutableArray{ let arr = NSMutableArray() arr.addObject(Person.createPerson("Debasis", lName: "aabb")) arr.addObject(Person.createPerson("John", lName: "ccdd")) arr.addObject(Person.createPerson("Jane", lName: "eeff")) return arr } } class Person:NSObject{ var firstName:String = "" var lastName:String = "" class func createPerson(fName:String, lName:String)->Person{ let person = Person() person.firstName = fName person.lastName = lName return person } }
On entering a search query
Note: The Searches are case sensitive
The working code can be downloaded here NSSearchField_ArrayController_NoBindings
I would like to get your example working with Xcode 11 and swift 5. So far I have updated your code to the following:
//
// AppDelegate.swift
// NSSearchField_NSTableView_Programatically
//
// Created by Debasis Das on 27/05/16.
// Copyright © 2016 Knowstack. All rights reserved.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTableViewDelegate , NSTableViewDataSource {
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var searchField: NSSearchField!
var contacts:[Person] = []
var backUpContacts:[Person] = []
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
tableView.delegate = self
tableView.dataSource = self
for item in self.dataArray(){
self.contacts.append(item as! Person)
}
self.backUpContacts = self.contacts //Creating a backup of the contacts, else we can also us the
self.tableView.reloadData()
self.createMenuForSearchField()
}
func createMenuForSearchField(){
let menu = NSMenu()
menu.title = “Menu”
let allMenuItem = NSMenuItem()
allMenuItem.title = “All”
allMenuItem.target = self
allMenuItem.action = #selector(AppDelegate.changeSearchFieldItem(sender:))
let fNameMenuItem = NSMenuItem()
fNameMenuItem.title = “First Name”
fNameMenuItem.target = self
fNameMenuItem.action = #selector(AppDelegate.changeSearchFieldItem(sender:))
let lNameMenuItem = NSMenuItem()
lNameMenuItem.title = “Last Name”
lNameMenuItem.target = self
lNameMenuItem.action = #selector(AppDelegate.changeSearchFieldItem(sender:))
menu.addItem(allMenuItem)
menu.addItem(fNameMenuItem)
menu.addItem(lNameMenuItem)
self.searchField.searchMenuTemplate = menu
self.changeSearchFieldItem(sender: allMenuItem)
}
@objc func changeSearchFieldItem(sender:AnyObject){
(self.searchField.cell as? NSSearchFieldCell)?.placeholderString = sender.title
}
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return self.contacts.count
}
private func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? {
let identifier = tableColumn?.identifier
let str = self.contacts[row].value(forKey: identifier!.rawValue)
return str as AnyObject?
}
private func tableView(tableView: NSTableView, setObjectValue object: AnyObject?, forTableColumn tableColumn: NSTableColumn?, row: Int) {
self.contacts[row].setValue(object, forKey: (tableColumn?.identifier)!.rawValue)
}
func dataArray()->NSMutableArray{
let arr = NSMutableArray()
arr.add(Person.createPerson(fName: “Debasis”, lName: “Das”))
arr.add(Person.createPerson(fName: “John”, lName: “Doe”))
arr.add(Person.createPerson(fName: “Jane”, lName: “Doe”))
return arr
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
override func textDidChange(obj: NSNotification) {
if obj.object as AnyObject? === self.searchField {
let searchString = self.searchField.stringValue
var predicate:NSPredicate = NSPredicate()
if searchString.isEmpty{
self.contacts = self.backUpContacts
}
else{
if (self.searchField.cell as? NSSearchFieldCell)?.placeholderString == “All”{
predicate = NSPredicate(format: “firstName contains %@ OR lastName contains %@ “,searchString,searchString)
}
else if (self.searchField.cell as? NSSearchFieldCell)?.placeholderString == “First Name”{
predicate = NSPredicate(format: “firstName contains %@”,searchString)
}
else if (self.searchField.cell as? NSSearchFieldCell)?.placeholderString == “Last Name”{
predicate = NSPredicate(format: “lastName contains %@”,searchString)
}
self.contacts = (self.backUpContacts as NSArray).filtered(using: predicate) as! [Person]
}
self.tableView.reloadData()
}
}
}
class Person:NSObject{
var firstName:String = “”
var lastName:String = “”
class func createPerson(fName:String, lName:String)->Person{
let person = Person()
person.firstName = fName
person.lastName = lName
return person
}
}
At this point I only have one error on this line:
override func textDidChange(obj: NSNotification) that I changed from original:
override func controlTextDidChange(obj: NSNotification)
and I get the message method does not override any method in its super class .. I see that controlTextDidChange is a method of a text field object. So, that is why I changed it to textDidChange.
How do I get this example going?