Updating tableview binded to array error - swift

I am extremely new to Swift so I am guessing this is a rookie mistake but here's my situation:
I am trying to create an application that allows you to add things to a list. I have a NSTextField where users input there data and a NSButton to add that data to the myTeam array. The tableView is binded (I think that's what you call it) to the myTeam array.
When I run the application a table containing the preset values of myTeam populate the table view correctly, but when I add to this array nothing happens. After research, I found this question.
Unfortunately, like most swift tutorials and Q&A, it was for iOS. But I thought I would give it a try. I added
self.tableView.reloadData()
self.refresher.endRefreshing()
to my code, and received two errors: Value of type '(NSTableView, NSTableColumn?, Int) -> Any?' has no member 'reloadData' and Value of type 'ViewController' has no member 'refresher', respectively.
Heres my code below:
import Cocoa
import Darwin
class ViewController: NSViewController, NSTableViewDataSource {
var myTeam = ["Test", "Test2"]
#IBOutlet weak var myText: NSTextField!
#IBOutlet weak var AddMember: NSButton!
#IBAction func addmem(_ sender: Any) {
myTeam.append(myText.stringValue);
self.tableView.reloadData()
self.refresher.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
// MARK: DataSource
func numberOfRows(in tableView: NSTableView) -> Int {
return myTeam.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return myTeam[row]
}
}
P.S. I used this tutorial to get as far as I did.

Simplest solution, we are going to use (full) Cocoa Bindings:
Replace the entire code with
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var myText: NSTextField!
#objc dynamic var myTeam = ["Test", "Test2"]
#IBAction func addmem(_ sender: NSButton) {
myTeam.append(myText.stringValue)
}
}
In Interface Builder select the table view (not the enclosing scroll view) and press ⌥⌘6 (Connections Inspector)
Disconnect dataSource
Press ⌥⌘7 (Bindings Inspector)
Bind Content to ViewController > Model Key Path myTeam
Since you are still using objectValueFor I assume that the value of the table cell view is already bound to objectValue.
macOS doesn't know a refresher.

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.

Unable to populate xib-created swift tableView using macOS

I am unable to populate a swift cell-based tableview in macOS 10.14.6 using an Xcode 11.2 xib. The app is Document based and the tableView is created with a separate WindowController xib. A similar project created programmatically in Xcode works ok, including drag and drop; I am relatively new to using xibs and likely have not set things correctly. A column identifier has been set in the xib and NSTableViewDataSource and NSTableViewDelegate have been added to the Window Controller. Pertinent source code follows and the complete Xcode project may be downloaded here: https://www.dropbox.com/s/6tsb98b7iihhfxl/tableView.zip?dl=0
Any help in getting the tableView populated with a String array would be appreciated. I would also like to get drag and drop working but can get by for now just getting the array items to show up in the table view. It correctly creates four rows, corresponding to the number of elements in the array, but there is no visible text. The tableView is cell-based, but I could use view-based if that would work better. Thank you in advance.
class WindowController: NSWindowController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet var tableView: NSTableView!
var sports : [String] = ["Basketball","Baseball","Football","Tennis"]
override func windowDidLoad() {
super.windowDidLoad()
tableView.registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
tableView.dataSource = self
tableView.delegate = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return (sports.count)
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
var value : Any? = 0
let columnIdentifier : String = (tableColumn?.identifier.rawValue)!
if (columnIdentifier == "Col1"){
value = sports[row]
}
return value
}
In Document.swift windowController is released at the end of showTableView() and the table view looses its data source. Add windowController to the window controllers of the document or hold a strong reference to windowController.
#IBAction func showTableView(_ sender: Any) {
let windowController = WindowController.init(windowNibName:NSNib.Name("WindowController"))
addWindowController(windowController)
windowController.showWindow(nil)
}

NSTableView.setNeedsDisplay() not redrawing on attached Formatter changes only

i am using a view based NSTableView with a column that shows dates, and the table cell views use a shared DateFormatter.
let view: NSTableCellView? = tableView.makeView(withIdentifier: column.identifier, owner: self) as! NSTableCellView?
let entry = (logController.arrangedObjects as! [LogEntry])[row]
switch column.identifier {
case columnDateKey:
view?.textField?.formatter = sharedDateFormatter
view?.textField?.objectValue = entry.date
The application has a user preference to choose the date format and previously the code
tableView.setNeedsDisplay(tableView.rect(ofColumn: tableView.column(withIdentifier: columnDateKey)))
would refresh the column with the new date format.
With macOS Mojave this does not happen. Investigation shows that although the drawRect: is called for the underlying TableView there are no calls made to tableView(:viewFor:row:) to obtain the new values for table cell views. Calling tableView.reloadData(forRowIndexes:columnIndexes:) does result in calls to tableView(:viewFor:row:) but the display does not refresh (although it does for tableView.reloadData()).
Any external cause to redraw e.g. selecting a row correctly updates that area alone. The other thing I've seen is that with a long table slowly scrolling up will eventually result in the new format appearing although existing cells do not change when scrolled back to until scrolled a long way past before returning. This would seem to infer that there are cached views that are not considered to have changed when only the configuration of the attached formatter changes (although are when the value of the contents changes)
This behaviour changed with the introduction of Mojave and I am finding it difficult to believe that no-one else has reported it and so am beginning to question my original code. Am I missing something?
The following test code demonstrates the problem, the "View requested" message is not printed for variants of setNeedsDisplay and display is only redrawn for reloadData()
styleButton is tick box to toggle number format and refreshButton is action button to request a redraw
Setting the value to a random value will result in expected redraw behaviour
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var table: NSTableView!
#IBOutlet weak var styleButton: NSButton!
#IBOutlet weak var refreshButton: NSButton!
#IBOutlet weak var testView: NSView!
let numberFormatter = NumberFormatter()
func applicationWillFinishLaunching(_ notification: Notification) {
numberFormatter.numberStyle = symbolButton.state == NSControl.StateValue.on ? NumberFormatter.Style.decimal : NumberFormatter.Style.none
}
#IBAction func refresh(sender: Any?) {
numberFormatter.numberStyle = styleButton.state == NSControl.StateValue.on ? NumberFormatter.Style.decimal : NumberFormatter.Style.none
table.setNeedsDisplay(table.rect(ofColumn: 0))
// table.needsDisplay = true
// table.reloadData(forRowIndexes: IndexSet(integersIn: 0..<table.numberOfRows), columnIndexes:[0])
// table.reloadData()
}
}
extension AppDelegate: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
if tableView == table {
return 40
}
return 0
}
}
extension AppDelegate: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
print("View requested")
guard tableColumn != nil else {
return nil
}
let column = tableColumn!
if tableView == table {
let view: NSTableCellView? = tableView.makeView(withIdentifier: column.identifier, owner: self) as! NSTableCellView?
view?.textField?.formatter = numberFormatter
view?.textField?.objectValue = 123.456
return view
}
return nil
}
}
Incorrectly relying on view.setNeedsDisplay to automatically update subviews. This is not the case (although had appeared to work that way, previously) - refer comment from Willeke above

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

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
}

Accessing Variables From a Different Swift Class

I have two swift files, each corresponding to a different view controller. I also have two classes, one for creating "Workout" objects and the other for holding these objects into an array. The workout class is a very simple class that just holds properties such as workout name, description, etc.
I am trying to access the array count of workoutList in my OverViewViewController.swift
Im a new developer and don't know how I would achieve this. Would I have to make a reference to the NewWorkoutViewController then try to grab the variable?
import UIKit
class OverViewViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Hello"
return cell
}
}
import UIKit
class NewWorkoutViewController: UIViewController {
#IBOutlet weak var setStepper: UILabel!
#IBOutlet weak var repStepper: UILabel!
#IBOutlet weak var workoutName: UITextField!
#IBOutlet weak var workoutDescription: UITextField!
var workoutList = WorkoutList().listOfWorkouts
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func stepCounter(_ sender: UIStepper) {
if sender.tag == 1 {
setStepper.text = "\(Int(sender.value))"
}
if sender.tag == 2 {
repStepper.text = "\(Int(sender.value))"
}
}
#IBAction func addToWorkoutList(_ sender: UIButton) {
let workout = Workout(name: workoutName.text!, description: workoutDescription.text!, sets: Int(setStepper.text!)!, reps: Int(repStepper.text!)!)
workoutList.append(workout)
print(workoutList.count)
}
}
So the best way to do is create a model class name it as WorkOut with your required information. Then add the that object in array which you can define in your viewController class. Now you can pass that array information to another viewController class and can use it.