Как изменить цвета сегмента в UISegmentedControl в iOS 13? - PullRequest
18 голосов
/ 04 июня 2019

A UISegmentedControl имеет новый внешний вид в iOS 13, и существующий код для изменения цветов сегментированного элемента управления больше не работает, как они.

До iOS 13 вы могли установить tintColor, и это будет использоваться для границы вокруг сегментированного элемента управления, линий между сегментами и цвета фона выбранного сегмента. Затем вы можете изменить цвет заголовков каждого сегмента, используя атрибут цвета переднего плана с titleTextAttributes.

Под iOS 13 tintColor ничего не делает. Вы можете установить сегментированный элемент управления backgroundColor, чтобы изменить общий цвет сегментированного элемента управления. Но я не могу найти способ изменить цвет, используемый в качестве фона выбранного сегмента. Настройка атрибутов текста все еще работает. Я даже пытался установить цвет фона заголовка, но это влияет только на фон заголовка, а не на цвет фона выбранного сегмента.

Короче говоря, как вы изменяете цвет фона выбранного в данный момент сегмента UISegmentedControl в iOS 13? Есть ли подходящее решение с использованием общедоступных API, которое не требует углубления в структуру частного подпредставления?

В iOS 13 нет новых свойств для UISegmentedControl или UIControl, и ни одно из изменений в UIView не имеет значения.

Ответы [ 5 ]

8 голосов
/ 05 июня 2019

Начиная с Xcode 11 beta 3

Теперь UISegmentedControl.

есть свойство
selectedSegmentTintColor
.

См. Ответ Рмадди


Чтобы вернуть внешний вид iOS 12

Мне не удалось подкрасить цвет выбранного сегмента, надеюсь, он будет исправлен в следующей бета-версии.

Установка фонового изображения для выбранного состояния не работает без установки фонового изображения для нормального состояния (которое удаляет все стили для 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
        }
    }
}

Image showing the effect of the above code

6 голосов
/ 16 июня 2019

Начиная с бета-версии Xcode 11 3

Теперь есть свойство selectedSegmentTintColor в UISegmentedControl.

Спасибо @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

4 голосов
/ 03 июля 2019

Начиная с iOS 13b3, теперь selectedSegmentTintColor на UISegmentedControl.

. Чтобы изменить общий цвет сегментированного элемента управления, используйте backgroundColor.

.Цвет выбранного сегмента используйте selectedSegmentTintColor.

. Чтобы изменить цвет / шрифт заголовков невыбранного сегмента, используйте setTitleTextAttributes с состоянием .normal / UIControlStateNormal.

.Чтобы изменить цвет / шрифт заголовков выбранного сегмента, используйте setTitleTextAttributes с состоянием .selected / UIControlStateSelected.

Если вы создаете сегментированный элемент управления с изображениями, если изображения создаются как шаблонизображения, тогда сегментированный элемент управления tintColor будет использоваться для окрашивания изображений.Но в этом есть проблема.Если вы установите для tintColor тот же цвет, что и для selectedSegmentTintColor, изображение не будет отображаться в выбранном сегменте.Если вы установите для tintColor тот же цвет, что и для backgroundColor, изображения на невыбранных сегментах не будут видны.Это означает, что ваш сегментированный элемент управления с изображениями должен использовать 3 разных цвета, чтобы все было видно.Или вы можете использовать не шаблонные изображения и не устанавливать tintColor.

В iOS 12 или более ранней версии просто установите сегментированный элемент управления tintColor или используйте общий оттенок приложения.

3 голосов
/ 05 июня 2019

Я попробовал обходной путь, и он прекрасно работает для меня.Вот версия 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
1 голос
/ 23 июня 2019

Вот мой взгляд на ответ Джонатана на Xamarin.iOS (C #), но с исправлениями для изменения размера изображения.Как и в комментарии Кёра к ответу Колина Блейка, я сделал все изображения, кроме делителя, размером сегментированного элемента управления.Делитель - это 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;
}
...