Search code examples
macosinterface-buildercocoa-bindingsscreensaver

Is it possible to use bindings in the nib with ScreenSaverDefaults?


I'm working on a screensaver, so I'm supposed to use ScreenSaverDefaults instead of NSUserDefaults. I'd like to have my configure panel use bindings for its UI, but they need to be wired to ScreenSaverDefaults, and I can't see a way to do that; the only defaults controller available in IB (xCode) is the standard user defaults controller. Is there a workaround, or is it just not possible to use bindings in the nib in the context of a screensaver?


Solution

  • I have successfully used bindings in a screen saver.

    (Possibly important note: I'm still developing on 10.6, with XC 3.2.6; this code has only been lightly tested so far on 10.10 (but does seem to work). Also, I mix C++ and Objective-C, so you may have minor code cleanup to do, in what follows.)

    My code creates, and the UI binds to, a custom user defaults controller (controls bind to File's Owner, which is set to my screen saver view, the controller key is empty, and the model key path is self.defaultsController.values.key_val, where key_val is whatever key is used in the defaults plist to access the value bound to a control).

    You must also create your own screen saver defaults, and direct your custom user defaults controller to use them, early in screen saver initialization (such as in the initWithFrame: method of your screen saver view), like so:

    // Find our bundle:
    NSBundle * screenSaverBundle = [NSBundle bundleForClass: [self class]];
    
    // Load per-user screen saver defaults:
    ScreenSaverDefaults * screenSaverDefaults = [ScreenSaverDefaults defaultsForModuleWithName: [screenSaverBundle bundleIdentifier]];
    
    // Create default defaults (values to use when no other values have been established):
    NSDictionary * defaultDefaultValues = [self defaultDefaults];
    
    // Register default defaults as fallback values (in registration domain):
    [screenSaverDefaults registerDefaults: defaultDefaultValues];
    
    // Configure custom user defaults controller to use the correct defaults:
    // NOTE: defaultsController of NSUserDefaultsController * type *MUST* be declared using @property in your header, and accessors must be provided (typically using @synthesize in the implementation section).
    defaultsController = [[NSUserDefaultsController alloc] initWithDefaults: screenSaverDefaults initialValues: nil];   // <- change nil to factory defaults, if desired
    
    // Make sure changes are only saved when committed by user:
    [defaultsController setAppliesImmediately: false];
    

    (The above code was slightly rearranged from various methods in my own code; might have a typo or two, but the gist is correct.)

    An implementation of defaultDefaults looks something like this (pardon my unconventional style):

    - (NSDictionary *) defaultDefaults
        {
        NSString * ResourcesPath = [[self screenSaverBundle] resourcePath];
        NSString * DefaultDefaultsPath = [ResourcesPath stringByAppendingPathComponent: @"DefaultDefaults.plist"];
    
        NSDictionary * DefaultDefaults = [NSDictionary dictionaryWithContentsOfFile: DefaultDefaultsPath];
    
        return DefaultDefaults;
        }
    

    If you want to provide a "factory reset" capability in your screen saver, you'll need to set the initialValues: argument to an "original factory values" dictionary when creating the custom user defaults controller. In response to a "Reset to Factory Defaults" button, simply send revertToInitialValues: to the controller.

    Finally, please note that depending on the intended lifetime of some of the objects created in the above code, you may need to retain some of them (and release them properly later). I assume you understand this bhaller; I'm just pointing this out for the general audience.