Search code examples
objective-ccocoacocoa-bindings

Changing cocoa bindings value programmatically


I have an NSButton in my preferences to interact with adding the application to the LoginItems. If the adding of the login item fails, I want to uncheck the box so the user doesn't get a false sense that it was added to the login items. However, after doing this, when I click the checkbox again, the bindings is not triggered.

enter image description here

- (void)addLoginItem:(BOOL)status
{
    NSURL *url = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:
                  @"Contents/Library/LoginItems/HelperApp.app"];

    // Registering helper app
    if (LSRegisterURL((__bridge CFURLRef)url, true) != noErr) {
        NSLog(@"LSRegisterURL failed!");
    }

    if (!SMLoginItemSetEnabled((__bridge CFStringRef)[[NSBundle mainBundle] bundleIdentifier], (status) ? true : false)) {
        NSLog(@"SMLoginItemSetEnabled failed!");
        [self willChangeValueForKey:@"startAtLogin"];
        [self.startAtLogin setValue:[NSNumber numberWithBool:[self automaticStartup]] forKey:@"state"];
        [self didChangeValueForKey:@"startAtLogin"];
    }
}

- (void)setAutomaticStartup:(BOOL)state
{
    NSLog(@"Set automatic startup: %d", state);
    if ([self respondsToSelector:@selector(addLoginItem:)]) {
        [self addLoginItem:state];
    }
}

- (BOOL)automaticStartup
{

    BOOL isEnabled  = NO;

    // the easy and sane method (SMJobCopyDictionary) can pose problems when sandboxed. -_-
    CFArrayRef cfJobDicts = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
    NSArray* jobDicts = CFBridgingRelease(cfJobDicts);

    if (jobDicts && [jobDicts count] > 0) {
        for (NSDictionary* job in jobDicts) {
            if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:[job objectForKey:@"Label"]]) {
                isEnabled = [[job objectForKey:@"OnDemand"] boolValue];
                break;
            }
        }
    }
    NSLog(@"Is Enabled: %d", isEnabled);
//    if (isEnabled != _enabled) {
    [self willChangeValueForKey:@"startupEnabled"];
        startupEnabled = isEnabled;
    [self didChangeValueForKey:@"startupEnabled"];
//    }

    return isEnabled;
}

I have my databinding for the checkbox bound to self.automaticStartup. If I remove the line [self.startAtLogin setValue:[NSNumber numberWithBool:[self automaticStartup]] forKey:@"state"]; then the bindings work fine, but it doesn't uncheck, if the adding of the item fails.

How can I change this binding value programmatically so that every other binding event is not ignored?


Solution

  • From your explanation, your bound value is automaticStartup, but you are sending willChangeValueForKey: for startAtLogin. In order for bindings to work correctly, you need to alert on the change to the bound variable at some point. However, since you are in the midst of setAutomaticStartup: at the time, it's not really safe to do that here.

    In this case, I would not use bindings to perform the change itself, I would consider the old-style IBAction mechanism and then set the checkbox value manually through an IBOutlet when you can confirm the status.