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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/56436559/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 09:52:05  来源:igfitidea点击:

How to change the colors of a segment in a UISegmentedControl in iOS 13?

iosuikituisegmentedcontrolios13

提问by rmaddy

A UISegmentedControlhas a new appearance in iOS 13 and existing code to alter the colors of the segmented control no longer work as they did.

AUISegmentedControl在 iOS 13 中有一个新的外观,现有的代码来改变分段控件的颜色不再像以前那样工作。

Prior to iOS 13 you could set the tintColorand that would be used for the border around the segmented control, the lines between the segments, and the background color of the selected segment. Then you could change the color of the titles of each segment using the foreground color attribute with titleTextAttributes.

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

Under iOS 13, the tintColordoes nothing. You can set the segmented control's backgroundColorto change the overall color of the segmented control. But I can't find any way to alter the color used as the background of the selected segment. Setting the text attributes still works. I even tried setting the background color of the title but that only affects the background of the title, not the rest of the selected segment's background color.

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

In short, how do you modify the background color of the currently selected segment of a UISegmentedControlin iOS 13? Is there a proper solution, using public APIs, that doesn't require digging into the private subview structure?

总之,UISegmentedControl在iOS 13中如何修改a的当前选中段的背景色?是否有适当的解决方案,使用公共 API,不需要深入研究私有子视图结构?

There are no new properties in iOS 13 for UISegmentedControlor UIControland none of the changes in UIVieware relevant.

iOS 13 中没有针对UISegmentedControlor 的新属性,UIControl并且其中的任何更改UIView都不相关。

回答by rmaddy

As of iOS 13b3, there is now a selectedSegmentTintColoron UISegmentedControl.

从 iOS 13b3 开始,现在有一个selectedSegmentTintColoron UISegmentedControl.

To change the overall color of the segmented control use its backgroundColor.

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

To change the color of the selected segment use selectedSegmentTintColor.

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

To change the color/font of the unselected segment titles, use setTitleTextAttributeswith a state of .normal/UIControlStateNormal.

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

To change the color/font of the selected segment titles, use setTitleTextAttributeswith a state of .selected/UIControlStateSelected.

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

If you create a segmented control with images, if the images are created as template images, then the segmented control's tintColorwill be used to color the images. But this has a problem. If you set the tintColorto the same color as selectedSegmentTintColorthen the image won't be visible in the selected segment. If you set the tintColorto the same color as backgroundColor, then the images on the unselected segments won't be visible. This means your segmented control with images must use 3 different colors for everything to be visible. Or you can use non-template images and not set the tintColor.

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

Under iOS 12 or earlier, simply set the segmented control's tintColoror rely on the app's overall tint color.

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

回答by Jonathan.

As of Xcode 11 beta 3

从 Xcode 11 beta 3 开始

There is now the selectedSegmentTintColorproperty on UISegmentedControl.

现在selectedSegmentTintColor有财产UISegmentedControl

See rmaddy's answer

rmaddy 的回答



To get back iOS 12 appearance

找回iOS 12外观

I wasn't able to tint the color of the selected segment, hopefully it will be fixed in an upcoming beta.

我无法为所选片段的颜色着色,希望它会在即将到来的测试版中得到修复。

Setting the background image of the selected state doesn't work without setting the background image of the normal state (which removes all the iOS 13 styling)

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

But I was able to get it back to the iOS 12 appearance (or near enough, I wasn't able to return the corner radius to its smaller size).

但是我能够让它恢复到 iOS 12 的外观(或者足够接近,我无法将角半径恢复到较小的尺寸)。

It's not ideal, but a bright white segmented control looks a bit out of place in our app.

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

(Didn't realise UIImage(color:)was an extension method in our codebase. But the code to implement it is around the web)

(没有意识到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
        }
    }
}

Image showing the effect of the above code

上面代码效果图

回答by Maulik Patel

IOS 13 and Swift 5.0 (Xcode 11.0)Segment Control 100% Working

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

enter image description here

在此处输入图片说明

enter image description here

在此处输入图片说明

 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
}

回答by Colin Blake

I've tried the workaround and it works great for me. Here's the Objective-C version:

我已经尝试了解决方法,它对我很有用。这是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

回答by C?ur

As of Xcode 11 beta 3

从 Xcode 11 beta 3 开始

There is now the selectedSegmentTintColorproperty on UISegmentedControl.

现在selectedSegmentTintColor有财产UISegmentedControl

Thank you @rmaddy!

谢谢@rmaddy!



Original answer, for Xcode 11 beta and beta 2

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

Is there a proper solution, using public APIs, that doesn't require digging into the private subview structure?

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

With Xcode 11.0 beta, it seems to be a challenge to do it by-the-rules, because it basically requires to redraw all the background images for every states by yourself, with round corners, transparency and resizableImage(withCapInsets:). For instance, you would need to generate a colored image similar to:
enter image description here

使用 Xcode 11.0 测试版,按照规则来做似乎是一个挑战,因为它基本上需要自己重新绘制每个状态的所有背景图像,圆角、透明度和resizableImage(withCapInsets:). 例如,您需要生成类似于以下内容的彩色图像:
在此处输入图片说明

So for now, the let's-dig-into-the-subviews way seems much easier:

所以现在,让我们深入研究子视图的方式似乎要容易得多:

class TintedSegmentedControl: UISegmentedControl {

    override func layoutSubviews() {
        super.layoutSubviews()

        if #available(iOS 13.0, *) {
            for subview in subviews {
                if let selectedImageView = subview.subviews.last(where: { 
if #available(iOS 13.0, *) {
   segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
   segmentedControl.selectedSegmentTintColor = UIColor.blue
} else {
   segmentedControl.tintColor = UIColor.blue
}
is UIImageView }) as? UIImageView, let image = selectedImageView.image { selectedImageView.image = image.withRenderingMode(.alwaysTemplate) break } } } } }

This solution will correctly apply the tint color to the selection, as in: enter image description here

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

回答by Vignan S

Swift version of @Ilahi Charfeddine answer:

@Ilahi Charfeddine 的 Swift 版本回答:

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

回答by Ilahi Charfeddine

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

回答by t9mike

Here is my take on Jonathan.'s answer for Xamarin.iOS (C#), but with fixes for image sizing. As with C?ur's comment on Colin Blake's answer, I made all images except the divider the size of the segmented control. The divider is 1xheight of the segment.

这是我对 Jonathan. 对 Xamarin.iOS (C#) 的回答,但修复了图像大小。与 C?ur 对 Colin Blake 回答的评论一样,我将除分隔符以外的所有图像都设为分段控件的大小。分隔线是段的 1x 高度。

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()!)!
    }
}

回答by Jigar Darji

iOS13 UISegmentController

iOS13 UISegmentController

how to use:

如何使用:

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

回答by FredFlinstone

XCODE 11.1 & iOS 13

Xcode 11.1 和 iOS 13

Based on @Jigar Darji 's answer but a safer implementation.

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

We first create a failable convenience initialiser:

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

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

Then we extend UISegmentedControl:

然后我们扩展 UISegmentedControl:

##代码##