Search code examples
iosswiftxcodeuibezierpathcashapelayer

How can an arrow that I make with UIBezierPath and CAShapeLayer point to exact center of a UILabel?


I drew a rectangle view with an arrow pointing to a label above the view using UIBezierPath and CAShapeLayer. I want the arrow to point to the exact center of the "P" label and iPhone11 simulator shows the result I want. However, when I run on iPhone8 simulator, the arrow doesn't point to the center of the label.

Why is the arrow pointing is little off to the right of the label on iPhone8 simulator?

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var label: UILabel! //horizontally and vertically aligned

@IBOutlet weak var pointingView: UIView! // leading & trailing constraints: 0, height:200

override func viewDidLoad() {
    super.viewDidLoad()
    drawPointingView()
}

private func drawPointingView() {
    let path = UIBezierPath()
    path.move(to: CGPoint(x: 0, y: 0))
    path.addLine(to: CGPoint(x: 0, y: pointingView.frame.height))
    path.addLine(to: CGPoint(x: pointingView.frame.width, y: pointingView.frame.height))
    path.addLine(to: CGPoint(x: pointingView.frame.width, y: 0))
    //draw an arrow pointing out to the center cordinate of the label. 
    path.addLine(to: CGPoint(x: label.center.x + 15, y: 0))
    path.addLine(to: CGPoint(x: label.center.x, y: -18))
    path.addLine(to: CGPoint(x: label.center.x - 15, y: 0))
    path.close()
    
    let shape = CAShapeLayer()
    shape.fillColor = UIColor.blue.cgColor
    shape.path = path.cgPath
    shape.strokeColor = UIColor.lightGray.cgColor
    shape.lineWidth = 1
    pointingView.layer.addSublayer(shape)
 }
}

iPhone11 Simulator
enter image description here

iPhone8 Simulator
enter image description here


Solution

  • It's because you're doing the drawing in viewDidLoad, and viewDidLoad is too soon. All those values you are relying on, stuff like pointingView.frame.width, are not known yet. You have to wait until after they are known. Otherwise you'll just be drawing in the wrong place (as in fact you are).

    Things like that are the result of layout, so you have to wait until after viewDidLayoutSubviews has been called for the first time.