Как изменить цвета сегмента в UISegmentedControl в iOS 13?
UISegmentedControl
имеет новый внешний вид в iOS 13, и существующий код для изменения цветов сегментированного элемента управления больше не работает, как они.
До iOS 13 вы могли установить tintColor
, который будет использоваться для границы вокруг сегментированного tintColor
управления, линий между сегментами и цвета фона выбранного сегмента. Затем вы можете изменить цвет заголовков каждого сегмента, используя атрибут цвета переднего плана с titleTextAttributes
.
Под iOS 13 tintColor
ничего не делает. Вы можете установить сегментированный элемент управления backgroundColor
для изменения общего цвета сегментированного элемента управления. Но я не могу найти способ изменить цвет, используемый в качестве фона выбранного сегмента. Настройка атрибутов текста все еще работает. Я даже пытался установить цвет фона заголовка, но это влияет только на фон заголовка, а не на остальную часть цвета выбранного сегмента.
Короче говоря, как вы изменяете цвет фона текущего выбранного сегмента UISegmentedControl
в iOS 13? Есть ли правильное решение с использованием общедоступных API, которое не требует углубления в структуру частного подпредставления?
В iOS 13 нет новых свойств для UISegmentedControl
или UIControl
и ни одно из изменений в UIView
имеет значения.
Ответы
Ответ 1
Как ИО 13b3, есть теперь selectedSegmentTintColor
на UISegmentedControl
.
Чтобы изменить общий цвет сегментированного элемента управления, используйте его backgroundColor
.
Для изменения цвета выбранного сегмента используйте selectedSegmentTintColor
.
Чтобы изменить цвет/шрифт заголовков невыбранных сегментов, используйте setTitleTextAttributes
с состоянием .normal
/UIControlStateNormal
.
Для того, чтобы изменить цвет/шрифт выбранных заголовков сегментов, используйте setTitleTextAttributes
с состоянием .selected
/UIControlStateSelected
.
Если вы создаете сегментированный элемент управления с изображениями, если изображения создаются в качестве шаблонных изображений, то сегментированный элемент управления tintColor
будет использоваться для окрашивания изображений. Но в этом есть проблема. Если вы установите для цвета tintColor
тот же цвет, что и selectedSegmentTintColor
изображение не будет отображаться в выбранном сегменте. Если вы установите для цвета tintColor
тот же цвет, что и для backgroundColor
, изображения на невыбранных сегментах не будут видны. Это означает, что ваш сегментированный элемент управления с изображениями должен использовать 3 разных цвета, чтобы все было видно. Или вы можете использовать не шаблонные изображения и не устанавливать tintColor
.
В iOS 12 или более ранней версии просто установите сегментированный элемент управления tintColor
или используйте общий оттенок приложения.
Ответ 2
Начиная с Xcode 11 beta 3
There is now the selectedSegmentTintColor
property on UISegmentedControl
.
Смотрите ответ Rmaddy
Чтобы вернуть iOS 12 внешний вид
Мне не удалось подкрасить цвет выбранного сегмента, надеюсь, это будет исправлено в следующей бета-версии.
Установка фонового изображения для выбранного состояния не работает без установки фонового изображения в нормальном состоянии (который удаляет все стили для iOS 13)
Но я смог вернуть его к внешнему виду iOS 12 (или достаточно близко, я не смог вернуть angular радиус к его меньшему размеру).
Это не идеально, но ярко-белый сегментированный элемент управления выглядит немного неуместно в нашем приложении.
(Не знал, что UIImage(color:)
был методом расширения в нашей кодовой базе. Но код для его реализации есть в сети)
extension UISegmentedControl {
/// Tint color does not 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]()
Ответ 3
Начиная с Xcode 11 beta 3
There is now the selectedSegmentTintColor
property on 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
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]];}
Ответ 5
Я попробовал обходной путь, и он прекрасно работает для меня. Вот версия 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
Ответ 6
Вот мой взгляд на Джонатана. ответ для Xamarin.iOS (С#), но с исправлениями для размера изображения. Как и в случае с 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;
}
Ответ 7
Вы можете реализовать следующий метод
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)
Ответ 8
Быстрая версия ответа @Ilahi Charfeddine:
if #available(iOS 13.0, *) {
segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
segmentedControl.selectedSegmentTintColor = UIColor.blue
} else {
segmentedControl.tintColor = UIColor.blue
}
Ответ 9
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()!)!
}
}
Ответ 10
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
}
}
}
Ответ 11
Немного расскажу об ответе Джонатана, если вы не хотите, чтобы закругленные углы были
extension UISegmentedControl {
/// Tint color does not have any effect on iOS 13.
func ensureiOS12Style(roundCorner: Bool = true) {
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)
if !roundCorner {
layer.masksToBounds = false
let borderView = UIView()
borderView.layer.borderWidth = 1
borderView.layer.borderColor = UIColor.black.cgColor
borderView.isUserInteractionEnabled = false
borderView.translatesAutoresizingMaskIntoConstraints = false
addSubview(borderView)
NSLayoutConstraint(item: borderView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: borderView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: borderView, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: borderView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0).isActive = true
}
}
}
}