views:

44

answers:

1

I'm curious as to the 'proper' method for achieving the following functionality using Quarts2D:

I want to have a view, and to be able to add a circle at any coordinate. As soon as I add the circle it should expand at a predefined rate; I'd also like to repeat this process and have a number if these expanding circles.

Think Missile Command:

Yellow spots keep expanding

Generally, if I was writing this in C++ using SDL or some other graphics library I would:

Have a class to represent an 'growing circle' Have a vector/array to hold pointers to all the 'growing circles' I create.

All circles diameters would be increased each tick, and in my renderloop I would iterate the list and draw appropriate circles to my buffer.

This, however, doesn't seem to fit well with how I've generally used views in previous iPhone development.

So I guess it's kind of open-ended, but is there a 'correct' way for something like this?

Would it be in a game loop style (as described above), or should I be subclassing UIView for a 'circle' object, and override drawRect? I guess I would then have to add each circle by creating a view and adding it to my main view?

Initial investigation also brought me across references to the CAShapeLayer class, though I'm guessing this might be much the same as implementing the UIView subclassing technique.

+1  A: 

Here's one way to do it. Add the following code to your UIViewController subclass and you'll get a circle that grows and then fades away wherever you touch:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self addGrowingCircleAtPoint:[[touches anyObject] locationInView:self.view]];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (flag && [[anim valueForKey:@"name"] isEqual:@"grow"]) {
        // when the grow animation is complete, we fade the layer
        CALayer* lyr = [anim valueForKey:@"layer"];
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.fromValue = [lyr valueForKey:@"opacity"];
        animation.toValue = [NSNumber numberWithFloat:0.f];
        animation.duration = .5f;
        animation.delegate = self;
        lyr.opacity = 0.f;  
        [animation setValue:@"fade" forKey:@"name"];
        [animation setValue:lyr forKey:@"layer"];
        [lyr addAnimation:animation forKey:@"opacity"];
    } else if (flag && [[anim valueForKey:@"name"] isEqual:@"fade"]) {
        // when the fade animation is complete, we remove the layer
        CALayer* lyr = [anim valueForKey:@"layer"];
        [lyr removeFromSuperlayer];
        [lyr release];
    }

}

- (void)addGrowingCircleAtPoint:(CGPoint)point {
    // create a circle path
    CGMutablePathRef circlePath = CGPathCreateMutable();
    CGPathAddArc(circlePath, NULL, 0.f, 0.f, 20.f, 0.f, (float)2.f*M_PI, true);

    // create a shape layer
    CAShapeLayer* lyr = [[CAShapeLayer alloc] init];
    lyr.path = circlePath;

    // don't leak, please
    CGPathRelease(circlePath);
    lyr.delegate = self;

    // set up the attributes of the shape layer and add it to our view's layer
    lyr.fillColor = [[UIColor redColor] CGColor];
    lyr.position = point;
    lyr.anchorPoint = CGPointMake(.5f, .5f);
    [self.view.layer addSublayer:lyr];

    // set up the growing animation
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.fromValue = [lyr valueForKey:@"transform"];
    // this will actually grow the circle into an oval
    CATransform3D t = CATransform3DMakeScale(6.f, 4.f, 1.f);
    animation.toValue = [NSValue valueWithCATransform3D:t];
    animation.duration = 2.f;
    animation.delegate = self;
    lyr.transform = t;  
    [animation setValue:@"grow" forKey:@"name"];
    [animation setValue:lyr forKey:@"layer"];
    [lyr addAnimation:animation forKey:@"transform"];
}
Art Gillespie
Nice solution, I like the effect :-)This would not be too useful if I wanted to query the state of a particular circle though? Although I suppose I could keep a reference to all existing layers and use those to query things like the layer's dimensions (should I want to handle collisions, for example)
davbryn
Yeah, there are a lot of ways to skin that cat. Take a look at CALayer`s `hitTest:` method. It uses the layer's rectangular bounds by default, but you could subclass CAShapeLayer for your expanding circle and provide a hitTest implementation that is smart about transparency/geometry.
Art Gillespie
Brilliant, thanks!
davbryn