(Xcode 9, Swift 4, macOS 10.12)
I’ve been working with the Cocoa framework on MacOS, on and off, since 2006. That’s more than ten years. Taking a value from a text field is one of the first exercises you learn when you first explore programming… and it wasn’t until I poked at Apple’s introductory iOS tutorial that I came across UITextFieldDelegate, which led me to stop in my tracks and look for a macOS counterpart.
Until now, I’ve used the ‘send on end editing’ action of NSTextField to handle the all-too-common ‘user typed into a text field and I want to do something with the result immediately’ scenario. And that was sometimes cumbersome and created a few interesting edge cases, but I filed it away for ‘handle later’.
I can honestly say that in more than ten years of reading books and articles, following tutorials, going to conferences etc etc, _no-one mentioned NSTextFieldDelegate_ as an alternative option. (I have just confirmed this. There *is* one book, but it’s mainly an iOS book with a short section on Mac development.)
I want to flag this not because I’m bitter (well, a tiny bit), but because it illustrates what I think is the biggest problem in Cocoa – particularly macOS – programming: the frameworks contain a lot of useful tools, and far too few entry points for people to find them. (I am, however, bitter over the week I spent trying to make AVAudioPlayer play more than one song in a row. Eventually I came across a mentioning of AVQueuePlayer… BUT NOT IN THE DOCUMENTATION. [Several things that should have let me play more than one song failed buggily. It was a mess.])
I don’t know *how* to order information to make it more accessible – I don’t think _any_ programming environment has solved this – but the current model is not there yet.
Anyway.
0) Create a new Mac app. Add a label, a text field, and a button. Create outlets for the label and textField in the ViewController:
@IBOutlet weak var resultLabel: NSTextField!
@IBOutlet weak var inputField: NSTextField!
Third party transfer
1) Give an action to the button that takes the value of the input field and passes it to the label.
@IBAction func transferText(_ sender: NSButton){
resultLabel.stringValue = inputField.stringValue
}
Build and run. Type ‘hello World’ and press the button.
This is the simplest way of getting a value out of one field and putting it into another; it has the slight disadvantage of needing a third party to manipulate data. (One could argue that this is the purpose of a viewController, to coordinate the data that comes in and to display it to the user.) While the code smell may not be particularly strong, it’s still there, though there are plenty of occasions where you would use this pattern – for a form, for instance, where you might want to validate each field as you go along, but only submit when the user presses the appropriate button.
TextField action
2a)Create an action _for the inputField_:
@IBAction func sendInput(_ sender: NSTextField) {
resultLabel.stringValue = sender.stringValue
}
Build and Run. Type something into the text field, hit enter, and watch the label change.
2b) Adding another text field. Call it ‘alternateInputField’ and create an outlet for it; hook it up to the same IBAction as above.
Build and Run. Write something in the first field, hit enter, it gets displayed on the label, write something in the second field, ditto.
Now tab to teh first field. The Label text miraculously changes.
2c) Change the setting in the IB Attributes Inspector for both textfields from ‘Send On End Editing’ to ‘Send On Enter Only’
Build and Run. Now tabbing away or clicking into the other text field does not trigger the action; only hitting the return or enter keys does.
3) Disconnect the sendInput action from both text fields. Declare the ViewController to conform to NSTextFieldDelegate and connect the inputField’s delegate to the ViewController in IB.
(Text and its delegates turns out to be much more complex than I anticipated, so I am not covering this topic fully at this point in time. There’s NSTextDelegate, NSTextFieldDelegate, and NSTextViewDelegate, and a lot of methods and not too many explanations.
Consequently, there’s a non-trivial chance that the methods I am describing here are not the optimum handling. I’m getting my information from Stackoverflow, Apple’s example code, and experimentation; use at your own peril.)
NSTextFieldDelegate
3a) Anyway. With NSTextFieldDelegate you can use (most simple implementation, does not take note of the sender and assumes you have only one sending text field)
override func controlTextDidChange(_ obj: Notification) {
resultLabel.stringValue = inputField.stringValue
}
(this results in an update every time the text changes, so you get a live update as you type. It also threw up a wall of weird errors in the console which seem to have to do with the length of its text and its attributes. As far as I can make out, these were enabled in Xcode 8; but they do not seem to have any actual relevance to what we’re doing here.)
or
override func controlTextDidEndEditing(_ obj: Notification) {
resultLabel.stringValue = inputField.stringValue
}
(This fires regardless of the NSTextField settings, on both Return and tabbing/clicking away from the field.)
I have not yet worked out what I need to do to make the NSTextDelegate and *its* methods work; but as I said, this is not my priority right now.
3b) The above is a hack and needs to be eliminated. Connect the second textField’s delegate to the ViewController, and replace the controlTextDidEndEditing function with
override func controlTextDidEndEditing(_ obj: Notification) {
if let sender = obj.object as? NSTextField {
resultLabel.stringValue = sender.stringValue
}
Better. We’re now back at getting values from either text field; I’m also back to occasionally getting odd errors in the console. I dislike errors. Errors popping up are a hint that there’s something wrong, but I have no clue where the problem lies; I am relatively confident that they are not actual things I need to deal with, but they still make me twitchy.
3c) Give the first text field a tag of 0 and the second a tag of 1 and change the delegate method to
override func controlTextDidEndEditing(_ obj: Notification) {
if let sender = obj.object as? NSTextField {
switch sender.tag {
case 0: resultLabel.stringValue = "(1) \(sender.stringValue)"
case 1: resultLabel.stringValue = "(2) \(sender.stringValue)"
default: print("Unknown text field")
}
}
In this exercise, the resultLabel showed the string from the inputField on startup (since that was not set, it said “(1) ” This is one of the places where I would not trust the result to remain consistent across releases (I have not tried it under 10.13, for instance). Setting the resultLabel’s stringValue in viewDidLoad had no effect; in order for the resultField to show the data you want, you need to set it in in the inputField.
And more…
Usage Example
Observing changes in a text field to update your data model. I’ll have to see whether this is useful to synching data – in an outlineView and the main part of the app, or whether this introduces more problems than it solves.
Alternatives
Using a field’s action. Both Target/Action and Delegate are standard Cocoa development patterns, and while the letter-by-letter update is a nice option, if all you want is an update when you have finished editing, there’s not much to choose between them. (I seem to recall problems with the action in implementatoin, but I would need to look up what they were, and test whether the delegate method has the same challenges.) On iOS, where you also need to deal with the onscreen keyboard, the textFieldDelegate is mandatory; on macOS, it seems to be optional, though I will need to experiment a lot more with a more complex app.
If all you want to do is update a variable, you could use bindings
Extensions
I haven’t found a use case for controlTextDidBeginEditing yet. (A search of github brings up three mentionings, none of which contain an implementation). There are a lot of fine-grained methods available in the frameworks, so rather than rolling my own, I would investigate the available delegate protocols further. (e.g. NSControlTextEditingDelegate, NSTextDelegate; NSTextFieldDelegate inherits from both, as far as I can see.)
Nov 22 2017
Text field actions and NSTextFieldDelegate
(Xcode 9, Swift 4, macOS 10.12)
I’ve been working with the Cocoa framework on MacOS, on and off, since 2006. That’s more than ten years. Taking a value from a text field is one of the first exercises you learn when you first explore programming… and it wasn’t until I poked at Apple’s introductory iOS tutorial that I came across UITextFieldDelegate, which led me to stop in my tracks and look for a macOS counterpart.
Until now, I’ve used the ‘send on end editing’ action of NSTextField to handle the all-too-common ‘user typed into a text field and I want to do something with the result immediately’ scenario. And that was sometimes cumbersome and created a few interesting edge cases, but I filed it away for ‘handle later’.
I can honestly say that in more than ten years of reading books and articles, following tutorials, going to conferences etc etc, _no-one mentioned NSTextFieldDelegate_ as an alternative option. (I have just confirmed this. There *is* one book, but it’s mainly an iOS book with a short section on Mac development.)
I want to flag this not because I’m bitter (well, a tiny bit), but because it illustrates what I think is the biggest problem in Cocoa – particularly macOS – programming: the frameworks contain a lot of useful tools, and far too few entry points for people to find them. (I am, however, bitter over the week I spent trying to make AVAudioPlayer play more than one song in a row. Eventually I came across a mentioning of AVQueuePlayer… BUT NOT IN THE DOCUMENTATION. [Several things that should have let me play more than one song failed buggily. It was a mess.])
I don’t know *how* to order information to make it more accessible – I don’t think _any_ programming environment has solved this – but the current model is not there yet.
Anyway.
0) Create a new Mac app. Add a label, a text field, and a button. Create outlets for the label and textField in the ViewController:
@IBOutlet weak var resultLabel: NSTextField!
@IBOutlet weak var inputField: NSTextField!
Third party transfer
1) Give an action to the button that takes the value of the input field and passes it to the label.
@IBAction func transferText(_ sender: NSButton){
resultLabel.stringValue = inputField.stringValue
}
Build and run. Type ‘hello World’ and press the button.
This is the simplest way of getting a value out of one field and putting it into another; it has the slight disadvantage of needing a third party to manipulate data. (One could argue that this is the purpose of a viewController, to coordinate the data that comes in and to display it to the user.) While the code smell may not be particularly strong, it’s still there, though there are plenty of occasions where you would use this pattern – for a form, for instance, where you might want to validate each field as you go along, but only submit when the user presses the appropriate button.
TextField action
2a)Create an action _for the inputField_:
@IBAction func sendInput(_ sender: NSTextField) {
resultLabel.stringValue = sender.stringValue
}
Build and Run. Type something into the text field, hit enter, and watch the label change.
2b) Adding another text field. Call it ‘alternateInputField’ and create an outlet for it; hook it up to the same IBAction as above.
Build and Run. Write something in the first field, hit enter, it gets displayed on the label, write something in the second field, ditto.
Now tab to teh first field. The Label text miraculously changes.
2c) Change the setting in the IB Attributes Inspector for both textfields from ‘Send On End Editing’ to ‘Send On Enter Only’
Build and Run. Now tabbing away or clicking into the other text field does not trigger the action; only hitting the return or enter keys does.
3) Disconnect the sendInput action from both text fields. Declare the ViewController to conform to NSTextFieldDelegate and connect the inputField’s delegate to the ViewController in IB.
(Text and its delegates turns out to be much more complex than I anticipated, so I am not covering this topic fully at this point in time. There’s NSTextDelegate, NSTextFieldDelegate, and NSTextViewDelegate, and a lot of methods and not too many explanations.
Consequently, there’s a non-trivial chance that the methods I am describing here are not the optimum handling. I’m getting my information from Stackoverflow, Apple’s example code, and experimentation; use at your own peril.)
NSTextFieldDelegate
3a) Anyway. With NSTextFieldDelegate you can use (most simple implementation, does not take note of the sender and assumes you have only one sending text field)
override func controlTextDidChange(_ obj: Notification) {
resultLabel.stringValue = inputField.stringValue
}
(this results in an update every time the text changes, so you get a live update as you type. It also threw up a wall of weird errors in the console which seem to have to do with the length of its text and its attributes. As far as I can make out, these were enabled in Xcode 8; but they do not seem to have any actual relevance to what we’re doing here.)
or
override func controlTextDidEndEditing(_ obj: Notification) {
resultLabel.stringValue = inputField.stringValue
}
(This fires regardless of the NSTextField settings, on both Return and tabbing/clicking away from the field.)
I have not yet worked out what I need to do to make the NSTextDelegate and *its* methods work; but as I said, this is not my priority right now.
3b) The above is a hack and needs to be eliminated. Connect the second textField’s delegate to the ViewController, and replace the controlTextDidEndEditing function with
override func controlTextDidEndEditing(_ obj: Notification) {
if let sender = obj.object as? NSTextField {
resultLabel.stringValue = sender.stringValue
}
Better. We’re now back at getting values from either text field; I’m also back to occasionally getting odd errors in the console. I dislike errors. Errors popping up are a hint that there’s something wrong, but I have no clue where the problem lies; I am relatively confident that they are not actual things I need to deal with, but they still make me twitchy.
3c) Give the first text field a tag of 0 and the second a tag of 1 and change the delegate method to
override func controlTextDidEndEditing(_ obj: Notification) {
if let sender = obj.object as? NSTextField {
switch sender.tag {
case 0: resultLabel.stringValue = "(1) \(sender.stringValue)"
case 1: resultLabel.stringValue = "(2) \(sender.stringValue)"
default: print("Unknown text field")
}
}
In this exercise, the resultLabel showed the string from the inputField on startup (since that was not set, it said “(1) ” This is one of the places where I would not trust the result to remain consistent across releases (I have not tried it under 10.13, for instance). Setting the resultLabel’s stringValue in viewDidLoad had no effect; in order for the resultField to show the data you want, you need to set it in in the inputField.
And more…
Usage Example
Observing changes in a text field to update your data model. I’ll have to see whether this is useful to synching data – in an outlineView and the main part of the app, or whether this introduces more problems than it solves.
Alternatives
Using a field’s action. Both Target/Action and Delegate are standard Cocoa development patterns, and while the letter-by-letter update is a nice option, if all you want is an update when you have finished editing, there’s not much to choose between them. (I seem to recall problems with the action in implementatoin, but I would need to look up what they were, and test whether the delegate method has the same challenges.) On iOS, where you also need to deal with the onscreen keyboard, the textFieldDelegate is mandatory; on macOS, it seems to be optional, though I will need to experiment a lot more with a more complex app.
If all you want to do is update a variable, you could use bindings
Extensions
I haven’t found a use case for controlTextDidBeginEditing yet. (A search of github brings up three mentionings, none of which contain an implementation). There are a lot of fine-grained methods available in the frameworks, so rather than rolling my own, I would investigate the available delegate protocols further. (e.g. NSControlTextEditingDelegate, NSTextDelegate; NSTextFieldDelegate inherits from both, as far as I can see.)
By Extelligent Cocoa • Wiki • • Tags: NSTextFieldDelegate