Search code examples
iosswiftcashapelayer

How to do this shape with UIBezierPath


I'm trying to convert my UIView to this shape with UIBezierPath, currently I'm only able to do the left bottom corner, seeking for help for adding other corners.

enter image description here

Code for left bottom only.

        let mask = CAShapeLayer()
        mask.frame = self.innerLayout.layer.bounds

        let path = UIBezierPath()
        let radius: CGFloat = 50
        let rect = mask.bounds

        path.move(to: rect.origin)
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY))
        path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.maxY), radius: radius, startAngle: 0, endAngle: CGFloat(M_PI_2 * 3), clockwise: false)

        mask.path = path.cgPath
        self.innerLayout.layer.mask = mask

I did couple of trials for adding other corners but my UIView got funny shapes. I simply added this by copying and pasting (and changing origin), I believe we will use this part of the code 4 times to add 4 corners

    path.move(to: CGPoint(x: rect.maxX, y: rect.maxY)
    path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
    path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
    path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY))
    path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.maxY), radius: radius, startAngle: 0, endAngle: CGFloat(M_PI_2 * 3), clockwise: false)

Solution

  • Here's a complete example to be run in Playground. Such geometric shapes are always tricky. It's very easy to get coordinates wrong or have an arc go into the wrong direction.

    import UIKit
    import PlaygroundSupport
    
    class ConcaveConrnerViewController : UIViewController {
        override func loadView() {
            let view = UIView()
            view.backgroundColor = .white
    
            let frame = CGRect(x: 100, y: 200, width: 200, height: 200)
            let subView = UIView(frame: frame)
            subView.backgroundColor = .red
            
            let mask = CAShapeLayer()
            mask.frame = subView.layer.bounds
    
            let path = UIBezierPath()
            let radius: CGFloat = 50
            let rect = mask.bounds
    
            path.move(to: CGPoint(x: rect.minX + radius, y: rect.minY))
            path.addLine(to: CGPoint(x: rect.maxX - radius, y: rect.minY))
            path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.minY), radius: radius, startAngle: CGFloat(Double.pi / 2 * 2), endAngle: CGFloat(Double.pi / 2 * 3), clockwise: false)
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - radius))
            path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.maxY), radius: radius, startAngle: CGFloat(Double.pi / 2 * 1), endAngle: CGFloat(Double.pi / 2 * 2), clockwise: false)
            path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY))
            path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.maxY), radius: radius, startAngle: CGFloat(Double.pi / 2 * 0), endAngle: CGFloat(Double.pi / 2 * 1), clockwise: false)
            path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + radius))
            path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius, startAngle: CGFloat(Double.pi / 2 * 3), endAngle: CGFloat(Double.pi / 2 * 0), clockwise: false)
            path.close()
    
            mask.path = path.cgPath
            subView.layer.mask = mask
            
            view.addSubview(subView)
            self.view = view
        }
    }
    
    PlaygroundPage.current.liveView = ConcaveConrnerViewController()