问题
With the code below I am drawing a rounded rectangle. It draws a nice solid light gray filled rounded rectangle (at the size of "self"). I actually want to draw the pixel inverse of this, that is: not a solid rounded rectangle, but a window or hole in the shape of this round rectangle in a solid light gray rectangle.
Is there a reverse clip method that I need to use? Or do I need to use a bezier path? Excuse if this is very basic, can't find the info though.
Thanks for reading!
- (void)drawRect:(CGRect)rect
{
// get the context
CGContextRef context = UIGraphicsGetCurrentContext
CGContextSaveGState(context);
//draw the rounded rectangle
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetRGBFillColor(context, 0.8, 0.8, 0.8, 1.0);
CGContextSetLineWidth(context, _lineWidth);
CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect));
CGFloat radius = _cornerRadius;
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);
CGContextMoveToPoint(context, minx, midy);
// Add an arc through 2 to 3
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
// Add an arc through 4 to 5
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
// Add an arc through 6 to 7
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
// Add an arc through 8 to 9
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
// Close the path
CGContextClosePath(context);
// Fill the path
CGContextDrawPath(context, kCGPathFill);
CGContextRestoreGState(context);
}
回答1:
Add multiple subpaths to your context, and draw with mode kCGPathEOFill. The Quartz 2D Programming Guide explains in more detail.
// Outer subpath: the whole rect
CGContextAddRect(context, rrect);
// Inner subpath: the area inside the whole rect
CGContextMoveToPoint(context, minx, midy);
...
// Close the inner subpath
CGContextClosePath(context);
// Fill the path
CGContextDrawPath(context, kCGPathEOFill);
回答2:
Here's yet another approach, using just UI object calls:
- (void)drawRect:(CGRect)rect
{
[[UIColor lightGrayColor] setFill];
CGRect r2 = CGRectInset(rect, 10, 10);
UIBezierPath* p = [UIBezierPath bezierPathWithRoundedRect:r2 cornerRadius:15];
[p appendPath: [UIBezierPath bezierPathWithRect:rect]];
p.usesEvenOddFillRule = YES;
[p fill];
}
Yields this:
The white is the background of the window; the grey is the UIView. As you can see, we're seeing right thru the view to whatever is behind it, which sounds like what you're describing.
回答3:
For a drop-in solution:
Add
PortholeView.swiftto your Xcode 6 (or higher) projectimport UIKit @IBDesignable class PortholeView: UIView { @IBInspectable var innerCornerRadius: CGFloat = 10.0 @IBInspectable var inset: CGFloat = 20.0 @IBInspectable var fillColor: UIColor = UIColor.grayColor() @IBInspectable var strokeWidth: CGFloat = 5.0 @IBInspectable var strokeColor: UIColor = UIColor.blackColor() override func drawRect(rect: CGRect) { // Prep constants let roundRectWidth = rect.width - (2 * inset) let roundRectHeight = rect.height - (2 * inset) // Use EvenOdd rule to subtract portalRect from outerFill // (See http://stackoverflow.com/questions/14141081/uiview-drawrect-draw-the-inverted-pixels-make-a-hole-a-window-negative-space) let outterFill = UIBezierPath(rect: rect) let portalRect = CGRectMake( rect.origin.x + inset, rect.origin.y + inset, roundRectWidth, roundRectHeight) fillColor.setFill() let portal = UIBezierPath(roundedRect: portalRect, cornerRadius: innerCornerRadius) outterFill.appendPath(portal) outterFill.usesEvenOddFillRule = true outterFill.fill() strokeColor.setStroke() portal.lineWidth = strokeWidth portal.stroke() } }Bind your target view in Interface Builder
- Adjust the insets, outer-fill color, stroke color, and stroke width right in IB!
- Set up your constraints and other views, keeping in mind that you may very well have to modify PortholeView if you need your rect to scale in special ways for rotation and whatnot. In this case, I've got a UIImage behind the PortholeView to demonstrate how the round rect gets "cut out" of the surrounding path thanks to the 'even odd rule'.
Thanks to @matt for the underlying drawing code & to Apple for exposing IBInspectable/IBDesignable in Interface Builder.
P.S. This venerable Cocoa with Love post will help you understand the "even/odd" rule, and its sibling, the "winding" rule, as well as providing some additional strategies for drawing cutout shapes. http://www.cocoawithlove.com/2010/05/5-ways-to-draw-2d-shape-with-hole-in.html
回答4:
Another approach: use UICreateGraphicsContextWithOptions(size, NO, 0) to make a bitmap. Draw the rectangle into the bitmap. Switch to the erasure blend mode:
CGContextSetBlendMode(con, kCGBlendModeClear);
Now draw the ellipse path and fill it. The result is a rectangle with a transparent elliptical hole. Now close out the image graphics context and draw the image into your original context.
回答5:
I have been looking around for to do this a project I'm working on.
I ended up doing something like this.
I hope this helps someone.
Swift code:
import UIKit
class BarCodeReaderOverlayView: UIView {
@IBOutlet weak var viewFinderView: UIView!
// MARK: - Drawing
override func drawRect(rect: CGRect) {
super.drawRect(rect)
if self.viewFinderView != nil {
// Ensures to use the current background color to set the filling color
self.backgroundColor?.setFill()
UIRectFill(rect)
let layer = CAShapeLayer()
let path = CGPathCreateMutable()
// Make hole in view's overlay
CGPathAddRect(path, nil, self.viewFinderView.frame)
CGPathAddRect(path, nil, bounds)
layer.path = path
layer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = layer
}
}
override func layoutSubviews () {
super.layoutSubviews()
}
// MARK: - Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
来源:https://stackoverflow.com/questions/14141081/uiview-drawrect-draw-the-inverted-pixels-make-a-hole-a-window-negative-space