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

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

Related

Set identifier of table cell view programmatically

I have table view like this (in a mac cocoa application):
In the leftmost panel you can see that I have set the identifier of the Table Cell View to "1". That's fine if you just have 2 columns, once the number goes up, this approach will become cumbersome. Can I do this programmatically?
Here is an example:
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
private var dataModel = DataModel()
private var answer = 0
private var keyData: (Int, [Int]) = (0, []) {
didSet {
tbl.reloadData()
}
}
#IBOutlet weak var questionIndex: NSTextField!
#IBOutlet weak var tbl: NSTableView!
#IBAction func replay(_ sender: Any) {
dataModel = DataModel()
questionIndex.stringValue = "0:"
answer = 0
updateModel()
}
#IBAction func forward(_ sender: NSButton) {
if sender.tag == 1 {
answer += keyData.0
}
updateModel()
}
func updateModel() {
let group = dataModel.nextGroup()
if let g = group {
self.keyData = g
let s = questionIndex.stringValue
questionIndex.stringValue = String(Int(String(s.characters.dropLast()))! + 1) + ":"
return
}
let alert = NSAlert()
alert.messageText = "You did have \(answer) on your mind, didn't you?"
alert.runModal()
}
override func viewDidLoad() {
super.viewDidLoad()
for (n, col) in tbl.tableColumns.enumerated() {
col.identifier = String(n)
}
updateModel()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return keyData.1.count / 8 + 1
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let colId = tableColumn!.identifier
let colIndex = Int(colId)!
let index = (row * 8) + colIndex
let cell = tbl.make(withIdentifier: colId, owner: self) as! NSTableCellView
if 0 <= index && index < keyData.1.count {
cell.textField!.integerValue = keyData.1[index]
} else {
cell.textField!.stringValue = ""
}
return cell
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
I have assigned the cell identifiers by hand, and made them identical the corresponding column index, so as to creating a mapping between the cell id and the 2D array (which is the underlying data model) column index. The app is running fine, I just don't like assigning these IDs by click-and-point.
The full project can be found here: https://github.com/kindlychung/MysteriousNum
Create custom cell and add init to it using following lines.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
and register this cell class as.
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "customCell")
also dequeueReusableCell using same cell like:
tableView.dequeueReusableCell(withIdentifier: "customCell",for: indexPath) as! CustomCell

swift array read/write data to file

I'd like to read data from an array and write data to a file and viceversa. The file itself has a known path.
The BTTableCell class manages and populates the tableView.
I'm using dati_Riga class in order to have a single array. I thought it would be easier to read and save data using WriteToFile in order to write data and NSMutableArray to read them, but I'm having trouble.
In the following code snippet of the ViewController file you can see functions I wrote in order to open and save data which don't work.
Where did I get wrong? My idea is to save data using BTTableCell but I wasn't able to make it work.
Data structure:
import Cocoa
class BPTableCell:NSTableCellView {
#IBOutlet weak var item_IconaFile: NSImageView!
#IBOutlet weak var item_NomeFile: NSTextField!
#IBOutlet weak var item_PathFile: NSTextField!
#IBOutlet weak var item_MD5: NSTextField!
#IBOutlet weak var item_SHA1: NSTextField!
}
class dati_Riga {
var dati_Riga_item_IconaFile: NSImage!
var dati_Riga_item_NomeFile: String!
var dati_Riga_item_PathFile: String!
var dati_Riga_item_MD5: String!
var dati_Riga_item_SHA1: String!
init (dati_Riga_item_IconaFile: NSImage, dati_Riga_item_NomeFile: String, dati_Riga_item_PathFile: String, dati_Riga_item_MD5: String, dati_Riga_item_SHA1: String)
{
self.dati_Riga_item_IconaFile = dati_Riga_item_IconaFile
self.dati_Riga_item_NomeFile = dati_Riga_item_NomeFile
self.dati_Riga_item_PathFile = dati_Riga_item_PathFile
self.dati_Riga_item_MD5 = dati_Riga_item_MD5
self.dati_Riga_item_SHA1 = dati_Riga_item_SHA1
}
}
Extract from "ViewController.swift":
import Cocoa
import Foundation
import AppKit
import CryptoSwift
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableview: NSTableView!
var item_IconaFile = [NSImage]()
var item_NomeFile = [String]()
var item_PathFile = [String]()
var item_MD5 = [String]()
var item_SHA1 = [String]()
var miei_Dati_Riga : [dati_Riga] = [dati_Riga(dati_Riga_item_IconaFile: NSImage(), dati_Riga_item_NomeFile: "", dati_Riga_item_PathFile: "", dati_Riga_item_MD5: "", dati_Riga_item_SHA1: "")]
override func viewDidLoad() {
super.viewDidLoad()
self.miei_Dati_Riga.removeAtIndex(0)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
// MARK: Funzioni relative la TableView
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let result : BPTableCell = tableView.makeViewWithIdentifier(tableColumn!.identifier, owner: self) as! BPTableCell
result.item_IconaFile.image = item_IconaFile[row]
result.item_NomeFile.stringValue = item_NomeFile[row]
result.item_PathFile.stringValue = item_PathFile[row]
result.item_MD5.stringValue = item_MD5[row]
result.item_SHA1.stringValue = item_SHA1[row]
return result
}
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
let num_Righe = item_NomeFile.count
return num_Righe
}
// MARK: Apri e Salva file
#IBAction func Save_File(sender: NSMenuItem) {
let salva_File = NSSavePanel()
salva_File.extensionHidden = true
salva_File.canSelectHiddenExtension = true
salva_File.allowedFileTypes = ["cpa"]
salva_File.beginWithCompletionHandler { (result:Int) -> Void in
if result == NSFileHandlingPanelOKButton {
let save_URL_File = salva_File.URL
let mio_Array: NSArray = self.miei_Dati_Riga
print(self.miei_Dati_Riga.count)
print(self.miei_Dati_Riga[0])
if mio_Array.writeToFile((save_URL_File?.path)!, atomically: true) {
print("File Salvato")}
else {
print("File Non Salvato")
}
}
}
}
#IBAction func Open_File(sender: NSMenuItem) {
let apri_File = NSOpenPanel()
apri_File.allowedFileTypes = ["cpa"]
apri_File.beginWithCompletionHandler { (result:Int) -> Void in
if result == NSFileHandlingPanelOKButton {
let contenuto_File = NSMutableArray (contentsOfURL: apri_File.URL!)
self.tableview.reloadData()
}
}
}

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.

Populate an NSTableView using an array - bind table

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()
}

Resources