NSTextField in NSTableCellView - end editing on loss of focus - macos

I have a view with a view-based NSTableView (which itself has a cell view with a single text field) and some buttons and textfields outside the tableview. One of the buttons adds an object into the datasource for the tableview, and after inserting the row into the tableview, immediately makes it editable.
If the user enters the text and pressed the return key, I receive the - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor delegate method fine, and I can run my validation and save the value. But the delegate doesn't get called if the user selects any of the other buttons or textfields outside the tableview.
What's the best way to detect this loss-of-focus on the textfield inside the NSTableCellView, so I can run some of my validation code on the tableview entry?

If I understand you correctly you want a control:textShouldEndEditing: notification to fire in the following situation:
You add a new object to the array controller.
The row in the table representing the object is automatically selected.
YOU programmatically select the text field in the relevant row for editing.
The user immediately (i.e. without making any edits in the text field) gives focus to a control elsewhere in the UI
One approach I've used in the past to get this working is to make an insignificant programmatic change to the field editor associated with the text field, just before the text field becomes available to the user for editing. The snippet below shows how to do this - this is step 2/step 3 in the above scenario:
func tableViewSelectionDidChange(notification: NSNotification) {
if justAddedToArrayController == true {
// This change of selection is occurring because the user has added a new
// object to the array controller, and it has been automatically selected
// in the table view. Now need to give focus to the text field in the
// newly selected row...
// Access the cell
var cell = tableView.viewAtColumn(0,
row: arrayController.selectionIndex,
makeIfNecessary: true) as NSTableCellView
// Make the text field associated with the cell the first responder (i.e.
// give it focus)
window.makeFirstResponder(cell.textField!)
// Access, then 'nudge' the field editor - make it think it's already
// been edited so that it'll fire 'should' messages even if the user
// doesn't add anything to the text field
var fe = tableView.window?.fieldEditor(true, forObject: cell.textField!)
fe!.insertText(cell.textField!.stringValue)
}
}

Related

NSTextField Loses Focus When Neighboring NSTableView Reloaded

I have a search field (NSTextField) called searchField and when you type in it, it refreshes the data shown in a NSTableView. The problem is that this refresh also triggers the selection of a table row, and that takes the focus out of the NSTextField.
The user types in the search field:
func controlTextDidChange(_ obj: Notification) {
if let field = obj.object as? NSTextField, field == searchField{
refreshData()
}
}
Then the NSTableView gets reloaded here:
func refreshData(){
//Process search term and filter data array
//...
//Restore previously selected row (if available)
let index = tableView.selectedRow
tableView.reloadData()
//Select the previously selected row
tableView.selectRowIndexes(NSIndexSet(index: index) as IndexSet, byExtendingSelection: false)
}
If I comment-out both the reloadData() and the selectRowIndexes then the search field behaves as intended (I can keep typing and it keeps the focus in the field). But if include either or both of those methods, the search field loses focus after I type the first character and refreshData() is called.
How can I keep focus in my search field and not let the table reload hijack the focus?
Ugh... it turns out I could never get the NSTableView to let go of the focus because it wasn't set to allow an Empty selection state. Checking a box in Interface Builder fixed it.

Preselecting a NSComboBox Entry

I have a modal window that contains a NSComboBox. "Uses Data Source" is set to turn and the combobox correctly lists the entries. The view controller is the delegate for the data source. When I call the modal window to update an existing object I would like the combobox to display the selected entry if there is one. How do I do this?
I've tried to access the entries in viewWillAppear. I get and error saying there are no entries. The various print statements I have in the code indicate that the values aren't loaded until the drop down arrow is clicked. The two function I'm supplying as the delegate are:
func numberOfItems(in comboBox: NSComboBox) -> Int
func comboBox(_ comboBox: NSComboBox, objectValueForItemAt index: Int) -> Any?
Would it be possible to set the selected entry in the second function above?
As NSComboBox is a subclass of NSTextField you should be able to set its text by setting the stringValue property of your comboBox.
self.comboBox.stringValue = "Hello World"

NSTableView - Initial Selection Grey until Clicked (Focussed)

I've got a simple example of an app here which I slapped together, and what I'm getting is pretty much what I'm after.
The issue is that when the view loads up, in the NSViewController's viewDidLoad, I set the tableView's selected index to 0 i.e. the first item (which works).
What I do notice is that when this happens, the selected row comes up as grey in color (i.e. as if it's not an active window/view)… It only seems to high light in the normal blue color when I physically click on the row that's selected.
I can confirm that the row is selected and everything appears fine.
Any ideas?
To confirm, the code I use to select the row is:
override func viewDidAppear() {
self.tableView.selectRowIndexes(NSIndexSet(index: 0), byExtendingSelection: false)
}
Here is what's happening with the actual view itself:
ABOVE: The darker grey line is the "selection bar". This is what happens as soon as the view becomes active.
ABOVE: Once I click on that row (the one which was once dark grey), I get he desired high lighting.. i.e. Navy Blue.
The reason why the cell is grey is because the table view doesn't have focus / isn't the first responder.
There are 3 states for tableView cell selection color
no selection = clear row background
selection and focus = blue row background
selection and no focus = grey row background
This is probably because another view has focus. Simply selecting a cell doesn't shift focus to a tableView. You need to call NSWindow.makeFirstResponder() to change the focus.
func tableViewSelectionDidChange(notification: NSNotification) {
let tableView = notification.object as! NSTableView
if tableView.selectedRow != -1 {
self.window!.makeFirstResponder(self.tableView)
}
}
I've managed to find out what's going on. (I think) and it seems to work.
I had to:
Subclass NSTableRowView
Add a new NSView just below the actual cell view (row) in Interface Builder
Set the new Row View's class to 'myNSTableViewSubClass'
Set the row view's Identifier to: NSTableViewRowViewKey (this is very specific, and that literally is the key, if this isn't set, it won't work be regarded as the Table Row View.
in the subclass I had to override the emphasised: Bool to always return yes e.g.:
override var emphasized: Bool{
get{
return true
}
set{
//You need to have the "set" there as it's a mutable prop
//It doesn't have to do untying though
}
}
And voila..
The catch in my case was in 4 above.

How to send a key event to an NSTextField that does not have focus in swift

I have a view that contains an NSTableView and an NSTextField. The table view has focus. I would like to use the text field as a filter for the table view. Is there any way that I can send a key press event, which is captured by the table view, to the NSTextField?
Here is the keyDown func as I have it, I would like to send theEvent to the text field in the default handler of the switch statement.
override func keyDown(theEvent: NSEvent) {
let s = theEvent.charactersIgnoringModifiers!
let s1 = s.unicodeScalars
let s2 = s1[s1.startIndex].value
let s3 = Int(s2)
switch s3 {
case NSUpArrowFunctionKey:
self.previous()
return
case NSDownArrowFunctionKey:
self.next()
return
default:
// this appends the text to the textField, Is there a way to send theEvent to the textField?
textField.stringValue = textField.stringValue + s;
// textField.keyDown(theEvent) // this does not work
break
}
}
I would like to not have to force the user to change focus to the text field in order to filter the table view results.
As far as I understand it the -keyDown: is sent to the firstResponder which in this case is the table view.
I don't like this idea and I would implement this is a different way, but you could try inserting the text field into the responder chain and a position above table view. You would have to subclass the text field and implement mouseDown to capture the events and you would also have to change you table views mouse down implementation so it didn't capture the event for the text field. This is overly complicated.
Why not simply set the target/action of the text field and deal with user input outside of the event loop?

Access data from table view cell in Swift

I'm having trouble accessing an optional string in swift from a table view cell. Each cell has a title label as well as a detail text label and the amount of cells at any given time in the table view is dependent on the user (the table view is used to display saved values). Since the strings saved as the detail text label's text in each cell are way too long to read in the table view, I wish to load them in another view that is segued to when a cell is pressed. This is my prepare for segue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "cellSegue"
{
let cell = tableView.cellForRowAtIndexPath(tableView.indexPathForSelectedRow()!)
var transfer : ExplanationView = segue.destinationViewController as ExplanationView
if let unwrapped = cell?.textLabel?.text!
{
transfer.infoText.text = unwrapped
}
}
}
When a segue is performed, I get a fatal crash, the offending line is "transfer.infoText.text = unwrapped" and Xcode tells me it found nil when unwrapping an optional. During my debuggingg efforts, I've tried "println(cell?.textLabel?.text!)" and Xcode prints "Optional(String)" where "String" is the actual string I'm trying to access so it seems like I'm on the right track, but obviously there's something I'm missing. I've also tried "cell?.textLabel?.text" but I get the same error. Any help is appreciated
You shouldn't ever extract data from a view. You should extract data from a model. The fact that you have a tableView with cells implies you have a model for the data that goes in those cells. It might be an array of strings or whatever. Instead of trying to extract the data from the cells, just get the data from the data model you used to populate the cells. Something like:
let selectedIndexPath = tableView.indexPathForSelectedRow()
let selectedData = data[selectedIndexPath.row]
Your problem is not unwrapped but instead transfer.infoText.text. Your IBOutlets are not set up at the time of the prepareForSegue. You need add a property (var) to your destination view controller to hold the unwrapped value. Then in viewDidLoad when the IBOutlets are set up, copy the string to your text field.

Resources