Search code examples
swiftdrawingbezierquadratic-curve

Quadratic curve not rendered correctly on different devices


I'm trying to draw a bezier path with a quadratic curve in the middle. The curve works well on iPhone 8 and XS, but is not responsive (i.e. not rendered correctly) on other devices.

Below is the image of the curve in iPhone XS (correct)

enter image description here

and iPhone XR (incorrect)

enter image description here

I've tried to use the view's constraint to get the middle value of the line but somehow it still not working

Here is the code where i'm drawing the path:

//self.viewTabBorder is the grey line, which is a uiview with 1 pixel height
override func viewWillAppear(_ animated: Bool) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: self.viewTabBorder.center.x - self.btnHome.frame.width + 20, y: 0))
        path.addQuadCurve(to: CGPoint(x: self.viewTabBorder.center.x + self.btnHome.frame.size.width - 20, y: 0), controlPoint: CGPoint(x: self.viewTabBorder.center.x, y: self.btnHome.frame.height + 5))
        path.addLine(to: CGPoint(x: self.viewTabBorder.center.x + self.btnHome.frame.size.width - 20, y: 0))

        let line = CAShapeLayer()
        line.path = path.cgPath
        line.strokeColor = UIColor(red: 224, green: 224, blue: 224).cgColor
        line.fillColor = UIColor.white.cgColor
        self.view.layer.addSublayer(line)
        self.viewTabBorder.layer.addSublayer(line)
    }

Can anyone show me what i'm missing? Thank you very much in advance!


Solution

  • A couple of observations:

    1. You are defining paths for sublayers to views. Doing this in viewWillAppear is too early in the process. The layout engine may not be done applying constraints, placing the views in their final location. Do the defining of the paths in viewDidLayoutSubviews, not in viewWillAppear (nor viewDidLoad, nor viewWillLayoutSubviews).

      This also has the virtue that should you ever change your app to support multiple orientations or do any changes that affect the constraints in the view, this viewDidLayoutSubviews we be called again so these sublayers will be properly updated as needed. If you use viewWillAppear your app will not support dynamic changes to your views (should you ever need that).

    2. That having been said, be aware that viewDidLayoutSubviews can be called multiple times. So you can, for example, add the sublayers in viewDidLoad, but update their paths in viewDidLayoutSubviews.

    3. It is likely not an issue here, but I would suggest you avoid using frame or center when defining a path for a sublayer. Remember that these two properties are defined in the coordinate system of their superview. I’d suggest you always use a view’s bounds (defined in a view’s own coordinate system), not its frame (defined in its superview’s coordinate system) when trying to define a path for a sublayer. And if you want, for example, the horizontal center of a view when defining a sublayer’s path, use bounds.midX not center.x.

      Sometimes this won’t matter (if the view goes edge-to-edge within its superview), but (a) it suggests a misunderstanding of the different coordinate systems; and (b) if you ever add safe areas or otherwise adjust the placement of the view, using the frame/center will get you into trouble.


    A slightly more advanced concept: Once you have your immediate issue behind you, you might consider moving this configuration of subviews into a UIView subclass (using its layoutSubviews method), possibly even making it an @IBDesignable class so you can see this rendered in Interface Builder. Or, another approach is to wrap this in a container view controller. But either way, this can help prevent view controller “bloat”. It’s probably a bit beyond the scope of this question, but something to consider as you move forward.