web-dev-qa-db-fra.com

Comment faire pour que UILabel affiche le texte souligné?

Tout ce que je veux, c'est une bordure noire d'un pixel autour de mon texte UILabel blanc.

Je suis allé jusqu'à sous-classer UILabel avec le code ci-dessous, que j'ai maladroitement bricolé à partir de quelques exemples en ligne tangentiellement liés. Et cela fonctionne mais c'est très, très lent (sauf sur le simulateur) et je n'ai pas pu le faire pour centrer le texte verticalement non plus (j'ai donc codé en dur la valeur y sur la dernière ligne temporairement). Ahhhh!

void ShowStringCentered(CGContextRef gc, float x, float y, const char *str) {
    CGContextSetTextDrawingMode(gc, kCGTextInvisible);
    CGContextShowTextAtPoint(gc, 0, 0, str, strlen(str));
    CGPoint pt = CGContextGetTextPosition(gc);

    CGContextSetTextDrawingMode(gc, kCGTextFillStroke);

    CGContextShowTextAtPoint(gc, x - pt.x / 2, y, str, strlen(str));
}


- (void)drawRect:(CGRect)rect{

    CGContextRef theContext = UIGraphicsGetCurrentContext();
    CGRect viewBounds = self.bounds;

    CGContextTranslateCTM(theContext, 0, viewBounds.size.height);
    CGContextScaleCTM(theContext, 1, -1);

    CGContextSelectFont (theContext, "Helvetica", viewBounds.size.height,  kCGEncodingMacRoman);

    CGContextSetRGBFillColor (theContext, 1, 1, 1, 1);
    CGContextSetRGBStrokeColor (theContext, 0, 0, 0, 1);
    CGContextSetLineWidth(theContext, 1.0);

    ShowStringCentered(theContext, rect.size.width / 2.0, 12, [[self text] cStringUsingEncoding:NSASCIIStringEncoding]);
}

J'ai juste le sentiment tenace que je néglige une façon plus simple de le faire. Peut-être en remplaçant "drawTextInRect", mais je n'arrive pas à obtenir drawTextInRect à plier à ma volonté malgré le regarder attentivement et froncer les sourcils vraiment très fort.

99
Monte Hurd

J'ai pu le faire en remplaçant drawTextInRect:

- (void)drawTextInRect:(CGRect)rect {

  CGSize shadowOffset = self.shadowOffset;
  UIColor *textColor = self.textColor;

  CGContextRef c = UIGraphicsGetCurrentContext();
  CGContextSetLineWidth(c, 1);
  CGContextSetLineJoin(c, kCGLineJoinRound);

  CGContextSetTextDrawingMode(c, kCGTextStroke);
  self.textColor = [UIColor whiteColor];
  [super drawTextInRect:rect];

  CGContextSetTextDrawingMode(c, kCGTextFill);
  self.textColor = textColor;
  self.shadowOffset = CGSizeMake(0, 0);
  [super drawTextInRect:rect];

  self.shadowOffset = shadowOffset;

}
163
kprevas

Une solution plus simple consiste à utiliser un chaîne attribuée comme ceci:

Swift 4:

let strokeTextAttributes: [NSAttributedStringKey : Any] = [
    NSAttributedStringKey.strokeColor : UIColor.black,
    NSAttributedStringKey.foregroundColor : UIColor.white,
    NSAttributedStringKey.strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)

Swift 4.2:

let strokeTextAttributes: [NSAttributedString.Key : Any] = [
    .strokeColor : UIColor.black,
    .foregroundColor : UIColor.white,
    .strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)

Sur un UITextField, vous pouvez également définir le defaultTextAttributes et le attributedPlaceholder.

Notez que le NSStrokeWidthAttributeNamedoit être négatif dans ce cas, c'est-à-dire que seuls les contours intérieurs fonctionnent.

UITextFields with an outline using attributed texts

100
Axel Guilmin

Après avoir lu la réponse acceptée et les deux corrections y apportées et la réponse d'Axel Guilmin, j'ai décidé de compiler une solution globale dans Swift, qui me convient:

import UIKit

class UIOutlinedLabel: UILabel {

    var outlineWidth: CGFloat = 1
    var outlineColor: UIColor = UIColor.whiteColor()

    override func drawTextInRect(rect: CGRect) {

        let strokeTextAttributes = [
            NSStrokeColorAttributeName : outlineColor,
            NSStrokeWidthAttributeName : -1 * outlineWidth,
        ]

        self.attributedText = NSAttributedString(string: self.text ?? "", attributes: strokeTextAttributes)
        super.drawTextInRect(rect)
    }
}

Vous pouvez ajouter cette classe UILabel personnalisée à une étiquette existante dans le générateur d'interface et modifier l'épaisseur de la bordure et sa couleur en ajoutant des attributs d'exécution définis par l'utilisateur comme ceci: Interface Builder setup

Résultat:

big red G with outline

24
Lachezar

Il y a un problème avec la mise en œuvre de la réponse. Le dessin d'un texte avec trait a une largeur de glyphe de caractères légèrement différente de celle d'un dessin sans trait, ce qui peut produire des résultats "non centrés". Il peut être corrigé en ajoutant un trait invisible autour du texte de remplissage.

Remplacer:

CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

avec:

CGContextSetTextDrawingMode(context, kCGTextFillStroke);
self.textColor = textColor;
[[UIColor clearColor] setStroke]; // invisible stroke
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

Je ne suis pas sûr à 100%, si c'est la vraie affaire, parce que je ne sais pas si self.textColor = textColor; a le même effet que [textColor setFill], mais ça devrait marcher.

Divulgation: je suis le développeur de THLabel.

J'ai publié une sous-classe UILabel il y a un certain temps, qui permet un contour dans le texte et d'autres effets. Vous pouvez le trouver ici: https://github.com/tobihagemann/THLabel

8
tobihagemann

Cela ne créera pas de contour en soi, mais cela mettra une ombre autour du texte, et si vous faites un rayon d'ombre suffisamment petit, cela pourrait ressembler à un contour.

label.layer.shadowColor = [[UIColor blackColor] CGColor];
label.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
label.layer.shadowOpacity = 1.0f;
label.layer.shadowRadius = 1.0f;

Je ne sais pas s'il est compatible avec les anciennes versions d'iOS ..

Quoi qu'il en soit, j'espère que cela aide ...

6
Suran

Si vous voulez animer quelque chose de compliqué, la meilleure façon est d'en faire une capture d'écran par programmation et de l'animer à la place!

Pour prendre une capture d'écran d'une vue, vous aurez besoin d'un code un peu comme ceci:

UIGraphicsBeginImageContext(mainContentView.bounds.size);
[mainContentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); 

Où mainContentView est la vue dont vous souhaitez prendre une capture d'écran. Ajoutez viewImage à un UIImageView et animez-le.

J'espère que cela accélère votre animation !!

N

4
Nick Cartwright

Comme MuscleRumble mentionné, la bordure de la réponse acceptée est un peu excentrée. J'ai pu corriger cela en définissant la largeur du trait sur zéro au lieu de changer la couleur pour effacer.

c'est-à-dire en remplaçant:

CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

avec:

CGContextSetTextDrawingMode(c, kCGTextFillStroke);
self.textColor = textColor;
CGContextSetLineWidth(c, 0); // set stroke width to zero
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];

J'aurais juste commenté sa réponse mais apparemment, je ne suis pas assez "réputé".

3
ufmike

A Swift version 4 classes basée sur le réponse de kprevas

import Foundation
import UIKit

public class OutlinedText: UILabel{
    internal var mOutlineColor:UIColor?
    internal var mOutlineWidth:CGFloat?

    @IBInspectable var outlineColor: UIColor{
        get { return mOutlineColor ?? UIColor.clear }
        set { mOutlineColor = newValue }
    }

    @IBInspectable var outlineWidth: CGFloat{
        get { return mOutlineWidth ?? 0 }
        set { mOutlineWidth = newValue }
    }    

    override public func drawText(in rect: CGRect) {
        let shadowOffset = self.shadowOffset
        let textColor = self.textColor

        let c = UIGraphicsGetCurrentContext()
        c?.setLineWidth(outlineWidth)
        c?.setLineJoin(.round)
        c?.setTextDrawingMode(.stroke)
        self.textColor = mOutlineColor;
        super.drawText(in:rect)

        c?.setTextDrawingMode(.fill)
        self.textColor = textColor
        self.shadowOffset = CGSize(width: 0, height: 0)
        super.drawText(in:rect)

        self.shadowOffset = shadowOffset
    }
}

Il peut être implémenté entièrement dans Interface Builder en définissant la classe personnalisée de UILabel sur OutlinedText. Vous aurez ensuite la possibilité de définir la largeur et la couleur du contour dans le volet Propriétés.

enter image description here

2
Chris Stillwell

si TOUT ce que vous voulez est une bordure noire d'un pixel autour de mon texte UILabel blanc,

alors je pense que vous rendez le problème plus difficile qu'il ne l'est ... Je ne sais pas par mémoire quelle fonction 'draw rect/frameRect' vous devriez utiliser, mais ce sera facile à trouver. cette méthode montre simplement la stratégie (laissez la superclasse faire le travail!):

- (void)drawRect:(CGRect)rect
{
  [super drawRect:rect];
  [context frameRect:rect]; // research which rect drawing function to use...
}
2
kent

J'ai trouvé un problème avec la réponse principale. La position du texte n'est pas nécessairement centrée correctement sur l'emplacement des sous-pixels, de sorte que le contour peut ne pas correspondre autour du texte. Je l'ai corrigé en utilisant le code suivant, qui utilise CGContextSetShouldSubpixelQuantizeFonts(ctx, false):

- (void)drawTextInRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    [self.textOutlineColor setStroke];
    [self.textColor setFill];

    CGContextSetShouldSubpixelQuantizeFonts(ctx, false);

    CGContextSetLineWidth(ctx, self.textOutlineWidth);
    CGContextSetLineJoin(ctx, kCGLineJoinRound);

    CGContextSetTextDrawingMode(ctx, kCGTextStroke);
    [self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];

    CGContextSetTextDrawingMode(ctx, kCGTextFill);
    [self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
}

Cela suppose que vous avez défini textOutlineColor et textOutlineWidth comme propriétés.

1
Robotbugs

Voici une autre réponse pour placer le texte décrit sur l'étiquette.

extension UILabel {

func setOutLinedText(_ text: String) {
    let attribute : [NSAttributedString.Key : Any] = [
        NSAttributedString.Key.strokeColor : UIColor.black,
        NSAttributedString.Key.foregroundColor : UIColor.white,
        NSAttributedString.Key.strokeWidth : -2.0,
        NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 12)
        ] as [NSAttributedString.Key  : Any]

    let customizedText = NSMutableAttributedString(string: text,
                                                   attributes: attribute)
    attributedText = customizedText
 }

}

définir le texte souligné simplement en utilisant la méthode d'extension.

lblTitle.setOutLinedText("Enter your email address or username")
0
Aman Pathak

il est également possible de sous-classer UILabel avec la logique suivante:

- (void)setText:(NSString *)text {
    [self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:text]];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
    [self addOutlineForAttributedText:attributedText];
}

- (void)addOutlineForAttributedText:(NSAttributedString *)attributedText {
    NSDictionary *strokeTextAttributes = @{
                                           NSStrokeColorAttributeName: [UIColor blackColor],
                                           NSStrokeWidthAttributeName : @(-2)
                                           };

    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
    [attrStr addAttributes:strokeTextAttributes range:NSMakeRange(0, attrStr.length)];

    super.attributedText = attrStr;
}

et si vous définissez du texte dans Storyboard, alors:

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

    self = [super initWithCoder:aDecoder];
    if (self) {
        // to apply border for text from storyboard
        [self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:self.text]];
    }
    return self;
}
0
dollar2048