NSPathControl, User Defaults Maddness

I am not sure if I am just totally new to NSPathControl and/or if the user defaults / preference file functionality has changed in some weird way that I missed.

I want to have a path control on a window that, when a path is selected, it writes that to a preference. I have used the user defaults feature before and it still works with a label for me so I don’t think things have changed. I have the PathCell value bound to the Shared User Defaults Control. It seems when the user selects a value for that it creates a preference file and writes stuff into the plist.

However, when I try setting the default when the app launches, no preference file is created and the preference is not written. Also, if I delete the preference file, the app continues to retain all the bound values as if the preference file was still there… not sure where those are coming from.

Help!

The applicationWillFinishLaunching contains

set recDefaults to {apSXPathToWatch:"Macintosh HD:Users:mark_munro:Desktop:TEST:"}
set defaults to current application's NSUserDefaults's standardUserDefaults()
defaults's registerDefaults_(recDefaults)

I have toyed with converting the path to a URL which seems to be what the path control wants to have but get all sorts of errors. The code above executes but simply does nothing.

Does anyone have any good example of interactions with an NSPathControl, preferably where the values are stored in User Defaults?

You’re not going to have much luck with HFS paths…

I tried POSIX and HFS paths and one other (fileURL or something or other)… problems all around.

My two questions are:

  1. How to make NSPathControl link to user defaults (user selects seem to work and stick, setting a path into defaults programmatically is my problem)

  2. How to clear values in an app interface that are bound to user defaults when the preference file is deleted. They seem to stick even after rebooting the app, etc.

Here is the “other” thing I tried…

tell current application's |NSURL| to set theURL to fileURLWithPath_("Macintosh HD:Users:mark_munro:Desktop:TEST:")

This gave me all kinds of grief, perhaps from my lack of understanding of it’s data structure…

The problem to bind the url property of NSPathControl to NSUserDefaults is that NSURL is not included in the supported types of property list.
Although there are methods of NSUserDefaults URLForKey: and setURL:forKey: they are convenience methods which are internally mapped to [NSURL fileURLWithPath:[defaults stringForKey:]] and setString:[url path] forKey:.

The workaround is either to use a subclass of NSValueTransformer to do the NSString - NSURL mapping or to use the standard method with a key value coding compliant instance variable which is bind to the NSPathControl element and read/write NSUserDefaults programmatically.

For the NSValueTransformer solution this is the NSValueTransformer script, the file is called PathValueTransformer.applescript


script PathValueTransformer
	property parent : class "NSValueTransformer"
	
	on transformedValueClass()
		return current application's |NSURL|'s |class|()
	end transformedValueClass
	
	on allowsReverseTransformation()
		return true
	end allowsReverseTransformation
	
	on transformedValue(value)
		if value is not missing value then
			return current application's |NSURL|'s fileURLWithPath:(value's stringByExpandingTildeInPath())
		end if
		return value
	end transformedValue
	
	on reverseTransformedValue(value)
		if value is not missing value then
			return value's |path|'s stringByAbbreviatingWithTildeInPath()
		end if
		return value
	end reverseTransformedValue
	
end script

as soon as possible “ in awakeFromNib() or better initialize() “ register the value transformer with


set PathValueTransformer to current application's NSClassFromString("PathValueTransformer")
set transformer to PathValueTransformer's alloc()'s init()
current application's NSValueTransformer's setValueTransformer:transformer forName:"PathValueTransformer"

Now in Interface Builder in all Value Transformer popup menus PathValueTransformer will appear

By the way “ as Shane mentioned “ Cocoa works only with POSIX paths

tell current application's |NSURL| to set theURL to fileURLWithPath_("/Users/mark_munro/Desktop/TEST/")

Stefan,

Thanks… That kind of worked but I was getting crashes so I had to move on to a classic label with a path stored in text. However, I still feel like something is now different with user defaults because they are just not behaving the same as they used to.

With a text label bound to user defaults, no preference file is created and yet values are being retained. Do you know where they are going? Also, if I change my default value that I am registering it updates the value displayed. Prefiously that would only change if the preference didn’t exist.

Does that make any sense to anyone?

I am going to try just rebuilding the entire app as this is a new project and this is the first thing I tried building in it.

The application crashes if the value transformer is initialized too late, the best place is the class method +initialize()

I’ve never tried to bind the string value of an NSTextField to user defaults.

Try this. In your app delegate:

	on applicationWillFinishLaunching_(aNotification)
        set thePath to "/Users/shane/Desktop"
        set theNSURL to current application's class "NSURL"'s fileURLWithPath_(thePath)
        set archURL to current application's NSArchiver's archivedDataWithRootObject_(theNSURL)
        set recDefaults to {aaSXPathToWatch:archURL}
        set defaults to current application's NSUserDefaults's standardUserDefaults()
        defaults's registerDefaults_(recDefaults)
	end applicationWillFinishLaunching_

For your path control, bind values to a Model Key Path of aaSXPathToWatch, and for Value Transformer, choose NSUnarchiveFromData.

The .plist file is an implementation detail. User defaults set (and get) their values via a separate process, and how (and where) that process stores them is its business. More to the point, when it saves them to disk them is also its business. It used to be that they were stored almost immediately, but nowadays there can be a delay – for at least one OS version, it could be considerable.

If you want to clear an app’s defaults, don’t rely on deleting the file (unless you’re happy to log out as well). Use defaults in Terminal (carefully). Better still, run this script:

use framework "Foundation"
current application's NSUserDefaults's standardUserDefaults()'s setPersistentDomain:(missing value) forName:"com.yourbiz.whatever" -- id of the app in question