Search code examples
iosswiftuiviewcashapelayer

Border only in corners with UIBezierPath() wrong space in lineDashPattern


Im trying to create a view with border only in corners (qrCode style) using lineDashPattern, but the space between the lines doesn't seem to match with the Pattern defined by me. This is my playground:

override func loadView() {
    let view = UIView()
    view.backgroundColor = .white
    let myView = UIView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
    myView.backgroundColor = .black
    let borderLayer = CAShapeLayer()
    let d = (myView.bounds.width / 2) as NSNumber
    borderLayer.strokeColor = UIColor.blue.cgColor
    borderLayer.lineDashPattern = [d]
    borderLayer.frame = myView.bounds
    borderLayer.fillColor = nil
    borderLayer.lineWidth = 10
    borderLayer.strokeStart = 0.1
    //borderLayer.lineDashPhase = -80
    borderLayer.path = UIBezierPath(roundedRect: myView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 30, height: 30)).cgPath
    borderLayer.lineCap = CAShapeLayerLineCap.round
    myView.layer.addSublayer(borderLayer)
    view.addSubview(myView)
    self.view = view
}

enter image description here

I'm trying to adjust the start of the lines with the lineDashPhase and strokeStart attributes, but doesn't seems to work. Can someone help me?


Solution

  • Interesting idea - using long dashes on a rounded rect... But I don't believe you'll get your desired results.

    You probably want to draw the 4 corners like this:

    enter image description here

    Your path would start with:

    move to: A
    line to: B
    arc with center: C.x B.y and clockwise from 9 o'clock to 12 o'clock 
    line to: D
    

    then update your points, and draw the top-right / bottom-right / bottom-left corners.

    Here's an example class:

    class BracketView: UIView {
        var radius: CGFloat = 30
        var lineLength: CGFloat = 30
        var lineWidth: CGFloat = 10
        var lineColor: UIColor = .blue
        
        var shapeLayer: CAShapeLayer!
        
        override class var layerClass: AnyClass {
            return CAShapeLayer.self
        }
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() -> Void {
            shapeLayer = self.layer as? CAShapeLayer
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.lineCap = .round
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            
            // set these properties here, so we can change them if desired
            shapeLayer.strokeColor = lineColor.cgColor
            shapeLayer.lineWidth = lineWidth
            
            let pth = UIBezierPath()
            
            var ptA: CGPoint = .zero
            var ptB: CGPoint = .zero
            var ptC: CGPoint = .zero
            var ptD: CGPoint = .zero
            var arcCenter: CGPoint = .zero
            var startAngle: CGFloat = 0
            
            // top-left corner
            
            startAngle = .pi    // 9 o'clock
            
            ptA.x = bounds.minX
            ptA.y = bounds.minY + radius + lineLength
            
            ptB.x = bounds.minX
            ptB.y = bounds.minY + radius
            
            ptC.x = bounds.minX + radius
            ptC.y = bounds.minY
            
            ptD.x = bounds.minX + radius + lineLength
            ptD.y = bounds.minY
    
            arcCenter.x = ptC.x
            arcCenter.y = ptB.y
            
            pth.move(to: ptA)
            pth.addLine(to: ptB)
            pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
            pth.addLine(to: ptD)
            
            // top-right corner
            
            startAngle += (.pi * 0.5)   // 12 o'clock
            
            ptA.x = bounds.maxX - (radius + lineLength)
            ptA.y = bounds.minY
            
            ptB.x = bounds.maxX - radius
            ptB.y = bounds.minY
            
            ptC.x = bounds.maxX
            ptC.y = bounds.minY + radius
            
            ptD.x = bounds.maxX
            ptD.y = bounds.minY + radius + lineLength
            
            arcCenter.x = ptB.x
            arcCenter.y = ptC.y
            
            pth.move(to: ptA)
            pth.addLine(to: ptB)
            pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
            pth.addLine(to: ptD)
            
            // bottom-right corner
            
            startAngle += (.pi * 0.5)   // 3 o'clock
            
            ptA.x = bounds.maxX
            ptA.y = bounds.maxY - (radius + lineLength)
            
            ptB.x = bounds.maxX
            ptB.y = bounds.maxY - radius
            
            ptC.x = bounds.maxX - radius
            ptC.y = bounds.maxY
            
            ptD.x = bounds.maxX - (radius + lineLength)
            ptD.y = bounds.maxY
            
            arcCenter.x = ptC.x
            arcCenter.y = ptB.y
            pth.move(to: ptA)
            pth.addLine(to: ptB)
            pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
            
            pth.addLine(to: ptD)
            
            // bottom-left corner
            
            startAngle += (.pi * 0.5)   // 6 o'clock
            
            ptA.x = bounds.minX + radius + lineLength
            ptA.y = bounds.maxY
            
            ptB.x = bounds.minX + radius
            ptB.y = bounds.maxY
            
            ptC.x = bounds.minX
            ptC.y = bounds.maxY - radius
            
            ptD.x = bounds.minX
            ptD.y = bounds.maxY - (radius + lineLength)
            
            arcCenter.x = ptB.x
            arcCenter.y = ptC.y
            
            pth.move(to: ptA)
            pth.addLine(to: ptB)
            pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
            pth.addLine(to: ptD)
            
            shapeLayer.path = pth.cgPath
        }
        
    }
    

    Result with a 200 x 200 frame, green background:

    enter image description here