5

I recently read some documentation and some blog entries about Key-Value Coding and found it extremely useful. I want to utilize it for my case, but I haven't been successful so far.

In my case, I have an NSMutableArray containing CGPoint's transformed to NSValue's. I will be adding many points to this data array and I want to get the minimum/maximum x and y values. For example:

NSMutableArray *data = [[NSMutableArray alloc] init];
[data addObject:[NSValue valueWithCGPoint:CGPointMake(20.0f, 10.0f)]];
[data addObject:[NSValue valueWithCGPoint:CGPointMake(5.0f, -15.0f)]];
[data addObject:[NSValue valueWithCGPoint:CGPointMake(-5.0f, 20.0f)]];
[data addObject:[NSValue valueWithCGPoint:CGPointMake(15.0f, 30.0f)]];

// min X:  -5.0,  max X: 20.0
// min Y: -15.0,  max Y: 30.0

I have two approaches so far. The first one is to store these four values in class instance variables and when a new object is added, compare it with those variables and update them if necessary. The second one involves a for-loop to to find the extremum values, however this approach would be inefficient.

If possible, I would like to use KVC to do this task and I believe that it would be a more general solution than those I have. In the future, I might also need to remove some of the objects from the array and that would make my first approach inapplicable and all I am left with would be the for-loop.

I tried to use some key paths, i.e. @"@max.x", @"@max.position.x" but all I got is an NSUnknownKeyException.

tolgamorf
  • 809
  • 6
  • 23
  • Where are these key paths coming from? Anyway, if you keep your array sorted then you will have a much better time (and you won't need a for loop either). You can insert via divide and conquer (start in the middle, go halfway either way depending on if the value is higher or lower, should only take a maximum of about 7 tries per 100 values). – borrrden May 28 '13 at 09:21
  • @borrrden, I cannot sort the array because these data are for plotting purposes. I got the keyPaths from the [documentation](https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Key-ValueCodingExtensions/Key-ValueCodingExtensions.html): `CGPoint Key Paths If the value of a given property is a CGPoint data type, you can append one of the field names in Table C-3 to the property to get or set that value. For example, to change the x component of a layer’s position property, you could write to the key path position.x.` – tolgamorf May 28 '13 at 09:23
  • I meant the "max" thing. What is that? – borrrden May 28 '13 at 09:25
  • @tolgamorf: It works if you access the key path of a of `CALayer`, but not with a general `NSValue`, compare http://stackoverflow.com/a/15657943/1187415. – Martin R May 28 '13 at 09:27
  • @borrrden `max` can be used for example to get the maximum value of an array of NSNumbers. See the answer [here](http://stackoverflow.com/a/3080688/1576979). – tolgamorf May 28 '13 at 09:27
  • @MartinR Thanks, I also saw it in the documentation but I thought maybe I could use a similar keyPath for CGPoints as well. But, apparently not. – tolgamorf May 28 '13 at 09:30
  • 2
    @borrrden also this [blog entry](http://nshipster.com/kvc-collection-operators/) has nice explanations for simple collection operators that can be used in key paths. – tolgamorf May 28 '13 at 09:32
  • Interesting, I never knew about this! – borrrden May 28 '13 at 09:37
  • Indeed, it makes some operations really simple. :) – tolgamorf May 28 '13 at 09:38

2 Answers2

9

You could make it work if you use a category to tell NSValue how to access the x and y value of a CGPoint:

@interface NSValue (MyKeyCategory)
- (id)valueForKey:(NSString *)key;
@end

@implementation NSValue (MyKeyCategory)
- (id)valueForKey:(NSString *)key
{
    if (strcmp([self objCType], @encode(CGPoint)) == 0) {
        CGPoint p = [self CGPointValue];
        if ([key isEqualToString:@"x"]) {
            return @(p.x);
        } else if ([key isEqualToString:@"y"]) {
            return @(p.y);
        }
    }
    return [super valueForKey:key];
}
@end

Then

CGFloat maxx = [[yourArray valueForKeyPath:@"@max.x"] floatValue];

actually works. But note that a simple loop will be much faster, in particular if you have to compute all 4 values @max.x, @max.y, @min.x, @min.y.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

This is impossible with your current approach. A CGPoint is not an obj-c object, so you can't explore its values by KVC. NSValue sees CGPoint only as a binary data and has no understaning about the inner structure of the data. The names x and y for CGPoint fields are not even accessible after compilation.

(The only exception when CGPoint values can be accessed by KVC are CAAnimation and CALayer which override the key-value search).

Possibly the best solution is to implement it by yourself, note that you can do some clever caching - test current max/min when adding points and clear min/max when current min/max is removed.

Sulthan
  • 128,090
  • 22
  • 218
  • 270