Swift NSTableview convert obj-c to swift - macos

Hey the original apple guide in obj-c is here,
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TableView/PopulatingView-TablesProgrammatically/PopulatingView-TablesProgrammatically.html#//apple_ref/doc/uid/10000026i-CH14-SW6
example 3.2
I have been following through the guide and can not get the program to work, i am getting these errors:
'AnyObject?' does not have a member named 'identifier'
line: result.identifier = "HelloWorld"
and
error: cannot convert the expression's type 'AnyObject?' to type '()'
Line: return result
What am i doing wrong?
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn, row: Int){
var names: [String] = ["Anna","Alex","brain","jack","gg"]
var result: AnyObject? = tableView.makeViewWithIdentifier("HelloWorld", owner: self)
if result == nil
{
// Create the new NSTextField with a frame of the {0,0} with the width
// of the table.
result = NSTextField(frame: NSRect())
// set identifier of the NSTextField instance to HelloWorld.
result.identifier = "HelloWorld"
}
result = names[row]
return result
}
New Working code
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn, row: Int) -> NSTextField{
var names = ["Anna","Alex","Brain","Jack","Hello"]
var result: NSTextField? = tableView.makeViewWithIdentifier("HelloWorld", owner: self) as? NSTextField
if result == nil
{
// Create the new NSTextField with a frame of the {0,0} with the width
// of the table.
result = NSTextField(frame: NSRect())
// set identifier of the NSTextField instance to HelloWorld.
result?.identifier = "HelloWorld"
}
result!.bezeled = false
result?.stringValue = names[row]
return result!
}

Here:
var result: AnyObject? = tableView.makeViewWithIdentifier("HelloWorld", owner: self)
you have declared the result variable as an optional AnyObject - and this protocol doesn't have an identifier property, which is instead a property of NSTextField.
What you should do is declare that variable using the correct type:
var result: NSTextField? = tableView.makeViewWithIdentifier("HelloWorld", owner: self) as? NSTextField
which can also be shortened thanks to type inference:
var result = tableView.makeViewWithIdentifier("HelloWorld", owner: self) as? NSTextField
Side note: I think your if is checking for the opposite condition:
if result != nil
whereas I think it should be:
if result == nil
Side note 2
There's no return value declared in your function, but you are returning an instance of NSTextField.
You should change the signature to:
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn, row: Int) -> NSView
and change the return statement to:
return result!
Note that result is defined as optional, but looking at the method implementation, at the end a non-nil value is available, so I've used forced unwrapping and declared the method to return a non-optional value. Of course feel free to change that if you want.

Related

Swift 3 Table Column Width Last Column Not Working

Refer to my attached image.
Notice the last column for some reason is always short on the width. I can't for the life of me figure out why or how to fix this?
Here is my code for my controller.
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var theTableview: NSTableView!
var data:NSArray = [""] //#JA - This is used
override func viewDidLoad() {
super.viewDidLoad()
//First remove all columns
let columns = self.theTableview.tableColumns
columns.forEach {
self.theTableview.removeTableColumn($0)
}
//self.theTableview?.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
for index in 0...100 {
let column = NSTableColumn(identifier: "defaultheader")
if(index != 0){
column.title = "Month \(index)"
}else{
column.title = "Factors"
}
self.theTableview.addTableColumn(column)
}
// Do any additional setup after loading the view.
data = ["Group 1","Group 2","Group 3","Group 4"]
self.theTableview.reloadData()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
return data.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.make(withIdentifier: "defaultcell", owner: nil) as? NSTableCellView {
cell.textField?.stringValue = data.object(at: row) as! String
return cell
}
return nil
}
#IBAction func startsimulation(_ sender: NSButton) {
//Recalculates the data variable for updating the table.
data = ["group1","group2"]
theTableview.reloadData()
}
}
NSTableColumn has a property resizingMask and NSTableView has a property columnAutoresizingStyle. Both can be set in IB or in code. Figure out a configuration so the columns behave like you want. The default Column Sizing of the table view in IB is 'Last Column Only', switching to 'None' will fix your problem.
Another solution is setting minWidth of the columns.

NSTableView Cell with Identifier keep giving nil

I am working on building MacOS app. I am trying to make table view that updates the cell when I press add button.
Following is my code:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let identifier = tableColumn?.identifier as NSString?
if ( identifier == "NameCell")
{
var result: NSTableCellView
let cell = tableView.make(withIdentifier: "NameCell", owner: self) as! NSTableCellView
cell.textField?.stringValue = self.data[row].setting!
return cell
}
else if (identifier == "SettingCell")
{
if let cell = tableView.make(withIdentifier: "SettingCell", owner: self) as? NSTableCellView {
cell.textField?.stringValue = self.data[row].setting!
return cell
}
}
return nil
}
However, the line let cell = tableView.make(withIdentifier: "NameCell", owner: self) as! NSTableCellView is keep failing because it returns nil
fatal error: unexpectedly found nil while unwrapping an Optional value
NameCell is from
Can anyone please help me find a way to solve this problem?
For anyone else who comes here with this same question when trying to make an NSTableView fully programmatically: makeView(withIdentifier:owner:) WILL return nil unless a corresponding NIB exists for the given identifier:
NSTableView documentation:
If a view with the specified identifier can’t be instantiated from the nib file or found in the reuse queue, this method returns nil.
Likewise, the 'owner' param is a NIB-specific concept. In short: you cannot use this method if populating your NSTableView with cells programmatically.
In this answer, I detail the Swift code to produce an NSTableCellView programmatically: https://stackoverflow.com/a/51736468/5951226
However, if you don't want all the features of an NSTableViewCell, note that you can return any NSView in tableView(_:viewFor:row:). So you could, as per the CocoaProgrammaticHowtoCollection, simply write:
let cell = NSTextField()
cell.identifier = "my_id" // Essential! Allows re-use of the instance.
// ... Set any properties you want on the NSTextField.
return cell
You should set the "Identifier" with "NameCell" in the NSTableCellView. And your codes should simplified as follow since the column's identifier won't change for ever:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var result: NSTableCellView
let cell = tableView.make(withIdentifier: "NameCell", owner: self) as! NSTableCellView
cell.textField?.stringValue = self.data[row].setting!
return cell
}
references settings in XCode Interface Builder:

Populating NSTableView from array in Swift (without XCode)

I'm trying to develop for OS X without the use of XCode in Swift. I'm running into extreme headaches trying to populate NSTableView from any kind of data source. Here is my code:
import Cocoa
class StupidDataSource: NSObject, NSTableViewDataSource, NSTableViewDelegate {
var lib:[NSDictionary] = [["a": "1", "b": "2"],
["a": "3", "b": "4"]]
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return lib.count
}
func tableView(tableView: NSTableView,
objectValueForTableColumn tableColumn: NSTableColumn?,
row: Int) -> AnyObject? {
let result = lib[row].objectForKey(tableColumn!.identifier)
return result
}
}
func make_table(window: NSWindow,
_ size:(x:Int, y:Int, width:Int, ht:Int),
_ title:String,
_ data:StupidDataSource
)-> NSTableView {
let tableContainer = NSScrollView(frame:NSMakeRect(0, 0, 400, 400))
let tableView = NSTableView(frame: NSMakeRect(0, 0, 400, 400))
tableView.setDataSource(data)
tableView.reloadData()
tableContainer.documentView = tableView
tableContainer.hasVerticalScroller = true
window.contentView!.addSubview(tableContainer)
return tableView
}
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification)
{
set_window_args(window, 400, 400, "Ass")
let dumb_dict = StupidDataSource()
let tableytable = make_table(window, (100, 100, 0 ,0), "poop", dumb_dict)
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
app.setActivationPolicy(.Regular)
let controller = AppDelegate()
app.delegate = controller
app.run()
This can be run at the command line with "swift file.swift". I have implemented the methods required for NSTableViewDataSource, and initialized everything correctly according to Apple's documentation, yet nothing shows up in the table. What is missing?
I'm no expert but it looks to me as though your tableView function is just returning the contents of the relevant row from the dictionary.
I personally return a cell as an NSView? like:-
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?
{
if let cell = myViewBasedTableView.makeViewWithIdentifier(tableColumn!.identifier, owner: nil) as? NSTableCellView
{
cell.textField!.stringValue = "Hello";
// Instead of "Hello". Put the contents of one element from your
// dict in here. This func is called automatically for every cell
// in the grid.
return cell;
}
return myEmptyDefaultCell;
}
Having said that maybe your example is correct for Cell Based tableView. I never used one of them so don't know. I always use viewBased table view. I often put little images/icons in the cells as well as text using code like:
cell.textField!.stringValue = "Hello";
cell.imageView!.image = smileyFace;
return cell;

Updated NSTableCellView.textField NOT updated in table display.

I have a feeling this table cell display problem has a simple answer but, I clearly need greater wisdom than mine to find it.
Here's my textbook controller, delegate and datasource class for the table and the enclosing view ...
import Cocoa
class Table8020ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var tokenText: [String] = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta"]
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfRowsInTableView(aTableView: NSTableView) -> Int {
println("numberOfRowsInTableView = \(tokenText.count)")
return tokenText.count
}
func tableView(aTableView: NSTableView, objectValueForTableColumn aTableColumn: NSTableColumn?, row rowIndex: Int) -> AnyObject? {
var result = aTableView.makeViewWithIdentifier(aTableColumn!.identifier, owner: self) as! NSTableCellView
println("textField in = \(result.textField!.stringValue)")
result.textField!.stringValue = tokenText[rowIndex]
println("textField out = \(result.textField!.stringValue)")
return result
}
}
I log the updates to the .textField which seems to work ok.
numberOfRowsInTableView = 6
textField in = Table View Cell
textField out = alpha
textField in = Table View Cell
textField out = beta
textField in = Table View Cell
textField out = gamma
textField in = Table View Cell
textField out = delta
textField in = Table View Cell
textField out = epsilon
textField in = Table View Cell
textField out = zeta
But the actual table display retains the original interface builder values! 'Table View Cell' Something mysterious appears to be happening after after the 'return result'.
I'm using the latest Xcode Version 6.3 (6D570) with Swift V1.2
You're making a couple of mistakes.
Firstly you're returning the wrong kind of value from tableView:objectValueForTableColumn:row. This method isn't requesting a view instance, it's requesting the object that your view instance will be representing. In other words it wants to know what model object the view will be displaying. In each case your model object is very simple - it's one of the strings in the tokenText array.
func tableView(tableView: NSTableView,
objectValueForTableColumn tableColumn: NSTableColumn?,
row: Int) -> AnyObject? {
return tokenText[row]
}
Secondly you've failed to implement tableView:viewForTableColumn:row:. This is where you create a view for each cell in your table and tell that view which bits of the model object you want to display. In each case your model object is just a string, so you essentially tell the view to display the entire model object in its textField:
func tableView(tableView: NSTableView,
viewForTableColumn tableColumn: NSTableColumn?,
row: Int) -> NSView? {
var view = tableView.makeViewWithIdentifier(tableColumn!.identifier,
owner: self) as! NSTableCellView
view.textField!.stringValue = tokenText[row]
return view
}

NSOutlineView crash when isGroupItem delegate method is used with Swift

I want to deploy Source List using NSOutlineView in a Swift project.
The view controller below works well when the isGroupItem delegate method is not invoked. However, many __NSMallocBlock__ items will be returned when the isGroupItem method is used. Which I have no idea where these items come from. The items I provided are only strings.
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
let topLevel = ["1", "2"]
let secLevel = ["1": ["1.1", "1.2"], "2": ["2.1", "2.2"]]
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
if let str = item as? String {
let arr = secLevel[str]! as [String]
return arr.count
} else {
return topLevel.count
}
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
return outlineView.parentForItem(item) == nil
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
var output: String!
if let str = item as? String {
output = secLevel[str]![index]
} else {
output = topLevel[index]
}
return NSString(string: output)
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
return item
}
func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool {
return (outlineView.parentForItem(item) == nil)
}
func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
return outlineView.makeViewWithIdentifier("HeaderCell", owner: self) as NSTextField
}
}
The sample project can be downloaded here
If you check out the NSOutlineView documentation you will see that it stores only pointers; it doesn't retain the objects returned from the child:ofItem: delegate method. So, when you do this line:
return NSString(string: output)
You are returning a new NSString instance that is quickly released (since the outline view does not retain it). After that point, anytime you ask questions about the items you will get a crash, because the NSString has been freed.
The solution is simple: store the NSStrings in an array and return those same instances each time.
corbin
This question has been answered by Ken Thomases in apple developer forum. Here extracted what he said:
The items you provide to the outline view must be persistent. Also, you have to return the same item each time for a given parent and index. You can't return objects that were created ad hoc, like you're doing in -outlineView:child:ofItem: where you call the NSString convenience constructor.
It works fine after persisting the datasource objects as follow:
let topLevel = [NSString(string: "1"), NSString(string: "2")]
let secLevel = ["1": [NSString(string: "1.1"), NSString(string: "1.2")], "2": [NSString(string: "2.1"), NSString(string: "2.2")]]
then return the stored NSString in the outlineView:child:ofItem: datasource method.
It's because NSOutlineView works with objects inherited from NSObject, and Swift string is incompatible type.

Resources