views:

33

answers:

1

I'm new in Core Data, and i got a problem i can't get my head around how to do "the right way"

I'll try and examplify my problem.

I got a entity Car. And a list of all the cars in my program. The cars have some attributes, but they are not predefined. So for each car i want to be able to define some properties. Therefore i have defined a new entity CarProperty, with a one to many relation with the car.

In the nscollectionview i would like to show some of the properties from the car, more specefic the number of kilometer (numKm) it has driven (if that property exist). So i want to bind it to a label. But how to do?

I can't say representedObject.properties.numKm, or representedObject.numKm.

How should I get around this?

Hope it makes sense.

A: 

This isn't an easy problem. The thing is, Core Data doesn't know anything about numKm as a property. How is it supposed to know that numKm corresponds to a particular CarProperty object?

The fundamental problem you're describing is key-value coding compliance. Cocoa's going to look for a method called numKm on the properties object. Not finding one, it'll try sending [properties valueForKey:@"numKm"]; Since valueForKey: doesn't know what to do with numKm, you get an error, but not before it calls [properties valueForUndefinedKey:@"numKm"]

But here's the catch: properties is an NSSet generated by Core Data, so you can't subclass it to override valueForUndefinedKey:. What you can do is create your own object that's KVC-compliant for your arbitrary properties and use that instead.

One solution is to subclass NSDictionary and make it act as a proxy. The primitive methods are count, objectForKey: and keyEnumerator. If you override these three methods, you can create an NSDictionary that's linked to your Car object and returns the appropriate CarProperty objects. For example:

@interface PropertyProxy : NSDictionary {
}
@property (nonatomic, readonly, assign) Car *car;

- (id)initWithCar:(Car *)car

@end

@implementation PropertyProxy

@synthesize car = _car;

- (id)initWithCar:(Car *)car {
    if (!(self = [super init]))
        return nil;

    _car = car;

    return self;
}

- (NSInteger)count {
    return [car.properties count];
}

- (id)objectForKey:(NSString *)key {
    return [[car.properties filteredSetUsingPredicate:[NSPredicate predicateWithFormt:@"key == %@", key]] anyObject];
}

- (NSEnumerator *)keyEnumerator {
    return [[car valueForKeyPath:@"properties.key"] objectEnumerator];
}

@end

Then, in your Car class, do this:

@interface Car : NSManagedObject {
    // other stuff
}

@property (nonatomic, readonly) NSDictionary *carProperties;

// other stuff

@end

@implementation Car

// other stuff

- (NSDictionary *)carProperties {
    return [[[PropertyProxy alloc] initWithCar:self] autorelease];
}

@end

(Disclaimer: I just typed this into my web browser, so no guarantees this actually compiles :-))

As you can see, it's not the easiest thing in the world to do. You'll be able to set up key paths like this:

representedObject.carProperties.numKm;

Keep in mind that, while this is key-value coding compliant, it is not key-value observing compliant. So if numKm changes, you won't be able to observe that. You would need to do some extra work to make that happen.

Alex
Thanks alot! That pointet me really much in a direction! Nice to know that i don't reinvent the weel on a common problem with a easy solution.
Jonas Jongejan
This answer is totally correct, however, I have to ask why are you using Core Data just to throw out it's power like this? If you have a car entity some things should really be attributes and other things should be relationships. For example numKm should just be either a float or int as it likely won't repeat between cars very often. The manufacturer on the other hand should totally be a relationship to a `Manufacturer` entity. THen you can even use bindings to view all cars from a particular manufacturer.
theMikeSwan
I can't speak to the OP's situation, but in mine, the properties aren't known until runtime. My app downloads lists of objects from a web server, where the lists can have arbitrary columns with abitrary types. Aside from a couple of properties that are guaranteed to exist and for which I did create properties, all the rest are variable in number and type.
Alex