iPhone - Draw transparent rectangle on UIView to reveal view beneath

40,483

Solution 1

You have to override the top view's drawRect method. So, for example, you might create a HoleyView class that derives from UIView (you can do that by adding a new file to your project, selecting Objective-C subclass, and setting "Subclass of" to UIView). In HoleyView, drawRect would look something like this:

- (void)drawRect:(CGRect)rect {
    // Start by filling the area with the blue color
    [[UIColor blueColor] setFill];
    UIRectFill( rect );

    // Assume that there's an ivar somewhere called holeRect of type CGRect
    // We could just fill holeRect, but it's more efficient to only fill the
    // area we're being asked to draw.
    CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );

    [[UIColor clearColor] setFill];
    UIRectFill( holeRectIntersection );
}

If you're using Interface builder, make sure to change the holey view's class to HoleyView. You can do that by selecting in the view in Interface Builder and selecting the "Identity" pane in the inspector (its the one on the far right the the "i" icon).

You also have to set the top view to be non-opaque either with the following code snippet, or by un-checking the Opaque checkbox in the view's properties in Interface Builder (you'll find it in the View section of the view's attributes) and set its background color's opacity to 0% (background color is set in the same section).

topView.opaque = NO;
topView.backgroundColor = [UIColor clearColor];

If you want to do circles, you have to use Core Graphics (aka Quartz 2D). You'll probably want to read the programming guide, which is available here.

To draw an ellipse instead of the rectangle, your drawRect would look something like this:

- (void)drawRect:(CGRect)rect {
    // Get the current graphics context
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor( context, [UIColor blueColor].CGColor );
    CGContextFillRect( context, rect );

    if( CGRectIntersectsRect( holeRect, rect ) )
    {
        CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
        CGContextFillEllipseInRect( context, holeRect );
    }
}

Solution 2

There is truth in all the other answers, but it is quite possible to draw with clear colour, or so to say erase the existing colours within any path, even with the -[UIBezierPath fill] or similar convenience methods. All you have to do is to set the context blend mode to an appropriate value depending on the effect you are trying to achieve, like so:

CGContextSetBlendMode(context, kCGBlendModeClear);
[[UIColor clearColor] set];
[myArbitraryPolygonPath fill];

There are around two dozen different options you could choose from, take a look around the CGContext reference.

Solution 3

to draw an ellipse, instead of a rect, just set the blendMode to clear:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor( context, [UIColor blackColor].CGColor );
    CGContextFillRect( context, rect );

    CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );

    CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
    CGContextSetBlendMode(context, kCGBlendModeClear);

    CGContextFillEllipseInRect( context, holeRect );
}

Solution 4

These may be the dumb ways but I would not do it the way you describe, but make it look the way you want it to look.

(Jacques' answer just popped up - looks good to me)

Approach 1: In the view controller to build a list of rectangles that tile around the exposed "hole". As your holes increase in number the number of tiling rects will also increase.

Approach 2: Reverse your thinking. The blue view should be at the back with sections of the red view placed on top of it. You still see a red view with all but the "holes" masked by the blue view, but what you are really doing is copying regions from the view you want to expose and putting them on top of the mask as you make each hole. If you have some effect simulating depth you could add that as required with each hole.

Neither requires subclassing or drawRect:.

Share:
40,483
Dave
Author by

Dave

Updated on August 07, 2020

Comments

  • Dave
    Dave almost 4 years

    I currently have two UIViews: one of a red background and the other blue. The blue view is a subview of the red view. What I would like to do is be able to "cut" out rectangles on the blue view so that the red view can be visible. How do you go about doing this?

  • rpetrich
    rpetrich over 13 years
    UIGraphicsFillRect with clearColor set as the fill color won't erase the rect it will paint empty color on top of the existing contents. CGContextClearRect is the only function that can "erase" part of a CGContext (not that is should be used in the general case--it's much better to mask the earlier drawing commands)
  • Jacques
    Jacques over 13 years
    I agree that masking could be a better way to go, but the code above will work (after fixing my boneheaded error where I used the wrong name for UIFillRect). I just ran it on a device to double-check. I did leave out that you need a transparent background for the view as well, so either do topView.background = [UIColor clearColor] or set the view's background color's opacity to 0.
  • Dave
    Dave over 13 years
    Awesome! Thanks guys, that worked like a charm. This works great for rectangles, but how would it work for different shapes (i.e. circle)?
  • Jacques
    Jacques over 13 years
    Dave, I added an example for drawing a circle. If you want to do arbitrary shapes, you need to create a path. Take a look at the Quartz 2D programming guide and the CGContext reference manual for details. Also, if you like an answer, please click the checkmark beside it to mark it as accepted :-).
  • Dave
    Dave over 13 years
    Thanks Jacques! I really appreciate the help.
  • Dee
    Dee over 12 years
    @Dave My app requirement is something like this, when I move my finger on blue view, I need to draw a circle on the blue view through that circle I should be able to see redview. How can I do this?
  • Kalle
    Kalle over 12 years
    This answer is so full of awesome. Thanks a lot. +1!
  • studyro
    studyro over 11 years
    @Jacques The UIRectFill() works fine, but CGContextFillRect with a clearColor seems draw nothing depending on my test.
  • Morkrom
    Morkrom over 10 years
    So close... The hole draws once ( my clear view is draggable), but then calls: <Error>: CGContextSetCompositeOperation: invalid context 0x0
  • lothorp
    lothorp over 10 years
    I am having a terrible time trying to draw holes with shadows... tips or pointers would be appreciated...
  • Josip B.
    Josip B. over 9 years
    My eclipse is always in black colour, even when I use CGContextSetBlendMode(context, kCGBlendModeClear), opaque = NO and backgroundColor = [UIColor clearColor]. Does anyone know why?
  • avance
    avance almost 9 years
    This answer did not work for me. I had to add CGContextSetBlendMode(context, kCGBlendModeClear) as suggested by another answer.
  • Dima Deplov
    Dima Deplov about 8 years
    @studyro you need to set opaque to false, this step is needed.
  • CIFilter
    CIFilter over 7 years
    You don't even need to set a clear color. The clear blend mode is going to set the premultiplied pixel value to 0 regardless of the source color.
  • damjandd
    damjandd over 7 years
    Could you expand on how to use this for my issue: stackoverflow.com/questions/42066123/…
  • iphonic
    iphonic about 7 years
    The last line should contain holeRectIntersection not holeRect.
  • Mathieson
    Mathieson almost 7 years
    This is great. To use in Swift, call UIBezierPath.fill(with blendMode: CGBlendMode, alpha: CGFloat)