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

后端 未结 5 924
星月不相逢
星月不相逢 2020-12-10 09:04

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

相关标签:
5条回答
  • 2020-12-10 09:46

    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:

    enter image description here

    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.

    0 讨论(0)
  • 2020-12-10 09:53

    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.

    0 讨论(0)
  • 2020-12-10 09:54

    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

    enter image description here

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

    enter image description here

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

    enter image description here

    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

    0 讨论(0)
  • 2020-12-10 09:58

    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);
    
    0 讨论(0)
  • 2020-12-10 09:58

    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)
        }
    }
    
    0 讨论(0)
提交回复
热议问题