How do I connect text fields from child window? - swift

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.

Related

#IBAction fails to work in Swift, though I can connect it in Interface Builder, and it works in Objective-C

In all cases, I can wire user-interface buttons to actions with Interface Builder. But the buttons work for Objective-C but not for Swift.
Objective-C example (it works):
- (IBAction)TogglePlaying:(id)sender {
(details snipped for brevity)
}
Swift example (it doesn't work, though it's wired to its button):
#IBAction func Go(_ sender: Any) {
print("Going")
OutputText!.stringValue = InputText!.stringValue
}
I have no idea of what the difference might be, because everything I've found on using IBAction in Swift indicates that I've written it correctly. Also, in Interface Builder, I've set File's Owner's Custom Class correctly.
Update:
Using
ios - Find what Action is called by a Button in interface builder in debug mode - Stack Overflow
Find what Action is called by a Button in interface builder in debug mode
I used "Debug View Hierarchy", right-clicked on "NSButton - Go!" in the widget-hierarchy view, and selected "Print Description of NSButton - Go!"
I got
Printing description of $13:
<NSButton: 0x7fac1b116250>
I then did:
po [0x7fac1b116250 allTargets]
error: Execution was interrupted, reason: Attempted to dereference an invalid ObjC Object or send it an unrecognized selector.
The process has been returned to the state before expression evaluation.
Update:
I tried
po [0x7faf38011790 target]
(new address of that button) and I got
nil
Update:
The complete code of TLWindow, in Swift:
import Cocoa
class TLWindow: NSWindowController {
#IBOutlet weak var InputText: NSTextField!
#IBOutlet weak var OutputText: NSTextField!
override var windowNibName: NSNib.Name? {
return NSNib.Name("TLWindow")
}
#IBAction func Go(_ sender: Any?) {
print("Going")
OutputText!.stringValue = InputText!.stringValue
}
}
I don't know how to show that the xib is wired up correctly without doing a lot of screenshots. But it is, with the "Go!" button connected to "Go:" in "File's Owner". Also, "File's Owner" is set to "TLWindow", this class.
You are creating an instance of TLWindow in newDocument(), but then you're letting it go out-of-scope...
Try this:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// add a property to "hang onto" the instance
var myTLWindow: TLWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
newDocument(self)
}
// Create an app window
#IBAction func newDocument(_ sender: Any?) {
let wc = TLWindow()
// add this line
myTLWindow = wc
wc.showWindow(self)
}
}

Just start HelloWorld display in MacOS application development [duplicate]

This question already has answers here:
(NSMenuItem): missing setter or instance variable
(3 answers)
Closed 4 years ago.
Tutorial for Hello World
I tried using the tutorial, link described above which was going until the execution but when i use the button click to display the name it show some error
Failed to connect (sayButtonClicked) outlet from
(HelloWorld.ViewController) to (NSButton): missing setter or instance
variable
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var nameField: NSTextField!
#IBOutlet weak var helloLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func sayButtonClicked(_ sender: Any) {
var name = nameField.stringValue
if name.isEmpty{
name = "World"
}
let greeting = "Hello\(name)!"
helloLabel.stringValue = greeting
}
}
It sounds like you have an outlet connection in your storyboard named "sayButtonClicked" but "sayButtonClicked" is an IBAction, not an outlet. Try doing the following:
Delete that "sayButtonClicked" outlet from your storyboard.
Open your storyboard in the editor and the source-code for the view controller in an "assistant editor"
Control-drag from the dot next to your IBAction in your code onto the button in your storyboard. This will connect the action to the button. You may need to select "touch up inside" as the event you want to trigger the action.

Set maximum characters (to one) in a NSTextfield in 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()
}
}

Preferences Menu in OS X with Swift

I'm running into a few issues trying to set up a Preferences menu for my app.
I'm currently using Swift and Xcode 7.
I have a Preferences window that opens from the Main menu. However, once it is open is where my issues lie. I have 2 buttons, one that resets the user inputs, and the other which is supposed to save the information.
I've got the reset button set up to make the text inputs default values.
My next goal is to have the save button set the variables with any entered text in the text fields and close the Preferences window. Then, in an ideal world, I'd like to be able to re-open the Preferences to make sure they saved.
Is this possible?
Currently, my program hangs if I click the Save button, or the Preferences window will not reopen if the red X in the corner is clicked.
Here is my current Preferences Window code, not even sure what of this is unnecessary:
import Cocoa
protocol PreferencesWindowDelegate {
func preferencesDidUpdate()
}
class PreferencesWindow: NSWindowController, NSWindowDelegate {
#IBAction func ResetPref(sender: NSButton) {
UserName.stringValue = "Name"
UserEmail.stringValue = "Email"
ManEmail.stringValue = "Man. Email"
SSEmail.stringValue = "SS Email"
}
#IBOutlet var SSEmail: NSTextField!
#IBOutlet var ManEmail: NSTextField!
#IBOutlet var UserEmail: NSTextField!
#IBOutlet var UserName: NSTextField!
#IBAction func SavePref(sender: NSButton) {
}
override var windowNibName : String! {
return "PreferencesWindow"
}
override func controlTextDidBeginEditing(_: NSNotification){
func controlTextDidChange(obj: NSNotification){
}
func windowDidLoad() {
super.windowDidLoad()
self.window?.center()
self.window?.makeKeyAndOrderFront(nil);
NSApp.activateIgnoringOtherApps(true);
}
var delegate: PreferencesWindowDelegate?
func controlTextDidEndEditing(obj: NSNotification){}
func windowWillClose(notification: NSNotification) {
delegate?.preferencesDidUpdate()
}
}
}
Any guidance is appreciated, as this is my first time working in Swift!
Is your preferencesDidUpdate() function defined?
You typically save preferences by using NSUserDefaults, which saves preferences to a .plist file. This is the Cocoa way. To save NSUSerDefaults, you just do
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(myValue, forKey: myKey)

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