I am trying to create a custom UISegmentedControl
which has a bar at the bottom below the selected option.
I have added a CALayer
to act as the bar. After the selected option is changed, the bar does not move to its expected position
The code for the custom UISegmentedControl
is as follows-
class ProfileSegmentedControl: UISegmentedControl {
private lazy var bottomBar = getBottomBar()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
}
private extension ProfileSegmentedControl {
func setup() {
tintColor = .clear
let normalAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.white,
.font: UIFont.systemFont(ofSize: 16, weight: .medium)
]
setTitleTextAttributes(normalAttributes, for: .normal)
setTitleTextAttributes([.foregroundColor: UIColor(named: "black") as Any], for: .selected)
}
}
private extension ProfileSegmentedControl {
func getBottomBar() -> CALayer {
let bar = CALayer()
bar.backgroundColor = UIColor(named: "black")?.cgColor
layer.addSublayer(bar)
return bar
}
func setBarFrame() {
let barWidth = bounds.width / CGFloat(numberOfSegments)
let barHeight: CGFloat = 2
let x = barWidth * CGFloat(selectedSegmentIndex)
let y = bounds.height - barHeight
bottomBar.frame = CGRect(x: x, y: y, width: barWidth, height: barHeight)
}
}
extension ProfileSegmentedControl {
override func layoutIfNeeded() {
super.layoutIfNeeded()
setBarFrame()
}
}
It takes 2 taps on an option to move the bottom bar to the expected position.
Can anyone point out why this is happening? Can anyone point out how to fix this?
Edit- When the view is first loaded and "Segment 0" is tapped, the bar appears at the correct position as shown below-
When the "Segment 1" is tapped once, the bar does not move as shown below-
When the "Segment 1" is tapped again, the bar moves to the correct position as shown below-
I don't think this approach will work, because selectedSegmentIndex
is not yet set when layoutIfNeeded()
is called. That's why you're getting Seg-2 underlined when tapping Seg-1 and visa-versa.
I'd suggest replacing your override of layoutIfNeeded()
with:
extension ProfileSegmentedControl {
override func layoutSubviews() {
super.layoutSubviews()
setBarFrame()
}
}
and then adding a valueChanged
action:
@IBAction func segmentChanged(_ sender: Any) {
if let segControl = sender as? ProfileSegmentedControl {
segControl.setNeedsLayout()
}
}
which, presumably, you're implementing anyway to take action when a segment is tapped.
This has the added advantage of auto-sizing the bar when the control width changes - such as on device rotation.