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

149

iOS 13中的UISegmentedControl外观有所改变,之前可以通过设置tintColor来更改边框、分段线和选定的段落的背景颜色,然后再使用titleTextAttributes中的前景色属性更改每个段落标题的颜色。

但在iOS 13中,tintColor不起作用了。现在你需要设置分段控件的backgroundColor来修改整个分段控件的颜色,但是找不到任何方法来修改选定段落背景颜色。设置文本属性仍然有效。即使尝试设置标题的背景色,也只会影响标题的背景而不是选定段落的背景颜色。

总之,在iOS 13中如何修改当前选定的UISegmentedControl段落的背景颜色?是否有公共API的正确解决方案,而不需要深入挖掘私有子视图结构?

iOS 13中的UISegmentedControlUIControl没有新属性,UIView的更改也都不相关。

18个回答

175
从 iOS 13b3 开始,UISegmentedControl 现在有一个 selectedSegmentTintColor 属性。要更改分段控件的整体颜色,请使用其 backgroundColor 属性。要更改所选段的颜色,请使用 selectedSegmentTintColor 属性。要更改未选中段标题的颜色/字体,请使用状态为 .normal/UIControlStateNormalsetTitleTextAttributes 方法。要更改所选段标题的颜色/字体,请使用状态为 .selected/UIControlStateSelectedsetTitleTextAttributes 方法。 如果您创建带有图像的分段控件,并且图像是模板图像,则分段控件的 tintColor 将用于着色图像。但是这会有一个问题。如果您将 tintColor 设置为与 selectedSegmentTintColor 相同的颜色,则所选段中的图像将不可见。如果您将 tintColor 设置为与 backgroundColor 相同的颜色,则未选定段上的图像将不可见。这意味着您的带有图像的分段控件必须使用三种不同的颜色才能使所有内容都可见。或者您可以使用非模板图像并且不设置 tintColor。 在 iOS 12 或更早版本中,只需设置分段控件的 tintColor 属性或依赖于应用程序的整体 tint 颜色即可。

我们如何在没有边框的情况下设置分段控制器?我在iOS 13中没有看到这个设置。以前,设置tintcolor就足以获得无边框的分段控制。 - Deepak Sharma
1
@YogeshPatel 邊框顏色呢?在iOS 13中沒有邊框顏色,在iOS 12中使用tintColor設置,這已經在答案中提到了。 - rmaddy
1
哦,那个边框。这适用于任何视图,不仅仅是分段控件。这超出了原始问题和答案的范围。你的评论已经足够了。 - rmaddy
1
我不知道为什么当我使用UIColor.white时,backgroundColor无法工作,但是当我使用UIColor.red或其他颜色时它可以工作? - Mohammed Abunada
@MohammedAbunada 你应该发布自己的问题并提供所有相关细节。 - rmaddy
显示剩余4条评论

69

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
}

26
你尝试将背景设置为白色了吗?对我来说,它显示为灰色。 - Chinmay Balutkar
你使用的是哪个版本的Xcode和Swift语言? - Maulik Patel
@Ronit 当然!我会尝试,但请告诉我现在需要什么类型的输出显示。 - Maulik Patel
2
Swift 5,Xcode 11.3!它显示它想要的!而不是我想要的 :) - Am1rFT
3
@Ronit,你解决了吗?.white也会给我灰色。 - CyberMew
我有同样的问题 - 白色和其他颜色没有正确设置。 - Gargo

58

截至Xcode 11 beta 3

UISegmentedControl现在有一个selectedSegmentTintColor属性。

参见rmaddy的答案


获取iOS 12外观

我无法给选定的片段着色,希望在即将推出的测试版中能够修复。

如果不设置正常状态的背景图像(这会删除所有iOS 13样式),则无法设置所选状态的背景图像。

但是,我已经将其恢复到了iOS 12的外观(或者接近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
        }
    }
}

Image showing the effect of the above code


9
以下是针对 UIImage(color:) 扩展的翻译:该扩展允许您通过颜色生成纯色图像。它使用 Core Graphics 框架创建图形上下文并绘制一个填充了指定颜色的矩形。然后,使用 CGImage 从图形上下文中获取图像数据,并使用此数据创建一个 UIImage 对象。最后返回该 UIImage 对象。以下是代码示例:extension UIImage { convenience init?(color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) { let rect = CGRect(origin: .zero, size: size) UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) color.setFill() UIRectFill(rect) guard let image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else { return nil } UIGraphicsEndImageContext() self.init(cgImage: image) } }要使用此扩展程序,请使用以下代码:let redImage = UIImage(color: UIColor.red) let blueImage = UIImage(color: UIColor.blue, size: CGSize(width: 10.0, height: 20.0)) - Nike Kov
好的答案,有效!请注意,这段代码不应该放在layoutSubviews中。 - Nike Kov
1
如果您能添加UIImage扩展,那就太好了,这样您的答案就不算完整。 - FredFlinstone
有没有使用Objective-C的方法?我正在处理一个旧的代码库,似乎只有在有源代码的情况下才能使用Obj-c扩展。 - Alyoshak
2
@VityaShurapov,它是为当它既被突出显示又被选中时设置的,它不是传递的状态数组,而是一个选项集,这意味着值被组合以创建新状态。 - Jonathan.
显示剩余10条评论

21

@Ilahi Charfeddine答案的Swift版本:

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

20

我尝试了这个解决方法,对我来说非常有效。以下是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 必须与分段控件的边界大小相同。 - Cœur
2
自从iOS13 beta 6以来,选定的按钮上没有显示tintcolor,所以我不得不添加一行代码:[self setTitleTextAttributes:@{NSForegroundColorAttributeName: UIColor.blackColor, NSFontAttributeName: [UIFont systemFontOfSize:13]} forState:UIControlStateSelected]; - Peter Johnson
当我尝试在 [[UISegmentedControl appearance] ensureiOS12Style] 上使用它时,我遇到了异常。有任何想法发生了什么?终止应用程序,原因是未捕获的异常 'NSInvalidArgumentException',原因:'-[NSMethodSignature getArgumentTypeAtIndex:]:索引(2)超出范围 [0, 1]' - Jeremy Hicks
由于某种原因,在共享扩展中这没有任何影响... - jjxtra

15

截至 Xcode 11 beta 3

UISegmentedControl 中现在有 selectedSegmentTintColor 属性。

感谢 @rmaddy!


Xcode 11 beta 和 beta 2 的原始回答

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

使用 Xcode 11.0 beta 版本似乎很难通过规则来实现,因为这基本上要求您自己通过 resizableImage(withCapInsets:) 绘制所有状态的背景图像,具有圆角、透明度和可变大小。例如,您需要生成类似于以下的彩色图像:
enter image description here

因此,目前,让我们深入挖掘子视图的方式似乎更容易:

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

这个解决方案将正确地应用色彩渲染到选择区域,如下图所示:enter image description here


1
因为时间紧迫,但最好能找到一种不涉及私人层次结构的解决方案 :) - Jonathan.
@Jonathan。谢谢。您已经得到了最接近的解决方案,而不涉及查看层次结构:因为一旦您为“normal”设置了“setBackgroundImage”,就必须设置所有其他图像(包括其他状态和“setDividerImage”),可能需要使用一些“UIBezierPath”和“resizableImage(withCapInsets :)”,这使得如果我们想以这种方式设计iOS 13,它变得过于复杂。 - Cœur
是的,理想情况下它将在beta版中得到修复。 - Jonathan.
3
自 iOS 13b3 起不再需要这个了。现在在 UISegmentedControl 上有一个 selectedSegmentTintColor 属性可用。 - rmaddy

14

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
太棒了!仍然适用于iOS 14及以下版本。 - Lito
这对于去除默认的灰色背景颜色非常有用。@DenNukem 为了获得片段色调颜色,let selected = UIImage(color: .systemPink, size: CGSize(width: 1, height: 32)) UISegmentedControl.appearance().setBackgroundImage(selected, for: .selected, barMetrics: .default) 对我很有效。 - sally2000
在使用iOS 14.5时,selectedSegmentTintColor属性没有被正确识别。 - CyberMew

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

8

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

6

SwiftUI的Picker缺少一些基本选项。对于想要在iOS 13或14中使用SegmentedPickerStyle()自定义选择器的人来说,最简单的方法是使用UISegmentedControl.appearance()全局设置外观。下面是一个示例函数,可以调用它来设置外观。

func setUISegmentControlAppearance() {
    UISegmentedControl.appearance().selectedSegmentTintColor = .white
    UISegmentedControl.appearance().backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.1)
    UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.black], for: .normal)
    UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
}

然而,如果您需要不同设置的多个控件,则使用 UISegmentedControl.appearance() 全局设置外观选项不是一个好选择。另一种选择是为 UISegmentedControl 实现 UIViewRepresentable。以下示例设置了原始问题中要求的属性,并将 .apportionsSegmentWidthsByContent = true 作为额外奖励。希望这会为您节省一些时间...

struct MyPicker: UIViewRepresentable {

    @Binding var selection: Int // The type of selection may vary depending on your use case
    var items: [Any]?

    class Coordinator: NSObject {
        let parent: MyPicker
        init(parent: MyPicker) {
            self.parent = parent
        }

        @objc func valueChanged(_ sender: UISegmentedControl) {
            self.parent.selection = Int(sender.selectedSegmentIndex)
        }
    }

    func makeCoordinator() -> MyPicker.Coordinator {
        Coordinator(parent: self)
    }

    func makeUIView(context: Context) -> UISegmentedControl {
        let picker = UISegmentedControl(items: self.items)

        // Any number of other UISegmentedControl settings can go here
        picker.selectedSegmentTintColor = .white
        picker.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.1)
        picker.setTitleTextAttributes([.foregroundColor: UIColor.black], for: .normal)
        picker.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
        picker.apportionsSegmentWidthsByContent = true

        // Make sure the coordinator updates the picker when the value changes
        picker.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged(_:)), for: .valueChanged)

        return picker
    }

    func updateUIView(_ uiView: UISegmentedControl, context: Context) {
        uiView.selectedSegmentIndex = self.selection
    }
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接