Un UISegmentedControl
a une nouvelle apparence dans iOS 13 et le code existant pour modifier les couleurs du contrôle segmenté ne fonctionne plus comme ils le faisaient.
Avant iOS 13, vous pouviez définir le tintColor
et celui-ci serait utilisé pour la bordure autour du contrôle segmenté, les lignes entre les segments et la couleur d'arrière-plan du segment sélectionné. Ensuite, vous pouvez changer la couleur des titres de chaque segment en utilisant l'attribut de couleur de premier plan avec titleTextAttributes
.
Sous iOS 13, le tintColor
ne fait rien. Vous pouvez définir backgroundColor
du contrôle segmenté pour modifier la couleur globale du contrôle segmenté. Mais je ne trouve aucun moyen de modifier la couleur utilisée comme arrière-plan du segment sélectionné. La définition des attributs de texte fonctionne toujours. J'ai même essayé de définir la couleur d'arrière-plan du titre, mais cela n'affecte que l'arrière-plan du titre, pas le reste de la couleur d'arrière-plan du segment sélectionné.
En bref, comment modifier la couleur d'arrière-plan du segment actuellement sélectionné d'un UISegmentedControl
dans iOS 13? Existe-t-il une solution appropriée, utilisant des API publiques, qui ne nécessite pas de creuser dans la structure de sous-vue privée?
Il n'y a pas de nouvelles propriétés dans iOS 13 pour UISegmentedControl
ou UIControl
et aucune des modifications de UIView
n'est pertinente.
Depuis iOS 13b3, il existe désormais un selectedSegmentTintColor
sur UISegmentedControl
.
Pour modifier la couleur globale du contrôle segmenté, utilisez son backgroundColor
.
Pour modifier la couleur du segment sélectionné, utilisez selectedSegmentTintColor
.
Pour changer la couleur/police des titres de segments non sélectionnés, utilisez setTitleTextAttributes
avec un état de .normal
/UIControlStateNormal
.
Pour modifier la couleur/police des titres de segment sélectionnés, utilisez setTitleTextAttributes
avec un état de .selected
/UIControlStateSelected
.
Si vous créez un contrôle segmenté avec des images, si les images sont créées en tant qu'images de modèle, le tintColor
du contrôle segmenté sera utilisé pour colorer les images. Mais cela a un problème. Si vous définissez tintColor
sur la même couleur que selectedSegmentTintColor
, l'image ne sera pas visible dans le segment sélectionné. Si vous définissez tintColor
sur la même couleur que backgroundColor
, les images sur les segments non sélectionnés ne seront pas visibles. Cela signifie que votre contrôle segmenté avec des images doit utiliser 3 couleurs différentes pour que tout soit visible. Ou vous pouvez utiliser des images non modèles et ne pas définir le tintColor
.
Sous iOS 12 ou version antérieure, définissez simplement le tintColor
du contrôle segmenté ou utilisez la couleur de teinte globale de l'application.
Il y a maintenant la propriété
selectedSegmentTintColor
surUISegmentedControl
.
Voir réponse de rmaddy
Je n'ai pas pu teinter la couleur du segment sélectionné, j'espère qu'il sera corrigé dans une prochaine version bêta.
La définition de l'image d'arrière-plan de l'état sélectionné ne fonctionne pas sans la définition de l'image d'arrière-plan de l'état normal (ce qui supprime tout le style iOS 13)
Mais j'ai pu le ramener à l'apparence iOS 12 (ou assez près, je n'ai pas pu ramener le rayon du coin à sa plus petite taille).
Ce n'est pas idéal, mais un contrôle segmenté blanc brillant semble un peu à sa place dans notre application.
(Je ne savais pas que UIImage(color:)
était une méthode d'extension dans notre base de code. Mais le code pour l'implémenter est sur le 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
}
}
}
IOS 13 et Swift 5.0 (Xcode 11.0) Contrôle de segment 100% de travail
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
}
Il y a maintenant la propriété
selectedSegmentTintColor
surUISegmentedControl
.
Merci @rmaddy!
Existe-t-il une solution appropriée, utilisant des API publiques, qui ne nécessite pas de creuser dans la structure de sous-vue privée?
Avec Xcode 11.0 beta, cela semble être un défi de le faire selon les règles, car il nécessite essentiellement de redessiner vous-même toutes les images d'arrière-plan pour chaque état, avec des coins arrondis, de la transparence et resizableImage(withCapInsets:)
. Par exemple, vous devrez générer une image colorée similaire à:
Donc pour l'instant, la façon de creuser dans les sous-vues semble beaucoup plus facile:
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
}
}
}
}
}
Cette solution appliquera correctement la couleur de teinte à la sélection, comme dans:
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]];}
J'ai essayé la solution de contournement et cela fonctionne très bien pour moi. Voici la version 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
Version rapide de @Ilahi Charfeddine réponse:
if #available(iOS 13.0, *) {
segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
segmentedControl.selectedSegmentTintColor = UIColor.blue
} else {
segmentedControl.tintColor = UIColor.blue
}
Voici mon point de vue sur la réponse de Jonathan. Pour Xamarin.iOS (C #), mais avec des correctifs pour le dimensionnement de l'image. Comme pour le commentaire de Cœur sur la réponse de Colin Blake, j'ai fait toutes les images sauf le diviseur de la taille du contrôle segmenté. Le diviseur est 1xheight du segment.
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;
}
Vous pouvez implémenter la méthode suivante
extension UISegmentedControl{
func selectedSegmentTintColor(_ color: UIColor) {
self.setTitleTextAttributes([.foregroundColor: color], for: .selected)
}
func unselectedSegmentTintColor(_ color: UIColor) {
self.setTitleTextAttributes([.foregroundColor: color], for: .normal)
}
}
Code d'utilisation
segmentControl.unselectedSegmentTintColor(.white)
segmentControl.selectedSegmentTintColor(.black)
iOS13 UISegmentController
comment utiliser:
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()!)!
}
}
XCODE 11.1 et iOS 1
Basé sur la réponse de @Jigar Darji mais une mise en œuvre plus sûre.
Nous créons d'abord un initialiseur pratique disponible:
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)
}
}
Ensuite, nous étendons 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
}
}
}