web-dev-qa-db-ja.com

iOS 13のUISegmentedControlでセグメントの色を変更する方法は?

UISegmentedControlはiOS 13で新しい外観になり、セグメント化されたコントロールの色を変更する既存のコードは機能しなくなりました。

IOS 13より前のバージョンでは、tintColorを設定できました。これは、セグメント化されたコントロールの周りの境界線、セグメント間の線、および選択したセグメントの背景色に使用されていました。次に、titleTextAttributesで前景色の属性を使用して、各セグメントのタイトルの色を変更できます。

IOS 13では、tintColorは何もしません。セグメント化されたコントロールのbackgroundColorを設定して、セグメント化されたコントロールの全体的な色を変更できます。しかし、選択したセグメントの背景として使用される色を変更する方法が見つかりません。テキスト属性の設定は引き続き機能します。タイトルの背景色を設定してみましたが、これはタイトルの背景にのみ影響し、選択したセグメントの残りの背景色には影響しません。

つまり、iOS 13でUISegmentedControlの現在選択されているセグメントの背景色をどのように変更しますか?プライベートサブビュー構造を掘り下げる必要のない、パブリックAPIを使用する適切なソリューションはありますか?

IOS 13にはUISegmentedControlまたはUIControlの新しいプロパティはなく、UIViewの変更は関係ありません。

95
rmaddy

IOS 13b3以降、selectedSegmentTintColorUISegmentedControlが追加されました。

セグメント化されたコントロールの全体的な色を変更するには、そのbackgroundColorを使用します。

選択したセグメントの色を変更するには、selectedSegmentTintColorを使用します。

選択されていないセグメントタイトルの色/フォントを変更するには、.normal/setTitleTextAttributesの状態でUIControlStateNormalを使用します。

選択したセグメントタイトルの色/フォントを変更するには、.selected/setTitleTextAttributesの状態でUIControlStateSelectedを使用します。

画像付きのセグメント化されたコントロールを作成する場合、画像がテンプレート画像として作成されると、セグメント化されたコントロールのtintColorが画像の色付けに使用されます。しかし、これには問題があります。 tintColorselectedSegmentTintColorと同じ色に設定すると、選択したセグメントで画像が表示されなくなります。 tintColorbackgroundColorと同じ色に設定すると、選択されていないセグメントの画像は表示されなくなります。つまり、画像付きのセグメント化されたコントロールは、すべてを表示するために3つの異なる色を使用する必要があります。または、テンプレート以外のイメージを使用して、tintColorを設定しないこともできます。

IOS 12以前では、セグメント化されたコントロールのtintColorを設定するか、アプリの全体的なティントカラーに依存します。

117
rmaddy

Xcode 11ベータ3以降

現在、selectedSegmentTintColorUISegmentedControl プロパティがあります。

rmaddy's answer を参照してください


IOS 12の外観に戻すには

選択したセグメントの色に色を付けることができませんでした。うまくいけば、次のベータ版で修正されるでしょう。

選択した状態の背景画像の設定は、通常の状態の背景画像を設定しないと機能しません(すべてのiOS 13スタイルが削除されます)

しかし、私はそれをiOS 12の外観に戻すことができました(または、十分に近いと、角の半径を小さいサイズに戻すことができませんでした)。

これは理想的ではありませんが、明るい白いセグメント化されたコントロールは、アプリで少し場違いに見えます。

UIImage(color:)がコードベースの拡張メソッドであることを理解していませんでした。しかし、それを実装するコードはWebにあります)

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

41
Jonathan.

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
}
17
Maulik Patel

Xcode 11ベータ3以降

現在、selectedSegmentTintColorUISegmentedControl プロパティがあります。

@rmaddyありがとうございます!


Xcode 11ベータおよびベータ2の元の回答

パブリックAPIを使用して、プライベートサブビュー構造を掘り下げる必要のない適切なソリューションはありますか?

Xcode 11.0ベータでは、基本的に、丸い角、透明度、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

10
Cœur
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

私は回避策を試してみましたが、それは私にとってはうまくいきます。これが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
8
Colin Blake

@Ilahi Charfeddine回答のSwiftバージョン:

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

Xamarin.iOS(C#)に対するJonathan。の答えを以下に示しますが、画像サイズの修正が含まれています。 Colin Blakeの回答に対するCœurのコメントと同様に、分割器を除くすべての画像をセグメント化されたコントロールのサイズにしました。仕切りはセグメントの高さの1倍です。

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

次のメソッドを実装できます

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)
3
Zain Anjum

iOS 13 UIセグメントコントローラー

使い方:

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

XCODE 11.1およびiOS 1

@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
    }
  }
}
1
FredFlinstone