NSPopupButton in view based NSTableView: getting bindings to work
Asked Answered
S

4

6

Problem Description

I'm trying to achieve something that should be simple and fairly common: having a bindings populated NSPopupButton inside bindings populated NSTableView. Apple describes this for a cell based table in the their documentation Implementing To-One Relationships Using Pop-Up Menus and it looks like this:

enter image description here

I can't get this to work for a view based table. The "Author" popup won't populate itself no matter what I do.

I have two array controllers, one for the items in the table (Items) and one for the authors (Authors), both associated with the respective entities in my core data model. I bind the NSManagedPopup in my cell as follows in interface builder:

  • Content -> Authors (Controller Key: arrangedObjects)
  • Content Values -> Authors (Controller Key: arrangedObjects, Model Key Path: name)
  • Selected Object -> Table Cell View (Model Key Path: objectValue.author

If I place the popup somewhere outside the table it works fine (except for the selection obviously), so I guess the binding setup should be ok.


Things I Have Already Tried

  1. Someone suggested a workaround using an IBOutlet property to the Authors array controller but this doesn't seem to work for me either.

  2. In another SO question it was suggested to subclass NSTableCellView and establish the required connections programmatically. I tried this but had only limited success.

    If I setup the bindings as follows:

    - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
        NSView *view = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
    
        if ([tableColumn.identifier isEqualToString:@"Author") {
            AuthorSelectorCell *authorSelectorCell = (AuthorSelectorCell *)view;
            [authorSelectorCell.popupButton bind:NSContentBinding toObject:self.authors withKeyPath:@"arrangedObjects" options:nil];
            [authorSelectorCell.popupButton bind:NSContentValuesBinding toObject:self.authors withKeyPath:@"arrangedObjects.name" options:nil];
            [authorSelectorCell.popupButton bind:NSSelectedObjectBinding toObject:view withKeyPath:@"objectValue.author" options:nil];
        }
    
        return view;
    }
    

    the popup does show the list of possible authors but the current selection always shows as "No Value". If I add

    [authorSelectorCell.popupButton bind:NSSelectedValueBinding toObject:view withKeyPath:@"objectValue.author.name" options:nil];
    

    the current selection is completely empty. The only way to make the current selection show up is by setting

    [authorSelectorCell.popupButton bind:NSSelectedObjectBinding toObject:view withKeyPath:@"objectValue.author.name" options:nil];
    

    which will break as soon as I select a different author since it will try to assign an NSString* to an Author* property.

Any Ideas?

Steatite answered 5/2, 2013 at 13:43 Comment(0)
M
9

I had the same problem. I've put a sample project showing this is possible on Github.

Someone suggested a workaround using an IBOutlet property to the Authors array controller but this doesn't seem to work for me either.

This is the approach that did work for me, and that is demonstrated in the sample project. The missing bit of the puzzle is that that IBOutlet to the array controller needs to be in the class that provides the TableView's delegate.

Milliner answered 11/1, 2014 at 10:50 Comment(2)
Oh, man, this was driving me absolutely crazy, thanks for the solution. Binding to my File's Owner's array controller IBOutlet (instead of the array controller object in the same nib!) worked. Strikes me as a bug, has anybody filed a radar?Springe
Thanks for leading me to the solution. The other key bit of information is to bind the popup's Selected Object to the Table Cell View (Model Key Path = objectValue.propertyName); this is mentioned in the original question but most tutorials don't seem to emphasize this sufficiently. (Apple's article Populating a Table View Using Cocoa Bindings does mention this, but not in the context of pop-up menus.)Affrica
C
0

Had the same problem and found this workaround - basically get your authors array controller out of nib with a IBOutlet and bind to it via file owner.

Classmate answered 18/2, 2013 at 11:1 Comment(0)
A
0

You can try this FOUR + 1 settings for NSPopUpbutton:

In my example, "allPersons" is equivalent to your "Authors". I have allPersons available as a property (NSArray*) in File's owner.

Additionally, I bound the tableView delegate to File's owner. If this is not bound, I just get a default list :Item1, Item2, Item3

enter image description here

Agential answered 4/7, 2017 at 18:20 Comment(0)
O
0

I always prefer the programmatic approach. Create a category on NSTableCellView:

+(instancetype)tableCellPopUpButton:(NSPopUpButton **)popUpButton
                         identifier:(NSString *)identifier
                    arrayController:(id)arrayController
                       relationship:(NSString *)relationshipName
        relationshipArrayController:(NSArrayController *)relationshipArrayController
              relationshipAttribute:(NSString *)relationshipAttribute
      relationshipAttributeIsScalar:(BOOL)relationshipAttributeIsScalar
                  valueTransformers:(NSDictionary *)valueTransformers
{
    NSTableCellView *newInstance = [[self alloc] init];
    newInstance.identifier = identifier;
    
    NSPopUpButton *aPopUpButton = [[NSPopUpButton alloc] init];
    aPopUpButton.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
    
    [aPopUpButton bind:NSContentBinding  //the collection of objects in the pop-up
        toObject:relationshipArrayController
     withKeyPath:@"arrangedObjects"
         options:nil];
     
    NSMutableDictionary *contentBindingOptions = [NSMutableDictionary dictionaryWithDictionary:[[TBBindingOptions class] contentBindingOptionsWithRelationshipName:relationshipName]];
    
    NSValueTransformer *aTransformer = [valueTransformers objectForKey:NSValueTransformerNameBindingOption];
    if (aTransformer) {
        [contentBindingOptions setObject:aTransformer forKey:NSValueTransformerNameBindingOption];
    }
    [aPopUpButton bind:NSContentValuesBinding // the labels of the objects in the pop-up
        toObject:relationshipArrayController
     withKeyPath:[NSString stringWithFormat:@"arrangedObjects.%@", relationshipAttribute]
         options:[self contentBindingOptionsWithRelationshipName:relationshipName]];
    
    NSMutableDictionary *valueBindingOptions = [NSMutableDictionary dictionaryWithObjectsAndKeys:
        [NSNumber numberWithBool:YES], NSAllowsEditingMultipleValuesSelectionBindingOption,
        [NSNumber numberWithBool:YES], NSConditionallySetsEditableBindingOption,
        [NSNumber numberWithBool:YES], NSCreatesSortDescriptorBindingOption,
        [NSNumber numberWithBool:YES], NSRaisesForNotApplicableKeysBindingOption,
        [NSNumber numberWithBool:YES], NSValidatesImmediatelyBindingOption,
        nil];;
    
    @try {
        // The object that the pop-up should use as the selected item
        if (relationshipAttributeIsScalar) {
            [aPopUpButton bind:NSSelectedValueBinding
                toObject:newInstance
             withKeyPath:[NSString stringWithFormat:@"objectValue.%@", relationshipName]
                 options:valueBindingOptions];
        } else {
            [aPopUpButton bind:NSSelectedObjectBinding
                toObject:newInstance
             withKeyPath:[NSString stringWithFormat:@"objectValue.%@", relationshipName]
                 options:valueBindingOptions];
        }
    }
    @catch (NSException *exception) {
        //NSLog(@"%@ %@ %@", [self class], NSStringFromSelector(_cmd), exception);
    }
    @finally {
        [newInstance addSubview:aPopUpButton];
        if (popUpButton != NULL) *popUpButton = aPopUpButton;
    }
    
    return newInstance;
}

+ (NSDictionary *)contentBindingOptionsWithRelationshipName:(NSString *)relationshipNameOrEmptyString
{
    NSString *nullPlaceholder;
    if([relationshipNameOrEmptyString isEqualToString:@""])
        nullPlaceholder = NSLocalizedString(@"(No value)", nil);
    else {
        NSString *formattedPlaceholder = [NSString stringWithFormat:@"(No %@)", relationshipNameOrEmptyString];
        nullPlaceholder = NSLocalizedString(formattedPlaceholder,
                                            nil);
    }
    
    return [NSDictionary dictionaryWithObjectsAndKeys:
            nullPlaceholder, NSNullPlaceholderBindingOption,
            [NSNumber numberWithBool:YES], NSInsertsNullPlaceholderBindingOption,
            [NSNumber numberWithBool:YES], NSRaisesForNotApplicableKeysBindingOption,
            nil];
}
Ofeliaofella answered 13/12, 2020 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.