简体   繁体   中英

Cocoa: Get Notified after Text Cell (NSTextField) is Edited & Start Editing Text Cell after Adding it in NSTableView in Swift 4?

I have made a simple demo using TableView here: https://github.com/deadcoder0904/TableViewDemo

I have used Defaults module as a dependency

My project looks like

追随你的梦想

All the code is in ViewController.swift as follows -

import Cocoa
import Defaults

extension Defaults.Keys {
    static let dreams = Defaults.Key<Array<String>>("dreams", default: [
        "Hit the gym",
        "Run daily",
        "Become a millionaire",
        "Become a better programmer",
        "Achieve your dreams"
        ])
}

class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {

    @IBOutlet weak var table: NSTableView!
    var dreams = defaults[.dreams]
    var selectedRow:Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        table.dataSource = self
        table.delegate = self
    }

    override var acceptsFirstResponder : Bool {
        return true
    }

    override func keyDown(with theEvent: NSEvent) {
        if theEvent.keyCode == 51 {
            removeDream()
        }
    }

    func tableViewSelectionDidChange(_ notification: Notification) {
        let table = notification.object as! NSTableView
        selectedRow = table.selectedRow
    }

    func numberOfRows(in tableView: NSTableView) -> Int {
        return dreams.count
    }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let dream = table.makeView(withIdentifier: tableColumn!.identifier, owner: self) as! NSTableCellView
        dream.textField?.stringValue = dreams[row]

        return dream
    }

    @IBAction func addTableRow(_ sender: Any) {
        addNewDream()
    }

    @IBAction func removeTableRow(_ sender: Any) {
        removeDream()
    }

    func addNewDream() {
        dreams.append("Double Click or Press Enter to Add Item")
        table.beginUpdates()
        let last = dreams.count - 1
        table.insertRows(at: IndexSet(integer: last), withAnimation: .effectFade)
        table.scrollRowToVisible(last)
        table.selectRowIndexes([last], byExtendingSelection: false)
        table.endUpdates()
        saveDreams()
    }

    func removeDream() {
        if selectedRow >= dreams.count {
            selectedRow = dreams.count - 1
        }
        if selectedRow != -1 {
            dreams.remove(at: selectedRow)
            table.removeRows(at: IndexSet(integer: selectedRow), withAnimation: .effectFade)
        }
        saveDreams()
    }

    func saveDreams() {
        defaults[.dreams] = dreams
    }
}

I want to do 2 things -

  1. Get notified after Text Cell is edited so that I can save the changed data using Defaults module

  2. After adding new Data by Clicking on the plus sign it adds Double Click or Press Enter to Add Item but what I want is I want to add Empty String which I can do with "" but I also want it to be focused & be editable so user can start entering text in it without having to Double Click or Press Enter .

I also want a solution in Swift 4 & not Objective-C. How to achieve this?

Use Cocoa Bindings, it's very powerful and saves a lot of boilerplate code.

Short tutorial:

Edit: To take full advantage of KVC the data source must be an NSObject subclass with dynamic properties

  • Create a simple class Dream (the description property is optional)

     class Dream : NSObject { @objc dynamic var name : String init(name : String) { self.name = name } override var description : String { return "Dream " + name } } 
  • In the view controller declare the data source array

     var dreams = [Dream]() 
  • and replace var selectedRow:Int = 0 with

     @objc dynamic var selectedIndexes = IndexSet() 
  • Go to Interface Builder

    • Select the table view, press ⌥⌘7 to go to the Bindings Inspector.
      Bind Selection Indexes to View Controller Model Key Path selectedIndexes .
      Press ⌥⌘6 and connect the dataSource (by drag&drop) to the view controller ( 在此处输入图片说明 ) .

    • Select the text field File 1 in Table Cell View in the table column. The easiest way is to ⌃⇧click in the text field area.
      Press ⌥⌘7 and bind Value to Table Cell View Model Key Path objectValue.name ( ! )

  • In the view controller populate the data source array in viewDidLoad ( I don't know that framework so I leave it out) and reload the table view.

     override func viewDidLoad() { super.viewDidLoad() let dreamNames = ["Hit the gym", "Run daily", "Become a millionaire", "Become a better programmer", "Achieve your dreams"] dreams = dreamNames.map{Dream(name: $0)} table.reloadData() } 
  • Delete acceptsFirstResponder

  • Delete tableViewSelectionDidChange
  • Delete tableView:viewFor:row:
  • Add

     func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { return dreams[row] } 
  • Replace addNewDream with

     func addNewDream() { let last = dreams.count dreams.append(Dream(name: "Double Click or Press Enter to Add Item")) table.insertRows(at: IndexSet(integer: last), withAnimation: .effectGap) table.scrollRowToVisible(last) table.selectRowIndexes([last], byExtendingSelection: false) saveDreams() } 
  • Replace removeDream() with

     func removeDream() { guard let selectedRow = selectedIndexes.first else { return } dreams.remove(at: selectedRow) table.removeRows(at: IndexSet(integer: selectedRow), withAnimation: .effectFade) saveDreams() } 

To save the array when the text was edited afterwards you have to implement the delegate method controlTextDidEndEditing(_:)

override func controlTextDidEndEditing(_ obj: Notification) {
    saveDreams()
}

and in Interface Builder connect the delegate of the text field in the table view to the view controller.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM