1

I'm still getting my head around the basic data types in Obj C so this one is really testing me. I've looked at a dozen or more related questions and still can't figure it out.

I have existing hard coding (based on example code I found somewhere) inside a UIView subclass thus:

CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGFloat gradientColours[16] = {
    0.0f, 0.8f, 0.0f, 1.0f, // green
    1.0f, 1.0f, 0.0f, 1.0f, // yellow
    0.9f, 0.0f, 0.0f, 1.0f  // red
};
CGFloat gradientLocations[] = { 0.0, 0.5, 1.0 };
CGGradientRef gradient = CGGradientCreateWithColorComponents(colourSpace, gradientColours, gradientLocations, 3);

That works fine but every instance will use this same gradient so I want to add a method to set the gradient.

I want to be able to make a simple call like

[myView1 setGradient:{
    0.0f, 0.8f, 0.0f, 1.0f, // green
    1.0f, 1.0f, 0.0f, 1.0f, // yellow
    0.9f, 0.0f, 0.0f, 1.0f  // red
    withLocations:{ 0.0, 0.5, 1.0 }];

I cannot for the life of me figure out how to code the method signature to accept these two arrays in a way they can successfully be used in the CGGradientCreateWithColorComponents constructor.

Any help much appreciated.

zkarj
  • 657
  • 9
  • 23
  • possible duplicate of [How can I pass a C array to a objective-C function?](http://stackoverflow.com/questions/11247350/how-can-i-pass-a-c-array-to-a-objective-c-function) – Caleb Jan 03 '14 at 22:46
  • 1
    another reference: http://stackoverflow.com/questions/6628461/objective-c-pass-array-as-function-argument – Alex Jan 03 '14 at 22:50
  • How would you pass an array of CGFloat to a C method? – Hot Licks Jan 03 '14 at 23:31

2 Answers2

4
 - (void) setGradient:(CGFloat*)gradientArray withArrayLength:(int)lengthOfArray {
      // do stuff here
 }

then you can call the method like so:

 CGFloat someArray[16] = { ... };
 [someObject setGradient:someArray withArrayLength:16];

EDIT: Rob reminded me that an argument containing the length of the array is necessary in this situation.

David Schwartz
  • 507
  • 3
  • 14
2

You can use NSArray and NSNumber literal syntax to get something like that. Here's how you call it:

[self setGradientColorComponents:@[
    @0.0f, @0.8f, @0.0f, @1.0f, // green
    @1.0f, @1.0f, @0.0f, @1.0f, // yellow
    @0.9f, @0.0f, @0.0f, @1.0f  // red
] withLocations:@[
    @0.0f, @0.5f, @1.0f
]];

Here's how you implement it:

- (void)setGradientColorComponents:(NSArray *)components withLocations:(NSArray *)locations {
    NSUInteger count = components.count;
    CGFloat componentFloats[count];
    for (NSUInteger i = 0; i < count; ++i) {
        componentFloats[i] = [components[i] floatValue];
    }

    count = locations.count;
    CGFloat locationFloats[count];
    for (NSUInteger i = 0; i < count; ++i) {
        locationFloats[i] = [locations[i] floatValue];
    }

    CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(rgb, componentFloats, locationFloats, count);
    CGColorSpaceRelease(rgb);

    // do whatever with gradient

    CGGradientRelease(gradient);
}

On the other hand, you can use a variadic function. Since a variadic function can only have one variadic list of arguments, you must mix the locations with the color components. You must also mark the end of the list somehow. Here's how you call it:

[self setGradient:
    0.0, 0.0, 0.8, 0.0, 1.0, // location 0.0, color green
    0.5, 1.0, 1.0, 0.0, 1.0, // location 0.5, color yellow
    1.0, 0.9, 0.0, 0.0, 1.0, // location 1.0, color red
    -1.0
];

This is more dangerous, because it's up to you to make sure that you only pass doubles to the function, and it's up to you to make sure you pass the -1.0 sentinel at the end of the list. I recommend sticking with my first solution (using NSArray and NSNumber literals). That said, here's how you implement the variadic solution:

- (void)setGradient:(CGFloat)location0, ... {
    size_t count = 0;
    va_list ap;
    va_start(ap, location0);
    // Note that when you pass a float as a variadic argument, the compiler promotes it to double.
    double f = location0;
    while (f >= 0.0 && f <= 1.0) {
        ++count;
        f = va_arg(ap, double);
    }
    va_end(ap);
    NSAssert(count % 5 == 0, @"number of arguments is %lu which is not a multiple of 5", (unsigned long)count);

    count /= 5;
    CGFloat locations[count];
    CGFloat components[4 * count];
    va_start(ap, location0);
    f = location0;
    for (size_t i = 0; i < count; ++i) {
        locations[i] = (CGFloat)f; f = va_arg(ap, double);
        components[i * 4 + 0] = (CGFloat)f; f = va_arg(ap, double);
        components[i * 4 + 1] = (CGFloat)f; f = va_arg(ap, double);
        components[i * 4 + 2] = (CGFloat)f; f = va_arg(ap, double);
        components[i * 4 + 3] = (CGFloat)f; f = va_arg(ap, double);
    }
    va_end(ap);

    CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(rgb, components, locations, count);
    CGColorSpaceRelease(rgb);

    // do whatever with gradient

    CGGradientRelease(gradient);
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 1
    these both seem excessively complicated. in the first solution the argument gets wrapped in a NSArray, but the CGGradientCreateWithColorComponents() function requires an array of CGFloats. why not simply pass the array of CGFloats as an argument? – David Schwartz Jan 03 '14 at 23:46
  • If there's a more direct way I'd love to see how to do it. In the meantime, this one worked for me. As I said in the question I'm not familiar with the basics of the language yet. I began writing in Obj C ten days ago with no prior experience of any C-type language! – zkarj Jan 04 '14 at 00:29
  • 2
    @DavidSchwartz You cannot pass a native array as an argument in C. You can pass a pointer to the first element of an array, but then you have to also somehow pass the length of the array or add a sentinel at the end. Either way is error-prone because the compiler doesn't help you detect a wrong count or a missing sentinel, and either error can lead to undefined behavior. That is why I recommend using Objective-C literals as in my first solution. In that solution, any likely error will either be caught at compile-time, or detected at runtime and cause an exception. – rob mayoff Jan 04 '14 at 03:13
  • @robmayoff thanks for the explanation. I obviously need to review my C fundamentals... – David Schwartz Jan 04 '14 at 03:32