27

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) enter image description here

Same view/code shown in Visual Debugger (white backgroundColor) enter image description here

Device running iOS 13 (blue backgroundColor) enter image description here

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.

Phil
  • 1,216
  • 2
  • 14
  • 23

7 Answers7

29

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 
            }
        }
    }
}
Erick Silva
  • 499
  • 5
  • 9
  • 1
    Look at my answer below, I just found a way to do it that's a little bit cooler than this, even if your way it's working – Carioni Nov 28 '19 at 20:32
  • This did the trick! I had given up on this entirely, thanks for sharing your solution. Its a bit hacky but given the ridiculousness of the situation at hand it feels appropriate :) – Phil Dec 03 '19 at 20:38
  • 3
    With this code my segmented control loses text but the gray overlay still presented. – IvanPavliuk Feb 06 '20 at 12:51
  • 1
    Using this solution, the dividers between segments disappear in my segmented control. Do you have the same side effect? If not, can you share your configuration of the segmented control? – ste8 May 10 '20 at 00:00
  • this solution is not good for me, same times tittle disappear :/ – Ahmed Mh Jul 03 '20 at 13:24
  • This trick works also using a normal `DispatchQueue.main.async(execute:)` instead of a 0.1 second delay using `DispatchQueue.main.asyncAfter(deadline:execute:)`. – alobaili Aug 10 '20 at 12:04
  • Using this solution, the dividers between segments disappear in my segmented control. Do you have the same side effect? If not, can you share your configuration of the segmented control? – Mallikarjun C Jun 12 '22 at 12:36
4

I've found the simplest solution.

let segmentControl: UISegmentControl = ... 
segmentControl.subviews.forEach { subview in
  subview.backgroundColor = .white
}
IvanovDeveloper
  • 553
  • 5
  • 19
2

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
}
IvanPavliuk
  • 1,460
  • 1
  • 21
  • 16
  • 8
    This works seemingly for every backgroundColor except .white and .clear. When you set it to either of those two colors what is shown is an ugly gray color. – c0d3p03t Feb 25 '20 at 22:06
  • 1
    @c0d3p03t I think the gray blending is visible with any light color. I'm trying to use a light tan, but I also see it with UIColor.yellow. – arlomedia Mar 19 '21 at 22:37
  • This is the answer that worked for me. However, you don't need the `as Any` in the dictionary value. – NRitH Mar 29 '22 at 16:42
1

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!
    }
}
Carioni
  • 673
  • 2
  • 8
  • 19
  • 1
    Hi Carioni, I've actually tried this method before and unless I'm missing something you lose the animation on changing selection which is what I'm trying to maintain. Erick Silva's suggestion while a bit hacky does do the trick. – Phil Dec 03 '19 at 20:38
  • @Phil can you share a video example of this? I'm not sure what are you referring to, because I'm not losing any animation – Carioni Dec 04 '19 at 14:29
1

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
    }
  }
}
Amer Hukic
  • 1,494
  • 1
  • 19
  • 29
  • Using this solution, the dividers between segments disappear in my segmented control. Do you have the same side effect? If not, can you share your configuration of the segmented control? – Mallikarjun C Jun 12 '22 at 12:36
1

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
                }
            }
        }
    }
}

}

Modified Segmented Control

Argus
  • 2,241
  • 1
  • 22
  • 27
-1

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++;
    }
}
bart
  • 304
  • 1
  • 3
  • 13