dev_mush
dev_mush

Reputation: 2196

Animating a box drawn in drawRect via CGMutablePathRef

my aim is to drow an empty rounded box, that should appear as a hole, onto my UIView to show what's under. I made this by overriding drawRect method as shown below. This gives me the ability to create a rounded rect of the size of my holeRect property (which is a CGRect), drop an inner shadow into it and place it on my view, clearing (using clearColor and CGContextSetBlendMode(context, kCGBlendModeSourceOut) )the area covered by this box and revealing what's behind my view (i took some code from this question and modified it) . Now my problem is that I have to move and resize this rect with an animation, and I can't find a way to do so, maybe I choose the wrong way to do this but I'm not so expert in drawing so any hint would be very appreciated.

- (void)drawRect:(CGRect)rect
{



//I set the frame of my "holey" rect
CGRect bounds =  self.holeRect;
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 20;

//fill my whole view with gray
CGContextSetFillColorWithColor( context, [UIColor colorWithRed:.5 green:.5 blue:.5 alpha:.8].CGColor );
CGContextFillRect( context, rect );

// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x + innerRect.size.width, bounds.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y, bounds.origin.x + bounds.size.width, innerRect.origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, innerRect.origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height, innerRect.origin.x + innerRect.size.width, bounds.origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y + bounds.size.height, bounds.origin.x, innerRect.origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x, innerRect.origin.y);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y, innerRect.origin.x, bounds.origin.y, radius);
CGPathCloseSubpath(visiblePath);

// Fill this path
UIColor *aColor = [UIColor clearColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextFillPath(context);


// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);

// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath);
CGContextClip(context);


// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 5.0f, [aColor CGColor]);

// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];   
CGContextSaveGState(context);   
CGContextAddPath(context, path);
CGContextEOFillPath(context);

// Release the paths
//CGPathRelease(path);
CGPathRelease(visiblePath);

}

Upvotes: 2

Views: 1104

Answers (2)

matt
matt

Reputation: 535989

Take a look at this answer I gave to a question about punching a hole in a UIImageView:

https://stackoverflow.com/a/8632731/341994

Notice that the way it works is that UIImageView's superview's layer has a sublayer that does the masking and thus punches the hole. This means that to animate the hole, all you have to do is animate the movement of that sublayer. That's simple with Core Animation, as described in my book:

http://www.apeth.com/iOSBook/ch17.html#_using_a_cabasicanimation

In other words, if you can encapsulate everything you're doing as a layer, then animating it by moving the layer is trivial.

Upvotes: 1

Aaron Golden
Aaron Golden

Reputation: 7102

Following the setup you have already, what you want to do is store the position of the cutout in an instance variable of your view class (the class implementing this drawRect method) and use an NSTimer to update the position periodically. For example, in your view's code you could make a scheduled timer like this:

[NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(updateCutoutPosition) userInfo:nil repeats:YES];

and then implement the update method:

- (void)updateCutoutPosition {
    // Do whatever you need to do to update the position of the cutout.
    [self setNeedsDisplay]; // Will cause drawRect to be called again soon.
}

Upvotes: 0

Related Questions