Set the outlet of the item in TableCellView within the .xib file to the custom NSTableCellView subclass - swift

I want to fill my NSTableView with content. Per table-cell-row are 3 items (2 NSTextFields and 1 NSImageView). For that I created a custom NSTableCellView where I want to set the #IBOutlets of the 3 Items, to set there the value for them. But when I try to set the referencing outlets, the only option is to create an action.
When I try to write #IBOutlet weak var personName: NSTextfield and then set the references, I can't because "xcode cannot locate the class in the current workspace"
When I create the NSTableViewinside a main.storyboard, I'm able to set the outlet references. So what is the different behavior between .storyboard and .xib?
When I try to connect the #IBOutlet with the Item "Person Name"
My NSViewController (owner of the .xib)
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView! //ref to tableView in xib
var persons:[Person] = [] //content to fill tableview
override func viewDidLoad() {
super.viewDidLoad()
persons.append(Person(name: "John", age: 23, piRef: "/Users/xy/Desktop/profilePic.png"))
persons.append(Person(name: "Marie", age: 26, piRef: "/Users/xy/Desktop/profilePic.png"))
tableView.delegate = self
tableView.dataSource = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return persons.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let tableCellView:personTableCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "defaultRow"), owner: self) as! personTableCell
//NSTableColumn in xib has id "defaulRow"
if let person:Person = persons[row] {
tableCellView.setPerson(person: person) //call method inside NSTableCellView-subclass to set item values
}
return tableCellView
}
}
The custom NSTableCellView subclass ("personTableCell")
class personTableCell: NSTableCellView {
var person:Person! = nil
//here should be:
//#IBOutlet weak var personName: NSTextField!
//#IBOutlet weak var personAge: NSTextField!
//#IBOutlet weak var personImg: NSImageView!
func setPerson(person: Person) {
self.person = person
self.personName = person.name
self.personAge = person.age
self.personImg = NSImage(byReferencingFile: person.profileImgRef)
}
}
I want to be able to add the item outlet references to my NSTableCellView-subclass.

It appears to me you're making this harder than it needs to be. makeView is giving you a reference to the cell. Therefore you can access its members directly. No need for outlets (which is why Xcode won't make them for you.)
I can't read your screenshots well enough to tell how the textfields are defined (old eyes), so I can only give you a generic example from a working demo of a custom cell class:
class DIYTableViewDelegate: NSObject, NSTableViewDelegate {
var count = 0 // counts the number of views actually created
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let id = tableColumn!.identifier
var view = tableView.makeView(withIdentifier: id, owner: nil) as? CustomTableCellView
if view == nil {
view = createCell(id)
count += 1
}
view!.textField!.stringValue = "\(id.rawValue) \(row) \(view!.count) \(count)"
view!.count += 1
return view
}
}
Also, it's customary in Swift to capitalize the first letter of types (classes, structures, enums, protocols) and lowercase methods & properties. Doesn't affect how your code compiles, but it helps other Swifties read it.
Here's another example that may help:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let vw = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? CustomTableCellView else { return nil }
vw.textField?.stringValue = String(pictures[row].dropLast(4))
vw.imageView?.image = NSImage(named: pictures[row])
return vw
}

Related

How to add additional textfields by clicking button in table view

I am trying to add an option to add additional student fields inside table so that user can add more than one student name.
But I am confused how to do it using table view.
I am not interested in hiding view with specific number of fields.
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
struct listItems{
var title : String
var isExpandable:Bool
var maxFields :Int
init(title:String,isExp:Bool,mxF:Int) {
self.title = title
self.isExpandable = isExp
self.maxFields = mxF
}
}
#IBOutlet weak var tblListTable: UITableView!
let data : [listItems] = [listItems(title: "Name", isExp: false, mxF: 1), listItems(title: "Student Name", isExp: true, mxF: 20), listItems(title: "Email", isExp: false, mxF: 1)]
override func viewDidLoad() {
super.viewDidLoad()
tblListTable.delegate = self
tblListTable.dataSource = self
self.tblListTable.reloadData()
print("isLoaded")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRow")
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ListCell
cell.lblName.text = data[indexPath.row].title
if data[indexPath.row].isExpandable == true {
cell.btnAddField.isHidden = false
print("ishidden")
}
else {
cell.btnAddField.isHidden = true
}
return cell
}
}
List Cell Class
import UIKit
protocol AddFieldDelegate : class {
func addField( _ tag : Int)
}
class ListCell: UITableViewCell {
#IBOutlet weak var btnAddField: UIButton!
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var txtField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func addField( _ tag : Int){
}
}
You are on the right track creating the AddFieldDelegate. However, rather than implementing the method inside the ListCell class you need to implement it in the ViewController.
First, change the view controller class definition line to:
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource, AddFieldDelegate {
This will allow you to call the delegate method from the view controller. Next, when you are creating your table view cells add the line:
cell.delegate = self
After that, move the method definition of the method addField to the view controller.
So inside of your view controller add:
func addField(titleOfTextFieldToAdd: String, numberAssociatedWithTextFieldToAdd: Int) {
data.append(listItems(title: titleOfTextFieldToAdd, isExp: false, mxF: numberAssociatedWithTextFieldToAdd))
self.tableView.reloadData()
}
I used an example definition of the addField method but you can change it to anything that you would like, just make sure that you change the data array and reload the table view data.
Lastly, we must define the delegate in the ListCell class. So add this line to the ListCell class:
weak var delegate: MyCustomCellDelegate?
You can then add the text field by running the following anywhere in your ListCell class:
delegate?.addField(titleOfTextFieldToAdd: "a name", numberAssociatedWithTextFieldToAdd: 50)
For more information on delegation, look at the answer to this question.
You have to append another item in your data array on button click and reload the tableview.

NSTableView Doesn't work when in ViewController

For some reason when I put my code for my NSTableView in a ViewController, none of the cells appear, but if I put the code in the AppDelegate, everything works great.
Any ideas as to why this is happening? I'm working with a .xib file if that helps at all.
class ViewController: NSViewController{
var delegate: AppDelegate? = nil
#IBOutlet weak var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
delegate = NSApplication.shared.delegate as? AppDelegate
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController: NSTableViewDataSource{
func numberOfRows(in tableView: NSTableView) -> Int {
print(delegate?.FlightList.count ?? 0)
return delegate?.FlightList.count ?? 0
}
}
extension ViewController: NSTableViewDelegate{
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var text: String = ""
var cellIdentifier: String = ""
guard let item = delegate?.FlightList[row] else {
return nil
}
if tableColumn == tableView.tableColumns[0] {
text = item.flightName
cellIdentifier = "flightID"
}
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
return nil
}
}
you need to implement the NSTableViewDataSource, NSTableViewDelegate protocols to tell the table this is the class its getting data from else the
tableView.dataSource = self
tableView.delegate = self
wont work and then you wont need to use the app delegate

How to create a custom NSTableCellView from a NIB?

I'm new to Swift and I am struggling with NSTableView! I'm trying to create a custom NSTableCellView from a NIB.
I want to load the cell from a NIB because:
it will be reused in multiple columns and multiple table-views
it will be visually and functionally complex (relatively)
it is likely to evolve during development
I'm able to load the cell in my table view but I'm getting a "Failed to connect outlet from... missing setter or instance variable" error in the debug area when I try to populate the view with data. The custom cell is visible in the tableview but doesn't seem to be instantiated.
I've searched for a solution online for hours! Help what am I missing?
This is my TableViewController...
protocol TableViewDelegate {
func itemWithIndexWasSelected(value: Int)
}
class TableViewController: NSViewController {
#IBOutlet weak var tableView: NSTableView!
let tableViewData =
[ [ "Column1": "John", "Column2": "Smith", "Hobby": "Birds"],
[ "Column1": "Jane", "Column2": "Doe", "Hobby": "Fish"],
[ "Column1": "Hal", "Column2": "Bernard", "Hobby": "Trees"],
[ "Column1": "Harry", "Column2": "Bell", "Hobby": "Rocks"] ]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
let customCellNib = NSNib.init(nibNamed: "CustomTableCellView", bundle: nil)
tableView.register(customCellNib, forIdentifier: NSUserInterfaceItemIdentifier("CustomCellView"))
tableView.reloadData()
}
}
extension TableViewController: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return tableViewData.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if tableColumn?.identifier.rawValue == "CustomCell" {
let result: CustomTableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("CustomCellView"), owner: self) as! CustomTableCellView
result.hobbyLabel?.stringValue = tableViewData[row]["Hobby"]!
result.hobbyButton?.title = "TESTTITLE"
return result
}
else {
let result = tableView.makeView(withIdentifier:(tableColumn?.identifier)!, owner: self) as! NSTableCellView
result.textField?.stringValue = tableViewData[row][(tableColumn?.identifier.rawValue)!]!
return result
}
}
}
I have a CustomTableCellView with a XIB that has the same name...
class CustomTableCellView: NSTableCellView {
#IBOutlet weak var hobbyButton: NSButton!
#IBOutlet weak var hobbyLabel: NSTextField!
}
I have a test project that I can send or upload... help will be hugely appreciated!
This is what I am seeing:
When editing a xib, the File's Owner proxy object represents the object which is passed as owner to makeView(withIdentifier:owner:) at runtime. In this case the owner object is the view controller. You can set the class of the File's Owner in the xib to TableViewController and connect actions. You can't set the class of the File's Owner to CustomTableCellView and connect outlets. Instead connect the outlets of the CustomTableCellView view object.

How to make some specific items of a NSTableView in bold?

I would like to set some items of a non-editable, View Based NSTableView in bold. The items correspond to a specific index number of the array I use to populate the TableView.
I would like to set the change before the NSTableView is displayed to the users.
I tried to handle this change in this method but I can't find a way to do it:
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any?
If you don't want to use Cocoa Bindings
It's very similar on how you do it on iOS. Configure the cell's view in tableView(_:viewFor:row:)
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView!
var daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
var boldDays = ["Monday", "Wednesday"]
override func viewDidLoad() {
self.tableView.dataSource = self
self.tableView.delegate = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return daysOfWeek.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
// Assuming that you have set the cell view's Identifier in Interface Builder
let cell = tableView.make(withIdentifier: "myCell", owner: self) as! NSTableCellView
let day = daysOfWeek[row]
cell.textField?.stringValue = day
if boldDays.contains(day) {
let fontSize = NSFont.systemFontSize()
cell.textField?.font = NSFont.boldSystemFont(ofSize: fontSize)
// if you require more extensive styling, it may be better to use NSMutableAttributedString
}
return cell
}
}
If you want to use Cocoa Bindings
Cocoa Bindings can make this very simple, but if you set the slightest things wrong, it's pretty hard to figure out where things went south. Heed the warning from Apple:
Populating a table view using Cocoa bindings is considered an advanced topic. Although using bindings requires significantly less code—in some cases no code at all—the bindings are hard to see and debug if you aren’t familiar with the interface. It’s strongly suggested that you become comfortable with the techniques for managing table views programmatically before you decide to use Cocoa bindings.
Anyhow, here's how to do it. First, the code:
// The data model must inherit from NSObject for KVO compliance
class WeekDay : NSObject {
var name: String
var isBold: Bool
init(name: String, isBold: Bool = false) {
self.name = name
self.isBold = isBold
}
}
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView!
let daysOfWeek = [
WeekDay(name: "Sunday"),
WeekDay(name: "Monday", isBold: true),
WeekDay(name: "Tuesday"),
WeekDay(name: "Wednesday", isBold: true),
WeekDay(name: "Thursday"),
WeekDay(name: "Friday"),
WeekDay(name: "Saturday")
]
override func viewDidLoad() {
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
Then the Interface Builder config:
Make sure the Table Column and Table Cell View have the same identifier. Best is to leave both of them blank for automatic
Select the Table View, bind Table Content to View Controller, Model Key Path = self.daysOfWeek
Select the Table View Cell, bind Value to Table Cell View (no kidding), Model Key Path = objectValue.name
Scroll down, bind Font Bold to Table Cell View, Model Key Path = objectValue.isBold
Either way, you should end up with something like this:
Polish as needed.

NSTableView Delegate methods won't get called

I'm currently trying to parse the reddit headlines from a specific subreddit and display these in an NSTableView. The thing is, the numberOfRows function gets called and returns the correct integer but the tableView delegate function never gets called.
As far as I can see everything is wired up correctly in the code.
ViewController:
#IBOutlet weak var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Downloader.load(url: URL(string: "https://www.reddit.com/r/" + "gaming" + ".json")!){
(result) in
let tvc = TableViewController(data: result)
self.tableView.delegate = tvc
self.tableView.dataSource = tvc
self.tableView.reloadData()
}
}
TableViewController:
class TableViewController: NSObject{
var json: JSON!
init(data: JSON) {
super.init()
self.json = data
}
}
extension TableViewController : NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return JSONFormatController.getTitlesFrom(json: json).count
}
}
extension TableViewController : NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var titles = JSONFormatController.getTitlesFrom(json: json)
if let cell = tableView.make(withIdentifier: "entry", owner: nil) as? NSTableCellView {
cell.textField?.stringValue = titles[row]
return cell
} else {
return nil
}
}
}
The result variable and getTitlesFrom method do work, I checked these.
I think your issue is that your TableViewController object is getting deallocated because you are not keeping a reference to it. Try this:
#IBOutlet weak var tableView: NSTableView!
var tvc : TableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Downloader.load(url: URL(string: "https://www.reddit.com/r/" + "gaming" + ".json")!){
(result) in
self.tvc = TableViewController(data: result)
self.tableView.delegate = self.tvc
self.tableView.dataSource = self.tvc
self.tableView.reloadData()
}
}
Explanation: tvc is a local variable of the download block which is getting deallocated after it has executed. Presumably your assumption is that storing the tvc in delegate and/or dataSource is keeping tvc alive. But they are not, they are weak references.