Search code examples
swiftautolayoutuibezierpath

How can I add a UIBezierPath to a UIView that uses auto layout?


In my project I want to add a line to a UIView (this UIView uses auto layout). I'm using UIBezierPath and CAShapeLayer to draw the line.

This is my code:

let myView = UIView()
self.view.addSubView(myView)
myView.translatesAutoresizingMaskIntoConstraints = false
myView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
myView.heightAnchor.constraint(equalToConstant: 40).isActive = true
myView.widthAnchor.constraint(equalToConstant: 100).isActive = true
myView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true

let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: myView.frame.height/2))
path.addLine(to: CGPoint(x: myView.frame.width, y: myView.frame.height/2))

let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 11.0

myView.layer.addSublayer(shapeLayer)

But my problem is no line shows in viewController. When I use this code everything is OK and the line shows perfectly:

let myView = UIView(frame: CGRect(x: 0, y: 160, width: 100, height: 40))
self.view.addSubView(myView)

How can I solve this problem? I must use auto layout.


Solution

  • Your best bet is to use a custom UIView subclass and set your layer's path in layoutSubviews(). That way you get the proper frame when needed.

    Here's a simple example:

    class LineView: UIView {
    
        let shapeLayer: CAShapeLayer = CAShapeLayer()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
    
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
    
        func commonInit() -> Void {
    
            layer.addSublayer(shapeLayer)
            shapeLayer.strokeColor = UIColor.black.cgColor
            shapeLayer.lineWidth = 11.0
    
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            let path = UIBezierPath()
            path.move(to: CGPoint(x: 0, y: bounds.midY))
            path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))
    
            shapeLayer.path = path.cgPath
    
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let myView = LineView()
            myView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(myView)
            NSLayoutConstraint.activate([
                myView.topAnchor.constraint(equalTo: self.view.topAnchor),
                myView.heightAnchor.constraint(equalToConstant: 40),
                myView.widthAnchor.constraint(equalToConstant: 100),
                myView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
            ])
    
            // if you want to see the frame of myView
            //myView.backgroundColor = .yellow
    
        }
    
    }
    

    Result - with yellow background so we can see the frame, and with your constraints (you probably want to use safeArea...):

    enter image description here