2

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);

}
Community
  • 1
  • 1
dev_mush
  • 2,136
  • 3
  • 22
  • 38

2 Answers2

1

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.

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks for your answer but this is not going to help me, I'm reading documentation regarding Quartz and CA, but in the meanwhile I can't seem to find a way to make something similar to your tutorial with my code where I'm not using any kind of UIImageView and so on. I need to use the box I draw manually and somehow animate it. – dev_mush Apr 11 '13 at 11:37
  • The image view in my other code is irrelevant. A masking layer can punch a hole in *whatever* is behind it. So you just animate your drawing, along with the mask that punches the hole. – matt Apr 11 '13 at 14:18
  • Do you understand that it is very easy to animate your rounded-rect drawing? If not, figure that out first and then worry about the hole-punching later. Perhaps you could modify your question to describe the animation you are thinking of. – matt Apr 11 '13 at 14:20
  • all I need is just to resize and move around in my view the drawing I made in the drawRect method shown above (perhaps specifying the new size and position via a CGRrect). Actually I don't know how to animate my rounded-rect drawing, I was able to animate things using **UIView**'s method __beginAnimations:context:__ , but never done this via core animation before. Now I'm studying what's behind Core Animation and CALayer, in the meanwhile any help that could speed me up is really appreciated :). – dev_mush Apr 11 '13 at 17:18
  • I already pointed you to the Animation chapter of my book. In my opinion, it's the best explanation! If (as I suspect) you're going to do all this by drawing into a sublayer, you'll probably want to read the preceding Layers chapter as well. – matt Apr 11 '13 at 17:26
  • ok I managed to animate a rounded recto drawing by creating a CAShapeLayer and adding it as a sublayer to my view's layer, than, changing it position and frame I'm able to animate the movement, now how could I make it transparent to see trough without using a mask? If I use a mask I can't animate the changes, here's a code snippet : http://pastebin.com/fKMPKtiL – dev_mush Apr 11 '13 at 19:33
  • "If I use a mask I can't animate the changes" Yes you can. Put the mask in another sublayer and animate the mask when you animate the rounded rect. – matt Apr 11 '13 at 21:39
0

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.
}
Aaron Golden
  • 7,092
  • 1
  • 25
  • 31
  • isn't this solution way too heavy for the CPU? – dev_mush Apr 11 '13 at 11:09
  • If you're set on drawing the animating things in a drawRect method then you're going to have relatively high CPU usage. Whether you can avoid that inefficiency or not depends on what you need to do with the cutout. Does its shape change on every animation frame? If so then you'll need to draw that shape, either in this `drawRect` method or in a mask layer's `drawInContext` method. If the cutout shape does *not* change frequently, just the position, then using a mask layer is a better approach. See http://stackoverflow.com/questions/15609170/get-image-pixels-using-mask/15609515#15609515 – Aaron Golden Apr 11 '13 at 18:49
  • I ran out of room in my previous comment, but the idea with the mask approach is that you'd draw the mask layer's shape once (or infrequently anyway) and you would animate the mask layer's frame to set the position of the cutout. If you're just animating frames you can use the `[UIView animateWithDuration:…` methods and that will be much more efficient than repeatedly triggering `drawRect`. – Aaron Golden Apr 11 '13 at 18:55
  • the problem is that I also need to resize the hole, not just move it around, doing so forces me to redraw the hole. – dev_mush Apr 11 '13 at 19:09
  • You could also try changing the size by just making the mask layer's frame larger or applying a scale transform to the mask layer, but that might make the edges of the cutout look fuzzy. You may be stuck with `drawRect` after all. – Aaron Golden Apr 11 '13 at 19:20
  • I tried using a mask, please can you give a look to my comment up here? – dev_mush Apr 11 '13 at 19:34