如何在iOS 13中的UISegmentedControl中更改细分的颜色?


108

A UISegmentedControl在iOS 13中具有新外观,并且现有代码更改分段控件的颜色不再像以前那样起作用。

在iOS 13之前,您可以设置tintColor和,以用于分段控件周围的边框,分段之间的线条以及所选分段的背景颜色。然后,您可以使用带有的前景色属性更改每个句段标题的颜色titleTextAttributes

在iOS 13下,tintColor什么都不做。您可以设置分段控件backgroundColor以更改分段控件的整体颜色。但是我找不到任何方法来更改用作所选片段背景的颜色。设置文本属性仍然有效。我什至尝试设置标题的背景色,但这只会影响标题的背景,而不会影响所选片段的其余背景色。

简而言之,您如何修改UISegmentedControliOS 13中当前选定的段的背景颜色?是否有使用公共API的适当解决方案,而无需深入研究私有子视图结构?

在iOS 13中,没有针对UISegmentedControl或的新属性,UIControl并且这些更改UIView均不相关。

Answers:


133

由于iOS的13B3的,现在有一个selectedSegmentTintColorUISegmentedControl

要更改分段控件的整体颜色,请使用backgroundColor

要更改所选段的颜色,请使用selectedSegmentTintColor

要更改未选择的段标题的颜色/字体,请使用/ setTitleTextAttributes状态。.normalUIControlStateNormal

要更改所选段标题的颜色/字体,请使用/ setTitleTextAttributes状态。.selectedUIControlStateSelected

如果使用图像创建分段控件,则将图像创建为模板图像时,tintColor将使用分段控件为图像着色。但这有一个问题。如果您将设置tintColor为与相同的颜色,selectedSegmentTintColor则图像在所选细分中将不可见。如果将设置tintColor为与相同的颜色backgroundColor,则未选择的线段上的图像将不可见。这意味着您的带有图像的分段控件必须使用3种不同的颜色才能使所有内容可见。或者,您可以使用非模板图像而不设置tintColor

在iOS 12或更早版本下,只需设置分段控件tintColor或依靠应用程序的整体色彩即可。


我们如何设置无边界的段控制器?我在iOS 13中看不到此设置。之前,设置tintcolor足以获取无边界的段控件。
Deepak Sharma

请添加边框颜色等,以便所有人都可以找到所有与段颜色相关的问题,此处已解决。怎么说?:)
Yogesh Patel

1
@YogeshPatel边框颜色如何?在iOS 13中没有边框颜色,而在iOS 12 tintColor中已经设置了边框颜色,答案中已经涵盖了边框颜色。
rmaddy19

@rmaddy我已经设置了此[segmentedControl.layer setBorderColor:[[UIColor whiteColor] CGColor]]; [segmentedControl.layer setBorderWidth:0.5]; 它给了我边框和边框颜色在iOS的13
约杰什帕特尔

1
哦,那个边界。这适用于任何视图,而不仅仅是分段控件。这超出了原始问题和答案的范围。您的评论就足够了。
rmaddy19年

47

从Xcode 11 beta 3开始

现在有selectedSegmentTintColor物业UISegmentedControl

查看rmaddy的答案


重新获得iOS 12外观

我无法为所选部分着色,希望它会在即将发布的Beta版中得到修复。

如果不设置正常状态的背景图像,则设置选定状态的背景图像将不起作用(这将删除所有iOS 13样式)

但是我能够将其恢复到iOS 12的外观(或者说距离足够近,我无法将拐角半径恢复为较小的尺寸)。

这并不理想,但是明亮的白色分段控件在我们的应用中看起来有点不合适。

(没有意识到这UIImage(color:)是我们代码库中的扩展方法。但是,实现它的代码是在网络上发布的)

extension UISegmentedControl {
    /// Tint color doesn't have any effect on iOS 13.
    func ensureiOS12Style() {
        if #available(iOS 13, *) {
            let tintColorImage = UIImage(color: tintColor)
            // Must set the background image for normal to something (even clear) else the rest won't work
            setBackgroundImage(UIImage(color: backgroundColor ?? .clear), for: .normal, barMetrics: .default)
            setBackgroundImage(tintColorImage, for: .selected, barMetrics: .default)
            setBackgroundImage(UIImage(color: tintColor.withAlphaComponent(0.2)), for: .highlighted, barMetrics: .default)
            setBackgroundImage(tintColorImage, for: [.highlighted, .selected], barMetrics: .default)
            setTitleTextAttributes([.foregroundColor: tintColor, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .regular)], for: .normal)
            setDividerImage(tintColorImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
            layer.borderWidth = 1
            layer.borderColor = tintColor.cgColor
        }
    }
}

该图显示了以上代码的效果


这可能是一个很好的解决方法。我还没有机会尝试这种方法,但这是否还需要调用setTitleTextAttributes以使所选段的标题具有白色?
rmaddy19年

嗯,看起来应该,但似乎不是。我无法访问该代码用法atm,但是左侧的图像是使用该代码创建的。
乔纳森。

8
用于UIImage(color :)扩展的stackoverflow.com/a/33675160/5790492
Nik Kov

1
如果您要添加UIImage扩展,那就太好了,这样,您的答案就不完整了
FredFlinstone

1
@VityaShurapov将其设置为突出显示选择时,它不是传入的状态数组,而是一个选项集,这意味着将这些值组合在一起以创建一个新状态。
乔纳森。

36

IOS 13和Swift 5.0(Xcode 11.0)段控制100%工作

在此处输入图片说明

在此处输入图片说明

 if #available(iOS 13.0, *) {
      yoursegmentedControl.backgroundColor = UIColor.black
      yoursegmentedControl.layer.borderColor = UIColor.white.cgColor
      yoursegmentedControl.selectedSegmentTintColor = UIColor.white
      yoursegmentedControl.layer.borderWidth = 1

      let titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]    
      yoursegmentedControl.setTitleTextAttributes(titleTextAttributes, for:.normal)

      let titleTextAttributes1 = [NSAttributedString.Key.foregroundColor: UIColor.black]
      yoursegmentedControl.setTitleTextAttributes(titleTextAttributes1, for:.selected)
  } else {
              // Fallback on earlier versions
}

7
您是否尝试过将背景设置为白色?对我来说,它会变成灰色
Ronit

@ItanHant您使用哪种xcode和快速语言?
Maulik Patel

@Ronit当然!!我会尝试,但是请告诉我现在什么类型的输出显示
Maulik Patel

迅捷5,xcode 11.3!它显示了它想要的!不是我想要的那一个:)
Itan Hant

15

我已经尝试了解决方法,对我来说效果很好。这是Objective-C版本:

@interface UISegmentedControl (Common)
- (void)ensureiOS12Style;
@end
@implementation UISegmentedControl (Common)
- (void)ensureiOS12Style {
    // UISegmentedControl has changed in iOS 13 and setting the tint
    // color now has no effect.
    if (@available(iOS 13, *)) {
        UIColor *tintColor = [self tintColor];
        UIImage *tintColorImage = [self imageWithColor:tintColor];
        // Must set the background image for normal to something (even clear) else the rest won't work
        [self setBackgroundImage:[self imageWithColor:self.backgroundColor ? self.backgroundColor : [UIColor clearColor]] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [self setBackgroundImage:tintColorImage forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
        [self setBackgroundImage:[self imageWithColor:[tintColor colorWithAlphaComponent:0.2]] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
        [self setBackgroundImage:tintColorImage forState:UIControlStateSelected|UIControlStateSelected barMetrics:UIBarMetricsDefault];
        [self setTitleTextAttributes:@{NSForegroundColorAttributeName: tintColor, NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateNormal];
        [self setDividerImage:tintColorImage forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        self.layer.borderWidth = 1;
        self.layer.borderColor = [tintColor CGColor];
    }
}

- (UIImage *)imageWithColor: (UIColor *)color {
    CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}
@end

2
我不确定它是否可以使用CGRectMake(0.0f, 0.0f, 1.0f, 1.0f):从我对Xcode 11 beta的测试rect中得出的结果,其大小必须与分段控件的边界相同。
心教堂

2
从iOS13 beta 6开始,tintcolor没有显示在所选按钮上,因此我不得不添加一行:[self setTitleTextAttributes:@ {NSForegroundColorAttributeName:UIColor.blackColor,NSFontAttributeName:[UIFont systemFontOfSize:13]} for State:UIControlStateSelected];
彼得·约翰逊

当我尝试使用它时,[[UISegmentedControl appearance] ensureiOS12Style]出现异常。知道发生了什么吗? Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSMethodSignature getArgumentTypeAtIndex:]: index (2) out of bounds [0, 1]'
杰里米·希克斯

13

从Xcode 11 beta 3开始

现在有selectedSegmentTintColor物业UISegmentedControl

谢谢@rmaddy!


原始答案,适用于Xcode 11 beta和beta 2

是否有使用公共API的适当解决方案,而无需深入研究私有子视图结构?

对于Xcode 11.0 beta,按规则进行操作似乎是一个挑战,因为它基本上要求您自己绘制每个州的所有背景图像,并带有圆角,透明度和resizableImage(withCapInsets:)。例如,您将需要生成类似于以下内容的彩色图像:
在此处输入图片说明

所以就目前而言,让我们深入子视图的方法似乎要容易得多:

class TintedSegmentedControl: UISegmentedControl {

    override func layoutSubviews() {
        super.layoutSubviews()

        if #available(iOS 13.0, *) {
            for subview in subviews {
                if let selectedImageView = subview.subviews.last(where: { $0 is UIImageView }) as? UIImageView,
                    let image = selectedImageView.image {
                    selectedImageView.image = image.withRenderingMode(.alwaysTemplate)
                    break
                }
            }
        }
    }
}

此解决方案将正确地将色调颜色应用于所选内容,如下所示: 在此处输入图片说明


1
获奖是因为时间不多了,但是最好找到一个不涉及挖掘私有层次的解决方案:)
乔纳森(Jonathan)。

@乔纳森 谢谢。您已经获得了不涉及层次结构的最接近的解决方案:因为一旦setBackgroundImage找到.normal,您就必须设置所有其他图像(包括其他状态和setDividerImage),可能要使用一些UIBezierPathresizableImage(withCapInsets:),如果我们想要iOS 13设计,这将使其过于复杂这条路。
心教堂

是啊,最好将固定在β
乔纳森。

3
从iOS 13b3开始不再需要此功能。现在有selectedSegmentTintColor物业UISegmentedControl
rmaddy19'7

11

@Ilahi Charfeddine的Swift版本答案:

if #available(iOS 13.0, *) {
   segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
   segmentedControl.selectedSegmentTintColor = UIColor.blue
} else {
   segmentedControl.tintColor = UIColor.blue
}

10
if (@available(iOS 13.0, *)) {

    [self.segmentedControl setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor whiteColor], NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateSelected];
    [self.segmentedControl setSelectedSegmentTintColor:[UIColor blueColor]];

} else {

[self.segmentedControl setTintColor:[UIColor blueColor]];}

7

iOS13 UISegmentController

如何使用:

segment.setOldLayout(tintColor: .green)

extension UISegmentedControl
{
    func setOldLayout(tintColor: UIColor)
    {
        if #available(iOS 13, *)
        {
            let bg = UIImage(color: .clear, size: CGSize(width: 1, height: 32))
             let devider = UIImage(color: tintColor, size: CGSize(width: 1, height: 32))

             //set background images
             self.setBackgroundImage(bg, for: .normal, barMetrics: .default)
             self.setBackgroundImage(devider, for: .selected, barMetrics: .default)

             //set divider color
             self.setDividerImage(devider, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)

             //set border
             self.layer.borderWidth = 1
             self.layer.borderColor = tintColor.cgColor

             //set label color
             self.setTitleTextAttributes([.foregroundColor: tintColor], for: .normal)
             self.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
        }
        else
        {
            self.tintColor = tintColor
        }
    }
}
extension UIImage {
    convenience init(color: UIColor, size: CGSize) {
        UIGraphicsBeginImageContextWithOptions(size, false, 1)
        color.set()
        let ctx = UIGraphicsGetCurrentContext()!
        ctx.fill(CGRect(origin: .zero, size: size))
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        self.init(data: image.pngData()!)!
    }
}

1
这是唯一对我有用的方法-背景图像。selectedSegmentTintColor由于某些原因无法正常工作。
DenNukem

7

XCODE 11.1和iOS 13

基于@Jigar Darji的答案,但实现更安全。

我们首先创建一个失败的便利初始化程序:

extension UIImage {

convenience init?(color: UIColor, size: CGSize) {
    UIGraphicsBeginImageContextWithOptions(size, false, 1)
    color.set()
    guard let ctx = UIGraphicsGetCurrentContext() else { return nil }
    ctx.fill(CGRect(origin: .zero, size: size))
    guard
        let image = UIGraphicsGetImageFromCurrentImageContext(),
        let imagePNGData = image.pngData()
        else { return nil }
    UIGraphicsEndImageContext()

    self.init(data: imagePNGData)
   }
}

然后,我们扩展UISegmentedControl:

extension UISegmentedControl {

func fallBackToPreIOS13Layout(using tintColor: UIColor) {
    if #available(iOS 13, *) {
        let backGroundImage = UIImage(color: .clear, size: CGSize(width: 1, height: 32))
        let dividerImage = UIImage(color: tintColor, size: CGSize(width: 1, height: 32))

        setBackgroundImage(backGroundImage, for: .normal, barMetrics: .default)
        setBackgroundImage(dividerImage, for: .selected, barMetrics: .default)

        setDividerImage(dividerImage,
                        forLeftSegmentState: .normal,
                        rightSegmentState: .normal, barMetrics: .default)

        layer.borderWidth = 1
        layer.borderColor = tintColor.cgColor

        setTitleTextAttributes([.foregroundColor: tintColor], for: .normal)
        setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
    } else {
        self.tintColor = tintColor
    }
  }
}

完善!谢谢!
Senocico Stelian

5

这是我对乔纳森(Jonathan。)对Xamarin.iOS(C#)的回答,但有图像大小调整的修复程序。就像Curur对Colin Blake的回答所做的评论一样,我使除分隔线以外的所有图像都达到了分段控件的大小。分频器为分段的1xheight。

public static UIImage ImageWithColor(UIColor color, CGSize size)
{
    var rect = new CGRect(0, 0, size.Width, size.Height);
    UIGraphics.BeginImageContext(rect.Size);
    var context = UIGraphics.GetCurrentContext();
    context.SetFillColor(color.CGColor);
    context.FillRect(rect);
    var image = UIGraphics.GetImageFromCurrentImageContext();
    UIGraphics.EndImageContext();
    return image;
}

// https://stackoverflow.com/a/56465501/420175
public static void ColorSegmentiOS13(UISegmentedControl uis, UIColor tintColor, UIColor textSelectedColor, UIColor textDeselectedColor)
{
    if (!UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
    {
        return;
    }

    UIImage image(UIColor color)
    {
        return ImageWithColor(color, uis.Frame.Size);
    }

    UIImage imageDivider(UIColor color)
    {
        return ImageWithColor(color, 1, uis.Frame.Height);
    }

    // Must set the background image for normal to something (even clear) else the rest won't work
    //setBackgroundImage(UIImage(color: backgroundColor ?? .clear), for: .normal, barMetrics: .default)
    uis.SetBackgroundImage(image(UIColor.Clear), UIControlState.Normal, UIBarMetrics.Default);

    // setBackgroundImage(tintColorImage, for: .selected, barMetrics: .default)
    uis.SetBackgroundImage(image(tintColor), UIControlState.Selected, UIBarMetrics.Default);

    // setBackgroundImage(UIImage(color: tintColor.withAlphaComponent(0.2)), for: .highlighted, barMetrics: .default)
    uis.SetBackgroundImage(image(tintColor.ColorWithAlpha(0.2f)), UIControlState.Highlighted, UIBarMetrics.Default);

    // setBackgroundImage(tintColorImage, for: [.highlighted, .selected], barMetrics: .default)
    uis.SetBackgroundImage(image(tintColor), UIControlState.Highlighted | UIControlState.Selected, UIBarMetrics.Default);

    // setTitleTextAttributes([.foregroundColor: tintColor, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .regular)], for: .normal)
    // Change: support distinct color for selected/de-selected; keep original font
    uis.SetTitleTextAttributes(new UITextAttributes() { TextColor = textDeselectedColor }, UIControlState.Normal); //Font = UIFont.SystemFontOfSize(13, UIFontWeight.Regular)
    uis.SetTitleTextAttributes(new UITextAttributes() { TextColor = textSelectedColor, }, UIControlState.Selected); //Font = UIFont.SystemFontOfSize(13, UIFontWeight.Regular)

    // setDividerImage(tintColorImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
    uis.SetDividerImage(imageDivider(tintColor), UIControlState.Normal, UIControlState.Normal, UIBarMetrics.Default);

    //layer.borderWidth = 1
    uis.Layer.BorderWidth = 1;

    //layer.borderColor = tintColor.cgColor
    uis.Layer.BorderColor = tintColor.CGColor;
}

3

您可以实现以下方法

extension UISegmentedControl{
    func selectedSegmentTintColor(_ color: UIColor) {
        self.setTitleTextAttributes([.foregroundColor: color], for: .selected)
    }
    func unselectedSegmentTintColor(_ color: UIColor) {
        self.setTitleTextAttributes([.foregroundColor: color], for: .normal)
    }
}

使用代码

segmentControl.unselectedSegmentTintColor(.white)
segmentControl.selectedSegmentTintColor(.black)

0

虽然上述答案很不错,但大多数答案都使选定句段中的文本颜色错误。我创建了UISegmentedControl可以在iOS 13和iOS 13之前的设备上使用的子类,并像在iOS 13之前的设备上一样使用tintColor属性。

    class LegacySegmentedControl: UISegmentedControl {
        private func stylize() {
            if #available(iOS 13.0, *) {
                selectedSegmentTintColor = tintColor
                let tintColorImage = UIImage(color: tintColor)
                setBackgroundImage(UIImage(color: backgroundColor ?? .clear), for: .normal, barMetrics: .default)
                setBackgroundImage(tintColorImage, for: .selected, barMetrics: .default)
                setBackgroundImage(UIImage(color: tintColor.withAlphaComponent(0.2)), for: .highlighted, barMetrics: .default)
                setBackgroundImage(tintColorImage, for: [.highlighted, .selected], barMetrics: .default)
                setTitleTextAttributes([.foregroundColor: tintColor!, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .regular)], for: .normal)

                setDividerImage(tintColorImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
                layer.borderWidth = 1
                layer.borderColor = tintColor.cgColor

// Detect underlying backgroundColor so the text color will be properly matched

                if let background = backgroundColor {
                    self.setTitleTextAttributes([.foregroundColor: background, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .regular)], for: .selected)
                } else {
                    func detectBackgroundColor(of view: UIView?) -> UIColor? {
                        guard let view = view else {
                            return nil
                        }
                        if let color = view.backgroundColor, color != .clear {
                            return color
                        }
                        return detectBackgroundColor(of: view.superview)
                    }
                    let textColor = detectBackgroundColor(of: self) ?? .black

                    self.setTitleTextAttributes([.foregroundColor: textColor, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .regular)], for: .selected)
                }
            }
        }

        override func tintColorDidChange() {
            super.tintColorDidChange()
            stylize()
        }
    }

    fileprivate extension UIImage {
        public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
          let rect = CGRect(origin: .zero, size: size)
          UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
          color.setFill()
          UIRectFill(rect)
          let image = UIGraphicsGetImageFromCurrentImageContext()
          UIGraphicsEndImageContext()

          guard let cgImage = image?.cgImage else { return nil }
          self.init(cgImage: cgImage)
        }
    }

使用tintColorDidChangemethod时,我们确保stylize每次tintColor在细分视图或任何基础视图上更改属性时都会调用该方法,这在iOS上是首选行为。

结果: 在此处输入图片说明


-2

如果您不想要圆角,请在Jonathan答案上稍微扩展一下

extension UISegmentedControl {
    /// Tint color doesn't have any effect on iOS 13.
    func ensureiOS12Style(roundCorner: Bool = true) {
        if #available(iOS 13, *) {
            let tintColorImage = UIImage(color: tintColor)
            // Must set the background image for normal to something (even clear) else the rest won't work
            setBackgroundImage(UIImage(color: backgroundColor ?? .clear), for: .normal, barMetrics: .default)
            setBackgroundImage(tintColorImage, for: .selected, barMetrics: .default)
            setBackgroundImage(UIImage(color: tintColor.withAlphaComponent(0.2)), for: .highlighted, barMetrics: .default)
            setBackgroundImage(tintColorImage, for: [.highlighted, .selected], barMetrics: .default)
            setTitleTextAttributes([.foregroundColor: tintColor, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .regular)], for: .normal)
            setDividerImage(tintColorImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)

            if !roundCorner {
                layer.masksToBounds = false

                let borderView = UIView()
                borderView.layer.borderWidth = 1
                borderView.layer.borderColor = UIColor.black.cgColor
                borderView.isUserInteractionEnabled = false
                borderView.translatesAutoresizingMaskIntoConstraints = false

                addSubview(borderView)

                NSLayoutConstraint(item: borderView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
                NSLayoutConstraint(item: borderView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
                NSLayoutConstraint(item: borderView, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0).isActive = true
                NSLayoutConstraint(item: borderView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0).isActive = true
            }
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.