Set maximum characters (to one) in a NSTextfield in Swift - swift

How do i set the maximum amount of characters in multiple NSTextfields (OSX cocoa app, NOT iOS) to one in Swift?
Please explain how to do it, because I'm a complete newbie when it comes to OSX app development and therefore I don't understand short answers like "Use NSFormatter", because I have no idea what it is and how to implement it. Like Examples

There's no built-in way to simply set the maximum, I think because you need to decide what behavior you want. For example, if there's already one character in the field, and the user enters a second character, what should happen? Should the 2nd character be ignored? Replace the first? Beep?
In any case, you can get whatever behavior you want using the NSText Delegate methods. Make your view controller (or whatever object has the logic) a delegate of the text field, and implement the various delegate method(s) to do what you need.
Again, the exact behavior is up to you, but if I were implementing this, I might be inclined to make the text field always use the last character entered (such that, if one character is already present, pressing a second replaces the first). To do that, you'd want to override textDidChange to look at the value of the text field, and modify it if appropriate. Something like:
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var textField: NSTextField!
override func controlTextDidChange(obj: NSNotification) {
if self.textField.stringValue.characters.count > 1 {
self.textField.stringValue = String(self.textField.stringValue.characters.last!)
}
}
}

You don't need to limit the characters a user will enter just only look at the first character entered. In fact, it is probably better since you will always have to handle possible user errors. If you want to you can issue an alert that they entered too many by getting the characters.count. You might want an alert if they don't answer at all. The code below will work as is if you set up a storyboard with 1 NSTextField and one button and connect them. If you have more than one textfield, i.e. like a multiple choice test, just set up all the text fields the same way.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var firstLetter: NSTextField!
Add as many text fields as you need:
#IBOutlet weak var secondLetter: NSTextField!
#IBOutlet weak var thirdLetter: NSTextField!
etc.
#IBAction func button(sender: AnyObject) {
var firstEntry = firstLetter!.stringValue
var index1 = firstEntry.startIndex
if firstEntry.characters.count > 1 {
runMyAlert("Bad USER! ONLY ONE Character!")
}
if firstEntry == "" { //left it blank
runMyAlert("You need to enter at least one character!")
exit(0) //or you'll crash on next line
}
var nameLetter1:Character = firstEntry[index1]
print( "First Letter == \(nameLetter1) ")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func runMyAlert( alertMessage: String){
var myWindow = NSWindow.self
let alert = NSAlert()
alert.messageText = "ERROR ERROR ERROR"
alert.addButtonWithTitle("OK")
alert.informativeText = alertMessage
alert.runModal()
}
}

Related

How do I programatically move the cursor from one NSTextField to another after a character count has been reached?

I'm trying to create a window where the user can enter the activation key for the product and I've done so by creating 5 different NSTextField's, as shown in the image.
What I want to add to this is the ability for the cursor to move to the next text field once a character count has been reached (which is 5 characters maximum per textfield).
I did find code for this but it was for IOS and didn't work since i didn't know what changes to make (Code for the IOS version)
here's the code I tried
override func viewDidLoad() {
super.viewDidLoad()
ActKeyOne.textDidChange(Notification.Name.init(rawValue: "textchanged"))
}
it gave the error:
Cannot convert value of type 'Notification.Name' (aka 'NSNotification.Name') to expected argument type 'Notification'
The answer to part of this this question was posted here by #cheesey
here is the complete code to create a window that takes the license key for a product from a user (This is using swift 4).
First set the Text Fields as the delegates and first responders in the viewDidLoad Function and then change the first responder once the string limit is hit
class CommercialActivationView: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
#IBOutlet weak var firsttextfield: NSTextField!
#IBOutlet weak var secondtextfield: NSTextField!
#IBOutlet weak var thirdtextfield: NSTextField!
firsttextfield.window?.makeFirstResponder(firsttextfield)
firsttextfield.delegate = self
}
func makeFirstResponder() {
if firsttextfield.stringValue.count == 5 {
firsttextfield.window?.makeFirstResponder(secondtextfield)
}
if secondtextfield.stringValue.count == 5 {
secondtextfield.window?.makeFirstResponder(thirdtextfield)
}
}
}
Now to create the extension that creates the character limit or the text field every time the user edits the TextField (Here i'm limiting the number of characters per text field to 5).
extension CommercialActivationView: NSTextFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
let object = obj.object as! NSTextField
if object.stringValue.count > 5{
object.stringValue = String(object.stringValue.dropLast())
makeFirstResponder()
}
}
This works such that once 5 characters are reached in 1 TextField it switches to the next one automatically. Also the code I've posted is for 3 TextFields more text fields can be add if needed.

Learning Swift & Xcode - #IBAction func reset for display if UITextField is empty

Disclaimer: I am teaching myself Swift & Xcode so my question is rather simple.
I'm building a simple application to get started, which has a text field connected to a String output.
The lesson I'm on currently has an excerpt which reads:
"The reset method simply needs to clear out the text of both the nameField and the lyricsView—you can do this by setting each of their text properties to an empty string."
I understand this probably involves an if statement, but I think the explanation on this is rather poor.
Here's the viewcontroller:
class ViewController: UIViewController {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var lyricsView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func reset(_ sender: Any) {
}
#IBAction func displayLyrics(_ sender: Any) {
}
}
Can someone explain what they mean by setting the properties of nameField and lyricsView to an empty string in order to reset?
Thanks!
If you want to clear the text of a textField or a textView, just set the text property to an empty string. As your lesson hint:
The reset method simply needs to clear out the text of both the
nameField and the lyricsView—you can do this by setting each of their
text properties to an empty string.
The reset method should like this:
#IBAction func reset(_ sender: Any) {
nameField.text = ""
lyricsView.text = ""
}
To clear those fields I'd use:
#IBAction func reset(_ sender: Any) {
nameField?.text = ""
lyricsView?.text = ""
}
The question mark will safely execute the code even if, for some reasons, those fields are not loaded yet, or have been freed or removed.
ADD this to reset method to remove the content:-
nameField.text = ""
lyricsView.text = ""
When you enter something into nameField something shows up in your lyricsView. So, there should be a way to clear what you've entered (and what is displayed). Hence, the reset function (I guess it's bound to a button).
Once you hit reset the text in nameField and lyricsView should disappear. You can do this by assigning both to something called an empty string, which is just two double-quotes:
let anEmptyString = ""
You'd need to assign the "" to the nameField and lyricsView text property.

How do I connect text fields from child window?

I have a single-window (OSX, not iOS) app that works beautifully, but I need to add a Preferences window. I have created the window and linked it to the Preferences menu (to which it opens and displays as expected). However, now I need to interact with the text fields I have set up.
Here is my new window:
Settings.xib
Here is my main window:
MainMenu.xib
Again, I can display it just fine, but I need to be able to set the values of the text fields in the Settings window. Not real sure how to do that? I am trying to stay away from Storyboards at the moment (just trying to keep it simple; maybe phase II). I am storing the values from the Settings window in the Keychain (which works beautifully in MainMenu.xib). Now I just need to be able to enter them and save them from Settings.xib.
My file structure is pretty straightforward. I have AppDelegate.swift which is doing all of the work. Do I (and how do I) connect Settings.xib to AppDelegate? I know I can bind the text fields from Settings.xib to outlets in AppDelegate (but when I do that I get an error: [General] [<NSApplication 0x6080001005a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key accountNumber).
I know this is a n00b question, but it is so simple it is hard to find an applicable answer.
Thanks!
Creating a new window controller was the key. My problem, as I stated in my question, was getting the text fields wired up as functional outlets. I was able to show the window as desired, just couldn't get to the text fields.
I created SettingsWindowController.swift to subclass NSWindowController. My connections are as shown:
Window connections
File's Owner connections
Additionally, in Settings.xib, the Custom Class of File's Owner is set to SettingsWindowController. Then the individual text fields are connected to SettingsWindowController.swift as outlets.
In SettingsWindowController.swift:
`import Cocoa
class SettingsController: NSWindowController, NSWindowDelegate
{
#IBOutlet weak var accountNumber: NSTextField!
#IBOutlet weak var meterNumber: NSTextField!
#IBOutlet weak var webCredentialKey: NSTextField!
#IBOutlet weak var webCredentialPassword: NSTextField!
// allows window creation without needing to specify NIB name
override var windowNibName: String! {
return "Settings"
}
override func windowDidLoad() {
super.windowDidLoad()
// initialization code removed for brevity
}
func windowWillClose(_ notification: Notification) {
// teardown code removed for brevity
NSApplication.shared().stopModal()
}
}'
In AppDelegate.swift,
`var prefs: SettingsController? = nil
#IBAction func OpenPreferences(_ sender: Any) {
DispatchQueue.main.async(execute: { () -> Void in
NSApplication.shared().runModal(for: (self.prefs?.window)!)
})
}`
func applicationDidFinishLaunching(_ aNotification: Notification)
{
// Insert code here to initialize your application
prefs = SettingsController()
}`
Hope this helps someone else.

textDidChange points to the wrong NSCollectionViewItem

I am trying to build an NSCollectionView filled with multiple editable TextViews. (OS X app in Swift.) My subclass of NSCollectionViewItem is called NoteViewItem. I am trying to have the program detect when one of the TextView has changed. I tried using both controlTextDidChange and textDidChange in the NoteViewItem's delegate with test print statement to see which would work. ControlTextDidChange did nothing; textDidChange recognized a change happened, so I went with that.
The problem is that textDidChange appears to point to a different NoteViewItem than the one that was shown on screen in the first place. It wasn't able to recognize the variable (called theNote) set in the original NoteViewItem; when I ask NoteViewItem to print String(self), I get two different results, one while setting the initial text and one in textDidChange. I'm wondering if I've set up my delegates and outlets wrongly. Any thoughts on why my references are off here?
Here's my code for NoteViewItem:
import Cocoa
class NoteViewItem: NSCollectionViewItem, NSTextViewDelegate
{
// MARK: Variables
#IBOutlet weak var theLabel: NSTextField!
#IBOutlet var theTextView: NSTextView!
var theNote: Note?
{
didSet
{
// Pre: The NoteViewItem's theNote property is set.
// Post: This observer has set the content of the *item's text view*, and label if it has one.
guard viewLoaded else { return }
if let theNote = theNote
{
// textField?.stringValue = theNote.noteText
theLabel.stringValue = theNote.filename
theTextView.string = theNote.noteText
theTextView.display()
print("theTextView.string set to "+theTextView.string!+" in NoteViewItem "+String(self))
}
else
{
theLabel.stringValue = "Empty note?"
}
}
}
// MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
// Hopefully this will set the note's background to white.
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.whiteColor().CGColor
}
// MARK: - NSTextViewDelegate
/*
override func controlTextDidChange(notification: NSNotification)
{
print("Control text changed.")
}
*/
func textDidChange(notification: NSNotification)
{
if let noteyMcNoteface = theNote
{
print("On edit, we have a note: "+String(noteyMcNoteface))
}
else
{
print("On edit, we have no note. I am NoteViewItem "+String(self))
}
}
}
I figured it out. My delegate, in the TextView, was connected to the wrong object in the Interface Builder for NoteViewItem.xib. I had connected it to the object labelled Note View Item, under objects in the outline. It should have been connected to File's Owner instead, since File's Owner stands for the NoteViewItem.swift class associated with the xib.
You'd think that if you want to connect the delegate to the NoteViewItem class and there is exactly one Note View Item listed in the outline, then that Note View Item is the thing you want to connect it to. Nope, you connect it to something entirely different that isn't called the Note View Item but is the Note View Item. I'm glad Interface Builder makes things so simple.

Cannot form weak reference to instance of class NSTextView

Using Swift only, here's my code in AppDelegate.swift:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow
#IBOutlet var textField: NSTextView
#IBAction func displaySomeText(AnyObject) {
textField.insertText("A string...")
}
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
In the interface builder, I have an object hooked up to receive input from a button, then the output goes to a text view. I'm trying to get the text view to populate with some text when I hit the button.
I tried this with a text field as well, and didn't get the error, but got a "dong" error sound and it didn't do anything else. In Objective-C, you had to use the (assign) parameter to get this to work from what I understand.
What am I doing wrong?
You cannot store a weak reference to an NSTextView due to historical issues with Cocoa and AppKit. See details in the Clang documentation. NSTextView is marked as NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE in NSTextView.h, there are also a few other classes to lookout.
Have you tried a Swift unowned reference instead of weak, which is kind of like Objective-C's assign (what you'd use for an NSTextView outlet in Objective-C)?
Use #IBOutlet var scrollView: NSScrollView instead of #IBOutlet var textField: NSTextView.
Then create a property returns documentView in scrollView.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow
#IBOutlet var scrollView: NSScrollView
var textField: NSTextView {
get {
return scrollView.contentView.documentView as NSTextView
}
}
#IBAction func displaySomeText(AnyObject) {
textField.insertText("A string...")
}
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
I have tried to replicate what you described. I have created a new OS X app using Xcode 6 beta 7. I have dropped a button and text view in the main form.
I think your problem is that the connection to the Text View object is not correct for some reason. To make things easier, I've connected the objects using control-drag, which adds the required code automatically. So first I've connected the Text View. To do this click on the text view object until Text View is selected. When I do this in my version of Xcode, the first time I click on the object, Bordered Scroll View is selected. Clicking on it again then selects Clip View. Finally, clicking on it again selects Text View. Now I control-drag from the object to the AppDelegate.swift code (It helps to display the Assistant Editor so that you have your form UI and code side-by-side).
By doing this I get this little window:
Notice that the type is NSTextView and the storage is Weak. I've only had to add the name and click Connect. This adds the following code in AppDelegate.swift:
#IBOutlet var textField: NSTextView!
The code is almost exactly like the one you have, except for the ! at the end of the line, which forces to unwrap the value of textField.
Just with that, the code as you have it in your question should work.
The other thing I would suggest is not to use insertText. According to Apple's documentation for NSTextView.insertText:
This method is the entry point for inserting text typed by the user
and is generally not suitable for other purposes. Programmatic
modification of the text is best done by operating on the text storage
directly.
As far as I understand this, programmatic modification of the text by operating on the text storage directly means dealing with NSText, which NSTextView inherits from. So instead, use NSText.string. This is how the click button action looks in my code:
#IBAction func displaySomeText(sender: NSButton) {
// If you want to add a new 'A string... ' every time you click the button
textField.string! += "A string... "
// otherwise just use
//textField.string = "A string..."
}
I have added the Button Action in the same way as I've added the Text View Outlet, by control-dragging, and, in this case, selecting NSButton as the sender, instead of leaving the default AnyObject.
#IBOutlet automatically makes a property weak IIRC, but weak doesn't automatically make a property optional. But it is required that a weak property be made optional, as the property could at any time be deallocated and made nil. So you have to declare your #IBOutlets as optional.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow? // Optional
#IBOutlet var textField: NSTextView?
#IBAction func displaySomeText(AnyObject) {
textField?.insertText("A string...") // Only call the method if the object exists
}
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
Does the "dong" error suggest a responder chain problem? What if you call becomeFirstResponder on the text field before inserting the text?
To create a weak reference user the weak keyword:
example:
#IBOutlet weak var myView: UIView
In your case
#IBOutlet weak var textField: NSTextView