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

rma*_*ddy 68 uisegmentedcontrol uikit ios ios13

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

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

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

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

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

rma*_*ddy 91

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

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

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

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

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

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

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


Jon*_*an. 33

从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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • /sf/answers/2357261231/用于UIImage(color :)扩展。 (3认同)
  • @VityaShurapov 将其设置为当它同时突出显示*和*时,它不是传入的状态数组,而是一个选项集,这意味着这些值被组合起来创建一个新状态。 (2认同)

Jig*_*rji 11

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()!)!
    }
}
Run Code Online (Sandbox Code Playgroud)


Cœu*_*œur 10

从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
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 从iOS 13b3开始不再需要此功能。现在,UISegmentedControl上具有selectedSegmentTintColor属性。 (2认同)

Ila*_*ine 8

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]];}
Run Code Online (Sandbox Code Playgroud)


Fre*_*one 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)
   }
}
Run Code Online (Sandbox Code Playgroud)

然后我们扩展 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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


小智 7

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

@interface UISegmentedControl (Common)
- (void)ensureiOS12Style;
@end
Run Code Online (Sandbox Code Playgroud)
@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
Run Code Online (Sandbox Code Playgroud)

  • 我不确定它是否适用于`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]} forState:UIControlStateSelected]; (2认同)

Ada*_*dam 6

虽然上面的答案很好,但大多数人都将所选段内的文本颜色弄错了。我创建了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)
        }
    }
Run Code Online (Sandbox Code Playgroud)

使用tintColorDidChange方法,我们确保每次段视图或任何底层视图上的属性更改时stylize都会调用该方法,这是 iOS 上的首选行为。tintColor

结果: 在此输入图像描述


use*_*465 6

SwiftUIPicker缺少​​一些基本选项。对于尝试在 iOS 13 或 14 的 SwiftUI 中使用 SegmentedPickerStyle() 自定义 Picker 的人来说,最简单的选择是用于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)
}
Run Code Online (Sandbox Code Playgroud)

但是,UISegmentedControl.appearance()如果您想要具有不同设置的多个控件,则全局设置外观选项并不是很好。另一种选择是实施UIViewRepresentablefor UISegmentedControl. 这是一个示例,设置原始问题中询问的属性并设置.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
    }
 }
Run Code Online (Sandbox Code Playgroud)


t9m*_*ike 5

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

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;
}
Run Code Online (Sandbox Code Playgroud)


Zai*_*jum 5

您可以实施以下方法

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

使用代码

segmentControl.unselectedSegmentTintColor(.white)
segmentControl.selectedSegmentTintColor(.black)
Run Code Online (Sandbox Code Playgroud)


Vig*_*n S 5

@Ilahi Charfeddine的Swift版本答案:

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


Mau*_*tel 5

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

在此处输入图片说明

在此处输入图片说明

 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
}
Run Code Online (Sandbox Code Playgroud)

  • 您是否尝试将背景设置为白色?对我来说,它即将变成灰色 (13认同)
  • @Ronit你解决了吗?`.white` 也给了我灰色。 (3认同)
  • 快速 5,Xcode 11.3!它显示了它想要的!不是我想要的:) (2认同)