Populate an NSTableView using an array - bind table - macos

Helo !
I have a problem with display some data from an array to a NSTableView .
I have my info saved in core data. I take this info and add to an array, but when i try to add this info in table, all what i receive is a table with correct numbers of rows, but all have the same implicit text : "TableView Cell" .
For every table column i've set an identifier , and my binds, i think, they are made correctly.
Here is my code for Table Controller :
#IBAction func reload(sender: NSButton) {
tableView.reloadData()
}
func getItemsFromDataBase() {
let fetchRequest = NSFetchRequest(entityName: "MyData")
// Create a sort descriptor object that sorts on the "title"
// property of the Core Data object
let sortDescriptor = NSSortDescriptor(key: "nume", ascending: true)
// Set the list of sort descriptors in the fetch request,
// so it includes the sort descriptor
fetchRequest.sortDescriptors = [sortDescriptor]
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [MyData] {
for var i = 0 ; i < fetchResults.count ; i++ {
objects.append(fetchResults[i]) //Get data from database and put in array
}
}
}
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
getItemsFromDataBase()
showNames()
return objects.count
}
func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? {
let identifier = tableColumn?.identifier
println(identifier!)
if let propertyEnum = ArrayDisplayProperty(rawValue: identifier!) {
let cellView = tableView.makeViewWithIdentifier(identifier!, owner: self) as NSTableCellView
let object = objects[row]
switch propertyEnum {
case .Nume:
cellView.textField!.stringValue = object.nume
case .Prenume:
cellView.textField!.stringValue = object.prenume
case .Varsta:
cellView.textField!.stringValue = "\(object.varsta)"
case .Localitate :
cellView.textField?.stringValue = object.localitate
case .Major:
cellView.textField?.stringValue = object.major == 1 ? "Yes" : "No"
}
println(object.nume)
return object.varsta
}
return 0
}

Have you bounded only that table column or also that columns Text Field and Text Field Cell?
Both of these Value need to be bounded to Table Cell View with model key path (let say you have entity Person with attribute 'name') objectValue.name. Xcode shows some warning 'cannot resolve the entered key path', but I think that can be ignored.
So example, if I have that entity Person with only one attribute name, my bindings looks like following with Array Controller:
Table View -> Table Content -> Content -> Bind to Array Controller with controller key arrangedObjects
Name column -> Value > Bind to Array Controller with controller key arrangedObjects and Model Key Path name
Text Field -> Value -> Bind to Table Cell View with model key path objectValue.name
Text Field Cell -> Value -> Bind to Table Cell View with model key path objectValue.name

SWIFT Code:--
Follow the steps for populating the data in NSTableView through bindings:-
1) Connect your ArrayController to your FileOwner/Appdelegate:-
2) Select your TableColumns and bind to ArrayController -> arrangedObjects -> Key
3) Now in your method follow below code:-
#IBOutlet weak var arrController : NSArrayController!
var yourArray: NSMutableArray = []
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
var dataArray = [["FirstName": "Debasis", "LastName": "Das"],
["FirstName": "Nishant", "LastName": "Singh"],
["FirstName": "John", "LastName": "Doe"],
["FirstName": "Jane", "LastName": "Doe"],
["FirstName": "Mary", "LastName": "Jane"]];
for var i = 0; i < dataArray.count; i++
{
yourArray.addObject(dataArray[i])
}
arrController.rearrangeObjects()
}

Related

Display Image View in Table View conditionally in Swift

I'm trying to display an image in a table view cell view on the condition of a Boolean value.
The Boolean is a representation of the state of an object of the class "Book" where the objects are initialized:
class Book: NSObject, Codable {
#objc dynamic var author: String
#objc dynamic var title: String
#objc dynamic var lentBy: String
#objc dynamic var available: Bool {
if lentBy == "" {
return true
} else {return false}
}
init(author: String, title: String, lentBy: String) {
self.author = author
self.title = title
self.lentBy = lentBy
}
}
If the String lentBy is not specified, the Bool available returns true: no one has lent the book and hence it should be available. Binding the available object to the table view, the respective table view cell displays either 1 or 0. Instead of 1 or 0 I would like it to display an image: NSStatusAvailable or NSStatusUnavailable.
Have a look at this: https://i.imgur.com/xkp0znT.png.
Where the text field "Geliehen von" (lent by) is empty, the status is 1 and should display the green circle; otherwise a red circle. The green circle you see now is simply dragged into the table cell view and is non-functional. But this is the idea.
Now I'm wondering how to display the respective image view instead of the Bool 1 or 0.
The table view is constructed with the interface builder in a storyboard. If I'm trying to make changes to it programmatically, nothing gets display in the table view anymore. I suppose this is due to the set bindings. Removing the bindings just for the last column doesn't work. This is how I tried it (without implementation of the image view; I don't know how to do that programmatically):
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if tableColumn == tableView.tableColumns[2] {
let cellIdentifier = "statusCellID"
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: self) as? NSTextField
if let cell = cell {
cell.identifier = NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)
cell.stringValue = books[row].lentBy
}
return cell
}
return nil
}
What's the best solution to achieve this? Could I somehow, instead of a Bool, directly return the respective, e.g. CGImage types for lentBys representation available?
You are using Cocoa Bindings. This makes it very easy.
In Interface Builder drag an NSTableCellView with image view into the last column and delete the current one.
Delete the text field and set appropriate constraints for the image view.
Rather than viewForColumn:Row implement
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return books[row]
}
Extend the model with an image property which is driven by KVO
class Book: NSObject, Codable {
#objc dynamic var author: String
#objc dynamic var title: String
#objc dynamic var lentBy: String
#objc dynamic var available: Bool {
return lentBy.isEmpty
}
#objc dynamic var image: NSImage {
return NSImage(named: (lentBy.isEmpty) ? NSImage.statusAvailableName : NSImage.statusUnavailableName)!
}
static func keyPathsForValuesAffectingImage() -> Set<String> { return ["lentBy"] }
init(author: String, title: String, lentBy: String) {
self.author = author
self.title = title
self.lentBy = lentBy
}
}
In Interface Builder bind the Value of the image view of the table cell view to Table Cell View > objectValue.image

Select and return selection of multiple rows in a single column Swift OSX table [duplicate]

I have a NSTableView with one column. I would like to print the row number of the row that the user has clicked on. I am not sure where I should start with this. Is there a method for this?
You can use the selectedRowIndexes property from the tableView in the tableViewSelectionDidChange method in your NSTableView delegate.
In this example, the tableView allows multiple selection.
Swift 3
func tableViewSelectionDidChange(_ notification: Notification) {
if let myTable = notification.object as? NSTableView {
// we create an [Int] array from the index set
let selected = myTable.selectedRowIndexes.map { Int($0) }
print(selected)
}
}
Swift 2
func tableViewSelectionDidChange(notification: NSNotification) {
var mySelectedRows = [Int]()
let myTableViewFromNotification = notification.object as! NSTableView
let indexes = myTableViewFromNotification.selectedRowIndexes
// we iterate over the indexes using `.indexGreaterThanIndex`
var index = indexes.firstIndex
while index != NSNotFound {
mySelectedRows.append(index)
index = indexes.indexGreaterThanIndex(index)
}
print(mySelectedRows)
}
Use -selectedRowIndexes
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableView_Class/#//apple_ref/occ/instp/NSTableView/selectedRowIndexes
Then you can use those indexes to grab the data from your dataSource
(typically an array)

Present dictionary in several NSTableviews

I'm a beginner to cocoa and I've been trying to make a simple app for Mac using swift programming language, but I'm stuck and can't find a solution.
I want to present a data from dictionary in two or more tableViews, where the first table will show key, and the second table will show value.
For example, I have a dictionary
var worldDict:NSDictionary = ["Africa":["Egypt", "Togo"],"Europe": ["Austria", "Spain"]]
I can present all continents in the first table, but I can't find out how to make second table to display countries from continent I choose in the first table.
My ViewController is a DataSource and Delegate for both tables.
extension ViewController: NSTableViewDataSource {
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if tableView == continentTable {
return self.worldDict.valueForKey("Continent")!.count
} else if tableView == countryTable {
return self.worldDict.valueForKey("Continent")!.allKeys.count
}
return 0
}
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
var cell = tableView.makeViewWithIdentifier(tableColumn!.identifier, owner: self) as! NSTableCellView
if tableView == self.continentTable {
let continent: AnyObject? = wordlDict.valueForKey("Continent")
var keys = continent!.allKeys
cell.textField?.stringValue = keys[row] as! String
} else if tableView == self.countryTable {
var countriesOfContinent: AnyObject? = worldDict.valueForKey("Continent")?.valueForKey("Africa")!
cell.textField?.stringValue = countriesOfContinent?.allKeys[row] as! String
}
return cell
}
}
Here I present data from dictionary in tables, but separately, and can't figure out how to make them work together.
Also I know how to get the number of row that has been selected
extension ViewController: NSTableViewDelegate {
func tableViewSelectionDidChange(notification: NSNotification) {
let continentSelected = rowSelected()
}}
func rowSelected() -> Int? {
let selectedRow = self.continentTable.selectedRow
if selectedRow >= 0 && selectedRow < self.worldDict.valueForKey("Continent")!.count {
return selectedRow
}
return nil
}
Part of the problem is that you're relying on the ordering of the keys returned by allKeys() to be reliable, which it's not. You need to keep a separate array of continents. It can basically be a copy of whatever allKeys() returned on one occasion, but you should not keep calling allKeys() each time.
In numberOfRowsInTableView(), for the countries table, you want to return the number of countries in the selected continent:
} else if tableView == countryTable {
if let selectedContinentRow = rowSelected() {
let selectedContinent = continentsArray[selectedContinentRow]
return self.worldDict[selectedContinent].count
}
return 0
}
For tableView(_:viewForTableColumn:row:), you want to return an element from the selected continent's array of countries:
} else if tableView == self.countryTable {
if let selectedContinentRow = rowSelected() {
let selectedContinent = continentsArray[selectedContinentRow]
return self.worldDict[selectedContinent][row]
}
}
Also, whenever the selected continent changes, you need to tell the countries table to reload its data:
func tableViewSelectionDidChange(notification: NSNotification) {
// ... whatever else ...
let tableView = notification.object as! NSTableView
if tableView == continentTable {
countryTable.reloadData()
}
}

Edit data in the model according to the user's input in view-based NSTableView

I have a view-based NSTableView where all the cells are editable. I need to refresh the data from the model every time the user modifies a textField from the view.
All the doc I find is related to the cell-based NSTableView.
Does anyone have a clue about this?
EDIT:
I'm using data source to populate this NSTableView.
This is the code of the Controller of the NSTableView
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet var globalView: NSView!
#IBOutlet var songsTableView: NSTableView!
var tableContents = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for (song) in songManager.songs {
var obj = Dictionary<String,String>()
obj["title"] = song.title
obj["artist"] = song.artist
tableContents.addObject(obj)
}
songsTableView.reloadData()
}
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return tableContents.count
}
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?{
var obj = tableContents[row] as Dictionary<String,String>
let column = tableColumn?.identifier
var cellView = tableView.makeViewWithIdentifier(column!, owner: self) as NSTableCellView
if column == "title" {
cellView.textField?.stringValue = obj["title"]!
}
if column == "artist" {
cellView.textField?.stringValue = obj["artist"]!
}
cellView.textField?.editable = true
return cellView
}
}
And this is the code of the class that manages the data.
var songManager = SongManager()
struct song {
var title = "No name"
var artist = "No artist"
}
class SongManager: NSObject {
var songs = [song]()
func addSong(title: String, artist: String) {
songs.append(song(title: title, artist: artist))
}
}
I have not touched the row that the storyboard creates by default, so I guess it contains a single NSTextField.
I get to display the data, but cannot detect when the user tried to modify a textfield.
Given how things are currently set up, the simplest approach is probably to connect the text field's action selector to an action method on a target, such as your controller. You can do that in IB or in your tableView(_:viewForTableColumn:row:) method.
In that action method, you can call songsTableView.rowForView(sender) to determine which row was edited. Each column's text field would have a different action method, such as changeTitle() or changeArtist(), so that you know which column was edited. (You could also use songsTableView.columnForView(sender), then get the table column by using the index in songsTableView.tableColumns[col], and checking the returned column's identifier. For that, you would assign specific identifiers to the columns rather than letting IB assign them automatically.)
Once you have the row, you look up your dictionary using var obj = tableContents[row] as Dictionary<String,String> and set the value for the key appropriate to the action method (or column identifier) to the sender's stringValue.

Why does this code in Swift produce an empty row on NSTableView?

I'm (obviously) new to Swift, and I'm new to Cocoa programming in general. When I build this code and run it, it automatically inserts an empty row on the NSTableView for peopleList[0], so when I go to add a new person, they show up in the second row, and the first remains empty. I'm at a loss as to why this is. I know I can probably remove the blank row programmatically before inserting the first row of data, but I'd like to know the root cause of this if possible.
Here is my code:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet var window: NSWindow
// Table View Outlet
#IBOutlet var tableViewData : NSTableView
// Text Field Cell Outlets
#IBOutlet var firstNameTextField : NSTextField
#IBOutlet var lastNameTextField : NSTextField
#IBOutlet var ageTextField : NSTextField
#IBOutlet var netWorthTextField : NSTextField
// declare an array of multiple dictionaries
var peopleList = [Dictionary <String, String>()]
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
func numberOfRowsInTableView(aTableView: NSTableView!) -> Int
{
let numberOfRows:Int = getDataArray().count
return numberOfRows
}
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject!
{
var newString = getDataArray().objectAtIndex(row).objectForKey(tableColumn.identifier)
return newString;
}
func getDataArray() -> NSArray {
return peopleList
}
// When button is hit, add NSTextField values to array and refresh the tableview
#IBAction func addFieldDataToArray(sender : AnyObject) {
peopleList.append(["FirstName": firstNameTextField.stringValue, "LastName": lastNameTextField.stringValue, "Age": ageTextField.stringValue, "NetWorth": netWorthTextField.stringValue])
println(peopleList)
tableViewData.reloadData()
}
}
This is happening because of the way you're initializing and appending to peopleList. Upon initialization, peopleList is an array of Dictionary<String, String> and contains a single empty dictionary:
> var peopleList = [Dictionary <String, String>()]
[[String : String]] = 1 value {
[0] = {}
}
After adding a person, it looks like this -- you've appended a new person's information, but that empty initial dictionary is still there.
[[String : String]] = 2 values {
[0] = {}
[1] = {
[0] = {
key = "NetWorth"
value = "About 10 bucks"
}
[1] = {
key = "LastName"
value = "Schmoe"
}
[2] = {
key = "Age"
value = "37"
}
[3] = {
key = "FirstName"
value = "Joe"
}
}
}
I'd recommend starting out with peopleList completely empty -- no empty row to worry about:
> var peopleList: [Dictionary<String, String>] = []
[[String : String]] = 0 values

Resources