Search code examples
swiftuikitdrawinguibezierpath

How to fill overlapped parts in UIBezierPath?


I would like to draw mask to around of crop rectangle that it has grips in corners and I'm trying to fill inside the rectangle with combined UIBezierPath like: 4 circle and 1 rectangle than assigning this path to CAShapeLayer to use it as sublayer on my view. So far so good it's filling desired area just overlapped parts (when we appending circles on rectangle) not filled (quarter triangle in all corner), so I tried using combinedPath.usesEvenOddFillRule = false - combinedPath.fill() - combinedPath.close() but none of them worked with me! So I need to fill these parts also someone can help me please?

You can check the image from this link

My code :

    // draw mask
    let path = UIBezierPath(rect: viewBackground.bounds)

    let maskRect = CGRect(x: imgView.frame.origin.x + roiRect.minX * rate, y: imgView.frame.origin.y + roiRect.minY * rate, width: roiRect.width * rate, height: roiRect.height * rate)
    // First apth is for rectangle
    let rectPath = UIBezierPath(rect: maskRect)
    path.append(rectPath)

    // and these paths for corners
    let cornerMaskCircleSize: CGFloat = circleSize/2
    let maskTopLeftCornerPath = UIBezierPath(ovalIn: CGRect(x: maskRect.minX - cornerMaskCircleSize/2, y: maskRect.minY - cornerMaskCircleSize/2, width: cornerMaskCircleSize, height: cornerMaskCircleSize))
    let maskTopRightCornerPath = UIBezierPath(ovalIn: CGRect(x: maskRect.maxX - cornerMaskCircleSize/2, y: maskRect.minY - cornerMaskCircleSize/2, width: cornerMaskCircleSize, height: cornerMaskCircleSize))
    let maskBottomRightCornerPath = UIBezierPath(ovalIn: CGRect(x: maskRect.maxX - cornerMaskCircleSize/2, y: maskRect.maxY - cornerMaskCircleSize/2, width: cornerMaskCircleSize, height: cornerMaskCircleSize))
    let maskBottomLeftCornerPath = UIBezierPath(ovalIn: CGRect(x: maskRect.minX - cornerMaskCircleSize/2, y: maskRect.maxY - cornerMaskCircleSize/2, width: cornerMaskCircleSize, height: cornerMaskCircleSize))

    // Combining all of them in one path
    path.append(maskTopLeftCornerPath)
    path.append(maskTopRightCornerPath)
    path.append(maskBottomRightCornerPath)
    path.append(maskBottomLeftCornerPath)
    path.usesEvenOddFillRule = true
    path.fill()
    path.close()

    cropMask.removeFromSuperlayer()
    cropMask.bounds = viewBackground.frame
    cropMask.position = viewBackground.center
    cropMask.path = path.cgPath
    cropMask.fillRule = .evenOdd
    cropMask.fillColor = UIColor.init(hexString: "#212f41").cgColor
    cropMask.opacity = 0.6

    viewBackground.layer.addSublayer(cropMask)

Solution

  • After 5 hours research finally I found this extension and it fixed my problem with different way, this extension it helps for drawing three quarter circle so I used it to draw circles that empty from rectangle corners side.

    And here is an example code for usage :

    fileprivate func drawMask() {
    
        let rectPath = UIBezierPath(rect: CGRect(x: 100, y: 200, width: 175, height: 150))
        let leftTopCornerPath = UIBezierPath(quarterCircleCentre: CGPoint(x: 100, y: 200), radius: 10, quadrant: .BottomRightThreeQuarterCircle)
        let rightTopCornerPath = UIBezierPath(quarterCircleCentre: CGPoint(x: 275, y: 200), radius: 10, quadrant: .BottomLeftThreeQuarterCircle)
        let leftBottomCornerPath = UIBezierPath(quarterCircleCentre: CGPoint(x: 100, y: 350), radius: 10, quadrant: .TopRightThreeQuarterCircle)
        let rightBottomCornerPath = UIBezierPath(quarterCircleCentre: CGPoint(x: 275, y: 350), radius: 10, quadrant: .TopLeftThreeQuarterCircle)
    
        let combined = UIBezierPath(rect: self.view.frame)
        combined.append(rectPath)
        combined.append(leftTopCornerPath)
        combined.append(rightTopCornerPath)
        combined.append(leftBottomCornerPath)
        combined.append(rightBottomCornerPath)
    
        let maskLayer = CAShapeLayer()
        maskLayer.bounds = self.view.bounds
        maskLayer.position = self.view.center
        maskLayer.path = combined.cgPath
        maskLayer.fillRule = .evenOdd
        maskLayer.fillColor = UIColor.black.cgColor
        maskLayer.opacity = 0.5
    
        self.view.layer.addSublayer(maskLayer)
    
    }
    

    Extension :

    extension UIBezierPath {
    
    
    enum QuarterCircleQuadrant {
        case TopLeft, BottomLeft, TopRight, BottomRight, TopLeftThreeQuarterCircle, BottomRightThreeQuarterCircle, BottomLeftThreeQuarterCircle, TopRightThreeQuarterCircle
        var startAngle: CGFloat {
            switch self {
            case .BottomLeft:
                return CGFloat(Double.pi)
            case .BottomLeftThreeQuarterCircle:
                return CGFloat(Double.pi)
            case .TopLeft:
                return CGFloat(Double.pi)
            case .BottomRight:
                return 0
            case .TopRight:
                return 0
            case .TopRightThreeQuarterCircle:
                return 0
            case .TopLeftThreeQuarterCircle:
                return CGFloat(Double.pi)
            case .BottomRightThreeQuarterCircle:
                return 0
            }
    
        }
        var endAngle: CGFloat {
            switch self {
            case .BottomLeft:
                return CGFloat(Double.pi/2)
            case .BottomLeftThreeQuarterCircle:
                return CGFloat(Double.pi/2)
            case .TopLeft:
                return CGFloat(Double.pi) * 1.5
            case .BottomRight:
                return CGFloat(Double.pi/2)
            case .TopRight:
                return CGFloat(Double.pi) * 1.5
            case .TopRightThreeQuarterCircle:
                return CGFloat(Double.pi) * 1.5
            case .TopLeftThreeQuarterCircle:
                return CGFloat(Double.pi) * 1.5
            case .BottomRightThreeQuarterCircle:
                return CGFloat(Double.pi/2)
            }
    
    
        }
    
        var clockwise: Bool {
            switch self {
            case .BottomLeft:
                return false
            case .TopLeft:
                return true
            case .BottomRight:
                return true
            case .TopRight:
                return false
            case .BottomLeftThreeQuarterCircle:
                return true
            case .TopRightThreeQuarterCircle:
                return true
            case .TopLeftThreeQuarterCircle:
                return false
            case .BottomRightThreeQuarterCircle:
                return false
            }
    
    
        }
    
    }
    
    convenience init(quarterCircleCentre centre:CGPoint, radius:CGFloat, quadrant:QuarterCircleQuadrant)
    {
        self.init()
        self.move(to: CGPoint(x: centre.x, y:centre.y))
        self.addArc(withCenter: centre, radius:radius, startAngle:quadrant.startAngle, endAngle: quadrant.endAngle, clockwise:quadrant.clockwise)
        self.close()
    }
    }
    

    Result image link