We have a custom UIView that uses constraints in Interface Builder and we have modified this view to have a custom shape which we need to draw.
The shape size depends on the constraints which are applied to this custom view in Interface Builder, so in order to get the shape in the custom UIView at the right size, we need to call it in LayoutSubviews.
The issue is when LayoutSubviews is getting called during screen orientation, another shape is been added to the screen since it is called in the custom UIView's setup function.
This behavior can be solved by adding a boolean that determines if this is the first time LayoutSubviews is called and only if it is, then adds the shape layer to the view.
I have made some research here and on the web and found an article on how to achieve the described behavior with a different approach.
https://thomasclowes.com/ios-when-to-layout-your-views/
The author mentioned a custom class BaseView and a protocol which can be implemented to solve this behavior.
The solution is written under "UIView and layoutSubviews".
I am struggling to understand the solution and wanted to know if someone can clarify and show a code example on how to achieve the solution mentioned in the article.
Thanks in advance.
class CustomView: UIView {
private var firstTimeInit = true
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
private func setup() {
// Check if the the method was already executed
if firstTimeInit == false { return }
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 21))
label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
label.text = "Daily"
label.textAlignment = .center
label.sizeToFit()
addSubview(label)
label.center.x = bounds.midX
let shapePath = UIBezierPath()
shapePath.move(to: CGPoint(x: 0, y: 0))
shapePath.addLine(to: CGPoint(x: frame.width, y: 0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = shapePath.cgPath
shapeLayer.strokeColor = UIColor(hex: 0xD5E5EF).cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineCap = .round
shapeLayer.position.y = frame.height / 2
shapeLayer.lineDashPattern = [frame.width / 2.5, frame.width / 5, frame.width / 2.5] as [NSNumber]
layer.addSublayer(shapeLayer)
firstTimeInit = false
}
}
Why don't you create your UILabel
and CAShapeLayer
once, add it to subview in constructor and then only use layoutSubviews
to recalculate their frame or pattern?
Like this:
class CustomView: UIView {
private let label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 21))
private let shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
label.text = "Daily"
label.textAlignment = .center
addSubview(label)
shapeLayer.strokeColor = UIColor(hex: 0xD5E5EF).cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineCap = .round
layer.addSublayer(shapeLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
label.sizeToFit()
label.center.x = bounds.midX
let shapePath = UIBezierPath()
shapePath.move(to: CGPoint(x: 0, y: 0))
shapePath.addLine(to: CGPoint(x: frame.width, y: 0))
shapeLayer.path = shapePath.cgPath
shapeLayer.position.y = frame.height / 2
shapeLayer.lineDashPattern = [frame.width / 2.5, frame.width / 5, frame.width / 2.5] as [NSNumber]
}
}
This way your custom shape would adapt to frame
change, without duplicating itself.
Obviously, you may need to actually implement required init?(coder aDecoder: NSCoder)
if you use it.