UIView drawRect: Draw the inverted pixels, make a hole, a window, negative space

╄→гoц情女王★ 提交于 2019-11-27 03:40:15

问题


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:

  1. Add PortholeView.swift to your Xcode 6 (or higher) project

    import 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()
      }
    }
    
  2. Bind your target view in Interface Builder

  1. Adjust the insets, outer-fill color, stroke color, and stroke width right in IB!

  1. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!