Reputation: 1246
iOS 13 introduced some changes to the UISegmentedControl including a really nice animation when switching the selected segment. However I'm noticing that it's not displaying the backgroundColor
property correctly, it always seems to have a bit of a tint to it.
I've seen questions that answer how to set the selectedSegmentTintColor
and such but I'm struggling to set the backgroundColor
to say .white
, no matter what I do it always shows up a bit of a gray even though there's no tintColor
or similar setting applied. Setting the backgroundColor
to other colors shows the same behavior but its most obvious with white. Adding to the mystery is that while this difference shows up on both iOS 13 simulators and a physical device running iOS 13, the visual debugger (in XCode 11 GM2) does not show this difference!
Here's a couple screenshots showing that even though the backgroundColor
of the UISegmentedControl
is set to the same as the backgroundColor
of the view shown behind it they are slightly different.
Device running iOS 13 (white backgroundColor)
Same view/code shown in Visual Debugger (white backgroundColor)
Device running iOS 13 (blue backgroundColor)
I've tried the suggestion of applying a backgroundImage
as suggested in this SO post: UISegmentedControl iOS 13 clear color but that ends up reverting the style back to how it looked in iOS 12 and you lose the nice animation as well.
Any guidance or suggestions is greatly appreciated! I've also filed a bug report with Apple, will see if anything comes of that.
Upvotes: 29
Views: 12384
Reputation: 55705
UISegmentedControl
is composed of several subviews. The first subviews are UIImageView
s that display resizable images to create the UI of each segment. These are the cause of the unexpected gray color. You can hide these image views if you want your background color to be fully visible, just note this will hide the separators you see between segments.
Note that the internal implementation could change in a future iOS version. Other solutions here would crash if the number of subviews changes to be fewer than the number of segments. Here is a safer solution that would not crash and could handle some possible changes to the subviews. While it is still not guaranteed to work in the future, at worst unintended UI elements would get hidden.
class CustomSegmentedControl: UISegmentedControl {
override init(frame: CGRect) {
super.init(frame: frame)
baseInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
baseInit()
}
override func layoutSubviews() {
super.layoutSubviews()
// The system displays an image view for each segment that is a gray color - hide it to reveal the intended background color
let imageViews = subviews.compactMap { $0 as? UIImageView }.prefix(numberOfSegments)
imageViews.forEach { $0.isHidden = true }
}
private func baseInit() {
backgroundColor = .white
selectedSegmentTintColor = .systemFill
}
}
Upvotes: 6
Reputation: 11
I completed the code of the previous answerer and everything worked for me
extension UISegmentedControl {
func applyWhiteBackgroundColor() {
// for remove bottom shadow of selected element
self.selectedSegmentTintColor = selectedSegmentTintColor?.withAlphaComponent(0.99)
if #available(iOS 13.0, *) {
//just to be sure it is full loaded
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let self = self else {
return
}
for i in 0 ..< (self.numberOfSegments) {
let backgroundSegmentView = self.subviews[i]
//it is not enogh changing the background color. It has some kind of shadow layer
backgroundSegmentView.isHidden = true
}
}
}
}
}
}
Upvotes: 1
Reputation: 1524
The accepted answer can be simplified and we can avoid using DispatchQueue.main.async
call by subclassing UISegmentedControl
and overriding the layoutSubviews
method:
class SegmentedControl: UISegmentedControl {
override func layoutSubviews() {
super.layoutSubviews()
for i in 0...(numberOfSegments - 1) {
subviews[i].isHidden = true
}
}
}
Upvotes: 2
Reputation: 602
I've found the simplest solution.
let segmentControl: UISegmentControl = ...
segmentControl.subviews.forEach { subview in
subview.backgroundColor = .white
}
Upvotes: 4
Reputation: 324
In Xamarin.iOS this worked for me:
class MySegmentedControl : UISegmentedControl
{
int insertedIndex = 0;
public override void InsertSubview(UIView view, nint atIndex)
{
base.InsertSubview(view, atIndex);
if (insertedIndex == 2 || insertedIndex == 3)
view.Hidden = true;
insertedIndex++;
}
}
Upvotes: -1
Reputation: 1790
Works for me (Swift 5).
let background = myColors.background
let selectedColor = myColors.foreground
if #available(iOS 13.0, *)
{
segmentedControl.tintColor = background
segmentedControl.backgroundColor = background
segmentedControl.selectedSegmentTintColor = selectedColor
segmentedControl.setTitleTextAttributes([.foregroundColor: selectedColor as Any], for: .normal)
segmentedControl.setTitleTextAttributes([.foregroundColor: background as Any], for: .selected)
}
else
{
segmentedControl.tintColor = background
segmentedControl.backgroundColor = selectedColor
segmentedControl.layer.cornerRadius = 4
}
Upvotes: 2
Reputation: 711
SWIFT 3 & 4+
From this answer https://stackoverflow.com/a/31652184/3249196 , if you want an all white background without the grey overlay justr replace tintColor
and backgroundColor
with UIColor.white
extension UISegmentedControl {
func removeBorders() {
setBackgroundImage(imageWithColor(color: backgroundColor!), for: .normal, barMetrics: .default)
setBackgroundImage(imageWithColor(color: tintColor!), for: .selected, barMetrics: .default)
setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
}
// create a 1x1 image with this color
private func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor);
context!.fill(rect);
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image!
}
}
Upvotes: 2
Reputation: 509
I have the same issue and there is no cool way to resolve it. So I did this small workaround. I dont like it and I am not proud of it, but it works.
func fixBackgroundSegmentControl( _ segmentControl: UISegmentedControl){
if #available(iOS 13.0, *) {
//just to be sure it is full loaded
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
for i in 0...(segmentControl.numberOfSegments-1) {
let backgroundSegmentView = segmentControl.subviews[i]
//it is not enogh changing the background color. It has some kind of shadow layer
backgroundSegmentView.isHidden = true
}
}
}
}
Upvotes: 30