问题
Using the Employees-Departments example what I want to do is bind a column to "Departments.arrangedObjects.employees.@sum.hoursWorked" as outlined below:
Entity Employee
- attributes: "firstName", "lastName", "hoursWorked"
- relationship: "departments"
Entity Department
- attributes: "name"
- relationship: "employees"
I want a table which will display some summary info about departments.
I bind the first column to my "Departments" array controller, "arrangedObjects.name". I can have a column displaying the number of employees in a department by binding to "arrangedObjects.employees.@count"
However I can't get a sum of the hoursWorked by employees as I assume I might by binding to "arrangedObjects.employees.@sum.hoursWorked"
The error I get is along the lines of "[<_NSFaultingMutableSet 0x1acea0> addObserver:forKeyPath:options:context:] is not supported. Key path: @sum.hoursWorked"
I believe this is because it is not possible to bind to the many end of a to-many relationship. If so how can I do what I want to do?
For extra credit, say each employee also has another attribute, "race", I would also like my summaries table to show the number of unique races in each department.
Thanks in advance.
回答1:
I encountered the same errors you did. It seems that while you can get the set of employees and perform some set of aggregate operations on it by doing something like:
Department* dept = ; NSSet* employees = dept.employees; NSNumber* sumOfHoursWorked = [employees valueForKeyPath: @"@sum.hoursWorked"];
There's a difference when you bind. Bindings are asking to observe the key path, not evaluate it once. Given that, it kinda, sorta makes sense why you can't bind to these key paths. Kinda. Sorta.
Now. As for a solution, what I usually do in cases like this is write a tiny little NSValueTransformer subclass to do just what I need, and then plug that into IB. This way, I write the ten lines of code I need, but don't end up doing the whole NSTableView data source spiel for want of a simple aggregate. In this case, you might do something like this:
// Declaration
@interface MySumOfHoursWorkedTransformer : NSValueTransformer
@end
@interface MyNumberOfRacesTransformer : NSValueTransformer
@end
// Implementation
@implementation MySumOfHoursWorkedTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
double hoursWorked = 0.0;
for (Employee* employee in [value valueForKey: @"employees"])
{
NSNumber* hoursWorkedNumber = employee.hoursWorked;
hoursWorked += hoursWorkedNumber ? [hoursWorkedNumber doubleValue] : 0.0;
}
retVal = [NSNumber numberWithDouble: hoursWorked];
}
return retVal;
}
@end
@implementation MyNumberOfRacesTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
NSMutableSet* raceSet = [NSMutableSet set];
for (Employee* employee in [value valueForKey: @"employees"])
{
id raceVal = employee.race;
if (raceVal)
[raceSet addObject: raceVal];
}
retVal = [NSNumber numberWithUnsignedInteger: raceSet.count];
}
return retVal;
}
@end
Then, just bind those TableColumns to ArrayController.arrangedObjects and plug in the appropriate value transformer subclass. Now, you won't be able to edit those values, but what would it mean to edit an aggregate value anyway?
Hope that helps. I've used this approach a bunch, and it sure beats giving up on bindings.
来源:https://stackoverflow.com/questions/3826009/cocoa-bindings-binding-to-the-many-end-of-a-to-many-relationship