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)
and iPhone XR (incorrect)
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!
A couple of observations:
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).
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
.
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.