Setting and saving preferences

Hi folks,

New ASOC programmer here, so pardon the stupidity in advance. :slight_smile: I’ve done a bit of Obj-C before (apps and such), but I’ve got a new project with ASOC, and I’m stumped here.

I’ve got the program working the way I want it (it’s a backup utility), and there are some fields that I would like to set for user preferences for the next time the user runs the program (username, password, domain, folder, that sort of stuff).

I’ve got a preferences window already set up.

What’s the best way to go about it? Plist? Array?

Any help would be much appreciated. Thanks in advance!

N

The easiest way is with bindings. Just bind the field’s value to the Shared user Defaults Controller with Controller Key of “values” and a Model Key Path of anything you want (you never have to use these key paths unless you want to query the controller in your code somewhere). If you type something into your fields, and quit your app, those values should reappear when you restart.

Ric

Thanks for the prompt reply!

So let me get this straight, because I’m getting into hot water quickly here…

  1. I’ve got a User Defaults on IB. Class is NSUserDefaultsController.

  2. In the ASOC file, I’ve declared a NSUserDefaults property/class outside of the script.

Now, in the script, I’ve got IBOutlet stuff such as…


property usernameTextBox : missing value
	property passwordTextBox : missing value

… which I have in turn do the binding in IB.

Then, when I start the backup, I do stuff such as…


on startSync_(sender)
		
		set usernameValue to usernameTextBox's stringValue()
		set passwordValue to passwordTextBox's stringValue()


… which in turn sets all the strings/integers/ and passes them to a shell script, thus creating backup magic.

Now, here’s the deal:

A. Do I bind the text boxes to the NSUserDefaults class in IB? Just the usual drag/link, then link it to “save?” Or do I need to set up new properties or anything?

B. I tried saving the settings on applicationShouldTerminate, and I could not get the program to quit. Here’s how I went about it. Locked every time.


on applicationShouldTerminate_(sender)
		tell NSUserDefaults
			tell its standardUserDefaults()
				its setObject_forKey_(usernameValue, "usernameValue")
				its setObject_forKey_(passwordValue, "passwordValue")
			end tell
		end tell
		return my NSTerminateNow
		
	end applicationShouldTerminate_


Thoughts? Thanks!

You don’t need to do all that stuff. There’s nothing to do when you exit your app, the user defaults controller does all that automatically. It can all be done in IB with no code and no properties in your code. Just click on one of your fields, go to the bindings pane and click on the disclosure triangle next to “Value” and click on the Bind To check box (the shared user defaults controller should already be showing in the pull down list, if it’s not, choose it from the list).
The controller Key of “values” should already be there too, just choose a name for the Model Key Path, and tab out of that field (make sure that the correct binding shows up next to the word value when you’re done – if you use, say pwd, for the model key path for the password field it should look like “Shared User Def…Controller.values.pwd”). That’s all there is to it, just repeat the process with your other fields. If you need to access the password value in your code you can use “udc’s values()'s valueForKey_(“pwd”)” where udc is the name of the IBOutlet connected to the Shared User Defaults Controller.

Ric

Of course, you can also do it in code instead of using the Shared user Defaults Controller. It’s probably better not to mix the two, just do one or the other. If you do it in code, the easiest way is to have properties that you bind to the value of each field. Then you can do this:

script UserDefaultsAppDelegate
	property parent : class "NSObject"
	property theUserName : missing value
	property thePassword : missing value
	property theFolder : missing value
	property ud : missing value
	
	on applicationWillFinishLaunching_(aNotification)
		set ud to current application's NSUserDefaults's standardUserDefaults()
		set my thePassword to ud's valueForKey_("password")
		set my theFolder to ud's valueForKey_("folder")
		set my theUserName to ud's valueForKey_("username")
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		ud's setValue_forKey_(thePassword, "password")
		ud's setValue_forKey_(theUserName, "username")
		ud's setValue_forKey_(theFolder, "folder")
		return current application's NSTerminateNow
	end applicationShouldTerminate_
	
end script

After Edit: You could add the following 3 lines after the “set ud to current appplication’s NSUserDefaults…” line if you want there to be something in the text fields the first time a user opens the app. If there are no defaults yet, these values are put into the fields, but if the user defaults exist, then those values are used and the ones below are ignored:

ud's registerDefaults_({|password|:"******"})
ud's registerDefaults_({|folder|:"Desktop"})
ud's registerDefaults_({username:"Enter Your Username"})

Ric

That’s true. One advantage of doing it with bindings is that the values get saved regularly, not just when quitting, which can be useful if the app is prone to crashing (heaven forbid;) ).

Rick,

Thanks for the reply! The IB route seems to be the most painless (the code route would run fine, but would again not allow me to shut down the application). So far, so good. Now, that said… :wink:

I was able to bind everything in IB. I’m assuming something is getting saved, somewhere, somehow.

Now, in order for those fields to be populated when I log in (using password as the example)…

  • The property for IBOutlet is “property passwordTextBox : missing value”

  • The string value I pass on to the shell script is usernameValue

  • The name I gave it for UserDefaults is password

… should I add to applicationWillFinishLaunching…


set ud to current application's NSUserDefaults's standardUserDefaults()  
       set my passwordTextBox to ud's valueForKey_("password")


Also, do I still need to call “property NSUserDefaults : class “NSUserDefaults” of current application” right before I start the script?

Right now, the way I have it, it runs fine and quits fine, but I’m not populating any of the fields when I launch.

Do I need to add anything at the terminate level (though it seems to make it not quit at that point…)

Thanks again for all the help, much appreciated!

N

Well, I’m not sure what you are doing now --are you trying to do it using the Shared User Defaults Controller in IB? If that’s the case, then there should be absolutely nothing in the code, zero, zilch!

I did this:

Made a new project
added a text field to the window in IB
bound the value of the text field to Shared User Defaults with Controller Key:values Model Key Path:username
saved and ran
typed in a name an clicked return
quit (using command-q)
restarted, my name I typed in was there, voila!

I didn’t put anything in the code (I didn’t even open up the editor)

Ric

After Edit: You should check to see if anything is actually being saved. The file is saved in your user folder under library —>preferences. If your program was called MyProgram, the file would be com.yourcompany.MyProgram.plist. If you click on the “date modified” header to arrange the list by date, you should see your file near the top if you look right after you close the program.

Ric,

Thanks! I started a new project, and just like you said, zero coding, and it works. Bravo!

The problem is, I can’t get it to work on my current project. Hmmm…

I don’t mind rebuilding the XIB from scratch, so that’s no biggie.

My question is:

Do I still need to have

property usernameTextBox : missing value

and bind that between the window and the app delegate? Or do I scratch that altogether? Because, at some point, I’m going to be using that info, a la:


       set usernameValue to usernameTextBox's stringValue()

Not sure if the “missing value” thing is what’s tripping it up or what.

Getting there… thanks for all the help!

No, you don’t really need a reference to the text field, because you can query the user defaults controller for the value. So you could do this:

set usernameValue to udc’s value()'s valueForKey_(“username”)

You would need to have “property udc:missing value” that you connect to the shared user defaults controller, and “username” would be the model key path that you used to bind that text field’s value to the controller.

As for why it’s not working in your project, it’s probably because you still have some other binding set up that’s wrong. It might be better to just redo your xib.

Ric

Gotcha. So, in recap…


property usernameTextBox : missing value  -- bind that to user defaults controller

set usernameValue to udc's value()'s valueForKey_("username")

And that should do it? Or do I just have one “property udc : missing value” and bind everything to:



set usernameValue to udc's value()'s valueForKey_("xyxyxyxy")
set passwordValue to udc's value()'s valueForKey_("xyxyxyxy")
... 

where “xyxyxyxy” is whatever value I set up in IB ?

Thanks Ric!

No, I don’t think you’ve got it yet. First, when you say “bind” in your comment above, I’m not sure whether you actually mean “make a binding” or make usernameTextBox the IBOutlet for a text field. You need to be clear in your mind about the difference between bindings and outlets. A binding is something you do in the bindings pane of the inspector, while making outlets is something you do by control dragging from the blue cube that is your script class to the UI element, or in the case of a controller, to the blue cube that represents it.

In any case, you want to make an outlet to the controller not to the text box, so you should give it a name that reflects that. I don’t know what you want to call your user defaults controller, but I’m using “udc” as an example (because I’m a lazy typer and I like short names), so I would have this:

property udc:missing value --connected to the shared user defaults controller in IB

set usernameValue to udc’s value()'s valueForKey_(“username”)

When getting values from the controller you have to have that “value()'s” word in there for it to work.

Ric

OK, so I’m not drag from text box to a controller. All I’m doing is finding the udc property and dragging it to the shared values controller.

Now, I only need ONE udc, right? That’s what’s going to get all the values (username, password, whatever integers, …). I DON’T need one udc for each thing, right? So far, so good.

Working OK on a new application. Things are acting up within the current one, but I’ll just build the xib from scratch and see what happens.

Now, couple of things…

set usernameValue to udc’s value()'s valueForKey_(“username”)

It runs OK, but Xcode is seeing something funny with it and not changing all the colors/bold-facing it, if you know what I mean. Is there some syntax I’m missing over here? I’m calling it up right after the (sender) on my script.

Also, to be sure… in IB, am I buinding the text field or the text field cell? Or does it matter any?

I’ll keep working on it and see what happens.

Thanks so much for your help! Told ya I was a newbie at this… :slight_smile:

It might be that you’re trying to save an attributedString rather than a regular NSString – if so that can’t be done directly, because the user defaults controller saves the values in a property list file that can only store certain things, and attributed strings are not one of them. Do you get an error message when you quit your program?

If this is the problem, it’s easy enough to fix. You just need to do one more thing in your bindings. The field under Model Key Path is called Value Transformer. You just need to click on the disclosure triangle there and choose NSKeyedUnarchiveFromData. That converts things that are not compatible with a property list into an NSData object which is.

Technically neither – you’re binding the value of the text field not the text field itself. But, I suppose you could bind the value of the text field cell instead, but I’ve never tried that.

Ric

I’m not getting an error, either way.

But if I use value’s instead of value()'s, it looks fine. What gives?

N

Sorry, that was a typo on my part – it should be plural, values’s or values()'s, either worked when I tried them, but it is a method, so to be consistent, I use values()'s

Ric

Perfect! Looking good so far. Saving all my values and such. XIB doing its rightful thing so far.

I’ve got some checkboxes as part of the preferences, so I’m assuming I can just set the values on those a la straight Obj-C? set whateverValue to udc’s values()'s integerForKey_(“whatever”) — right? The binding on IB should be the same as with text boxes, right?

Thanks again – I’ll keep you posted!

N

If you want to set the value through the controller then you should use: udc’s values()'s setValue_forKey_(1,“check1”)

There is no integerForKey_ or setInteger_forKey_ method. And yes, the binding is the same.

Ric

Napster,

Thank you for your post about preferences. You encouraged me to test this Cocoa feature.

As long I remember, programing preferences has always be a (necessary) pain: implementing dialog resources, writing the code to set up the values, recovering them after the user dismissed the dialog and saving them to a file.

With the Shared User Default Controller, it’s just a dream!

  1. You create a Shared User Default Controller;
  2. You bind it to a property of your code (for example mySUDC : missing value)
  3. You make the dialog(s) in IB, as complex as you want, bind the values to Shared User Default Controller (the values do not even have to be properties in your code)
  4. That’s all. Not a single line of code! Anytime in your program your can check any value with mySUDC’s values()'s valueForKey_(“”) as . The values are updated, so there is no need to save the preferences, nor to restart the application.

Regards,