Come faccio a far visualizzare a UILabel il testo sottolineato?


108

Tutto quello che voglio è un bordo nero di un pixel attorno al mio testo UILabel bianco.

Sono arrivato fino alla sottoclasse di UILabel con il codice seguente, che ho messo insieme goffamente da alcuni esempi online correlati tangenzialmente. E funziona ma è molto, molto lento (tranne che sul simulatore) e non sono riuscito a centrare il testo verticalmente (quindi ho codificato temporaneamente il valore y sull'ultima riga). 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]);
}

Ho solo la fastidiosa sensazione di trascurare un modo più semplice per farlo. Forse sovrascrivendo "drawTextInRect", ma non riesco a convincere drawTextInRect a piegarsi alla mia volontà nonostante lo fissi intensamente e aggrottando le sopracciglia.


Chiarimento: la lentezza è evidente nella mia app perché sto animando l'etichetta quando il suo valore cambia con una leggera crescita e riduzione. Senza sottoclassi è fluido, ma con il codice sopra l'animazione dell'etichetta è molto instabile. Devo solo usare un UIWebView? Mi sento sciocco a farlo perché l'etichetta mostra solo un numero singolo ...
Monte Hurd

Ok, sembra che il problema di prestazioni che stavo avendo non fosse correlato al codice di struttura, ma non riesco ancora a farlo allineare verticalmente. pt.y è sempre zero per qualche motivo.
Monte Hurd

Questo è molto lento per caratteri come Chalkduster
jjxtra

Risposte:


164

Sono stato in grado di farlo sovrascrivendo 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;

}

3
Ho provato questa tecnica ma i risultati sono poco soddisfacenti. Sul mio iPhone 4 ho provato a utilizzare questo codice per disegnare del testo bianco con un contorno nero. Quello che ho ottenuto erano contorni neri sui bordi sinistro e destro di ogni lettera nel testo ma non contorni in alto o in basso. Qualche idea?
Mike Hershberg

scusa sto provando a usare il tuo codice nel mio progetto ma non succede niente! guarda questa foto: link
Momi

@ Momeks: devi sottoclassare UILabele inserire l' -drawTextInRect:implementazione lì.
Jeff Kelley

1
@Momeks: Se non sai come creare una sottoclasse in Objective-C, dovresti fare qualche ricerca su Google; Apple ha una guida al linguaggio Objective-C.
Jeff Kelley

1
Probabilmente - non penso che esistesse quando ho scritto questo 2,5 anni fa :)
kprevas

114

Una soluzione più semplice è usare una stringa attribuita in questo modo:

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)

Su a UITextFieldpuoi impostare anche il defaultTextAttributese il attributedPlaceholder.

Nota che in questo caso NSStrokeWidthAttributeName deve essere negativo , vale a dire che funzionano solo i contorni interni.

UITextFields con uno schema utilizzando testi attribuiti


17
Per comodità in Objective-C questo sarà: label.attributedText = [[NSAttributedString alloc] initWithString: @ "String" attributes: @ {NSStrokeColorAttributeName: [UIColor blackColor], NSForegroundColorAttributeName: [UIColor whiteColor], @AttributeName]: @AttributeName ;
Leszek Szary

8
L'uso di questo meme ha comunicato efficacemente le sfumature di questo approccio
woody121

Swift 4.2: lasciate strokeTextAttributes: [String: Qualsiasi] = [NSStrokeColorAttributeName: UIColor.black, NSForegroundColorAttributeName: UIColor.white, NSStrokeWidthAttributeName: -4.0,] = consoleView.typingAttributes strokeTextAttributes
Teo Sartori

Grazie @TeoSartori, ho aggiunto la sintassi Swift 4.2 nella mia risposta;)
Axel Guilmin

25

Dopo aver letto la risposta accettata e le due correzioni ad essa e la risposta di Axel Guilmin, ho deciso di compilare una soluzione globale in Swift, che mi si addice:

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)
    }
}

È possibile aggiungere questa classe UILabel personalizzata a un'etichetta esistente in Interface Builder e modificare lo spessore del bordo e il suo colore aggiungendo attributi di runtime definiti dall'utente in questo modo: Configurazione di Interface Builder

Risultato:

grande G rossa con contorno


1
Bello. Farò un IBInspectable con questo :)
Mário Carvalho

@Lachezar come si può fare con il testo di un UIButton?
CrazyOne

8

C'è un problema con l'implementazione della risposta. Disegnare un testo con un tratto ha una larghezza del glifo del carattere leggermente diversa rispetto al disegno di un testo senza tratto, che può produrre risultati "non centrati". Può essere risolto aggiungendo un tratto invisibile attorno al testo di riempimento.

Sostituire:

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

con:

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

Non sono sicuro al 100%, se questo è il vero affare, perché non so se self.textColor = textColor;ha lo stesso effetto di [textColor setFill], ma dovrebbe funzionare.

Divulgazione: sono lo sviluppatore di THLabel.

Qualche tempo fa ho rilasciato una sottoclasse UILabel, che consente una struttura nel testo e altri effetti. Puoi trovarlo qui: https://github.com/tobihagemann/THLabel


4
appena usato THLabel, la vita è già abbastanza complicata così com'è
Prat

1
Grazie a THLabel, sono stato in grado di disegnare un contorno attorno al testo aggiungendo solo due righe di codice. Grazie per aver fornito una soluzione a un problema che ho passato troppo tempo a cercare di risolvere!
Alan Kinnaman

Sono sorpreso che nessuno se ne sia accorto, ma il comando del tratto di testo incorporato non crea un contorno , ma invece crea un " inline " - cioè il tratto viene disegnato all'interno delle lettere, non all'esterno. Con THLabel possiamo scegliere di avere il tratto effettivamente disegnato all'esterno. Ottimo lavoro!
lenooh

5

Questo non creerà un contorno di per sé, ma metterà un'ombra attorno al testo e se rendi il raggio dell'ombra abbastanza piccolo potrebbe assomigliare a un contorno.

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

Non so se sia compatibile con le versioni precedenti di iOS ..

Comunque, spero che aiuti ...


9
Questo non è un bordo di un pixel attorno all'etichetta. Questa è una semplice ombra sul fondo.
Tim

1
questa è una soluzione sbagliata. Buon dio chi l'ha segnato ?!
Sam B

hanno detto abbastanza chiaramente "delineato", questa è un'ombra. non risponde alla domanda
hitme

5

Una versione di classe Swift 4 basata sulla risposta di 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
    }
}

Può essere implementato interamente in Interface Builder impostando la classe personalizzata di UILabel su OutlinedText. Avrai quindi la possibilità di impostare la larghezza e il colore del contorno dal riquadro Proprietà.

inserisci qui la descrizione dell'immagine


Perfetto anche per Swift 5.
Antee86,

4

Se vuoi animare qualcosa di complicato, il modo migliore è prendere uno screenshot in modo programmatico e animarlo invece!

Per acquisire uno screenshot di una vista, avrai bisogno di un codice un po 'come questo:

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

Dove mainContentView è la vista di cui vuoi fare uno screenshot. Aggiungi viewImage a un UIImageView e animalo.

Spero che acceleri la tua animazione !!

N


È un trucco fantastico che posso pensare ad alcuni posti da usare, e probabilmente risolve il problema delle prestazioni, ma continuo a sperare che ci sia un modo più semplice della mia schifosa sottoclasse per rendere il testo uilabel mostrare un contorno. Nel frattempo giocherò con il tuo esempio, grazie!
Monte Hurd

Condivido il tuo dolore. Non sono sicuro che troverai un buon modo per farlo. Scrivo spesso risme di codice per gli effetti del prodotto, quindi ne faccio l'istantanea (come sopra) per un'animazione fluida! Per l'esempio sopra è necessario includere <QuartzCore / QuartzCore.h> nel codice! Buona fortuna! N
Nick Cartwright

4

Come accennato da MuscleRumble , il confine della risposta accettata è un po 'fuori centro. Sono stato in grado di correggere questo problema impostando la larghezza del tratto su zero invece di cambiare il colore in chiaro.

ovvero sostituire:

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

con:

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

Avrei solo commentato la sua risposta ma a quanto pare non sono abbastanza "rispettabile".


2

se TUTTO quello che vuoi è un bordo nero di un pixel attorno al mio testo UILabel bianco,

allora penso che tu stia rendendo il problema più difficile di quanto non sia ... Non so a memoria quale funzione 'draw rect / frameRect' dovresti usare, ma sarà facile per te da trovare. questo metodo dimostra solo la strategia (lascia che sia la superclasse a fare il lavoro!):

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

non lo capisco. probabilmente perché ho fissato lo schermo per circa 12 ore e non ho ottenuto molto a questo punto ...
Monte Hurd

stai sottoclasse UILabel, una classe che già disegna testo. e il motivo per cui crei delle sottoclassi è perché desideri aggiungere un bordo nero attorno al testo. quindi se lasci che sia la superclasse a disegnare il testo, tutto quello che devi fare è disegnare il bordo, no?
Kent

Questo è un buon punto ... anche se, se vuole aggiungere un bordo nero di 1 pixel, la sottoclasse probabilmente disegnerà le lettere troppo vicine tra loro! .... farei qualcosa come postato nella domanda originale e ottimizzerei (come affermato nel mio post)! N
Nick Cartwright

@nickcartwright: se si verifica che la superclasse stia facendo come dici, potresti inserire il CGRect prima di chiamare [super drawRect]. ancora più facile e sicuro che riscrivere la funzionalità della superclasse.
Kent

E poi @kent se ne andò così frustrato. Ottima risposta, +1.
Dan Rosenstark

1

Ho riscontrato un problema con la risposta principale. La posizione del testo non è necessariamente centrata correttamente rispetto alla posizione del sottopixel, in modo che il contorno possa non corrispondere al testo. L'ho risolto utilizzando il seguente codice, che utilizza 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];
}

Ciò presuppone che tu abbia definito textOutlineColore textOutlineWidthcome proprietà.


0

Ecco un'altra risposta per impostare il testo delineato sull'etichetta.

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
 }

}

impostare il testo delineato semplicemente utilizzando il metodo di estensione.

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

0

è anche possibile sottoclassare UILabel con la seguente logica:

- (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;
}

e se imposti il ​​testo in Storyboard, allora:

- (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

Se il tuo obiettivo è qualcosa del genere:

inserisci qui la descrizione dell'immagine

Ecco come l'ho ottenuto: ho aggiunto una nuova labelclasse personalizzata come Sottoview alla mia attuale UILabel (ispirata a questa risposta ).

Basta copiarlo e incollarlo nel tuo progetto e sei a posto:

extension UILabel {
    func addTextOutline(usingColor outlineColor: UIColor, outlineWidth: CGFloat) {
        class OutlinedText: UILabel{
            var outlineWidth: CGFloat = 0
            var outlineColor: UIColor = .clear

            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.textAlignment = .center
                self.textColor = outlineColor
                super.drawText(in:rect)

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

                self.shadowOffset = shadowOffset
            }
        }

        let textOutline = OutlinedText()
        let outlineTag = 9999

        if let prevTextOutline = viewWithTag(outlineTag) {
            prevTextOutline.removeFromSuperview()
        }

        textOutline.outlineColor = outlineColor
        textOutline.outlineWidth = outlineWidth
        textOutline.textColor = textColor
        textOutline.font = font
        textOutline.text = text
        textOutline.tag = outlineTag

        sizeToFit()
        addSubview(textOutline)
        textOutline.frame = CGRect(x: -(outlineWidth / 2), y: -(outlineWidth / 2),
                                   width: bounds.width + outlineWidth,
                                   height: bounds.height + outlineWidth)
    }
}

USO:

yourLabel.addTextOutline(usingColor: .red, outlineWidth: 6)

funziona anche per a UIButtoncon tutte le sue animazioni:

yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)

-3

Perché non crei una UIView con bordo 1px in Photoshop, quindi imposti una UIView con l'immagine e posizionala dietro la tua UILabel?

Codice:

UIView *myView;
UIImage *imageName = [UIImage imageNamed:@"1pxBorderImage.png"];
UIColor *tempColour = [[UIColor alloc] initWithPatternImage:imageName];
myView.backgroundColor = tempColour;
[tempColour release];

Ti salverà dalla sottoclasse di un oggetto ed è abbastanza semplice da fare.

Per non parlare se vuoi fare l'animazione, è integrato nella classe UIView.


Il testo sull'etichetta cambia e ho bisogno che il testo stesso abbia un contorno nero di 1 pixel. (Il resto dello sfondo dell'UILabel è trasparente.)
Monte Hurd

Pensavo di aver capito come questo farà delineare qualsiasi testo che inserisco nell'etichetta UIL, ma ero delirantemente stanco e ora non riesco a pensare a come farlo. Vado a leggere su initWithPatternImage.
Monte Hurd

-3

Per mettere un bordo con bordi arrotondati attorno a un'etichetta UIL, faccio quanto segue:

labelName.layer.borderWidth = 1;
labelName.layer.borderColor = [[UIColor grayColor] CGColor];
labelName.layer.cornerRadius = 10;

(non dimenticare di includere QuartzCore / QuartzCore.h)


5
Questo aggiunge un grande bordo attorno all'intera etichetta, non intorno al testo
Pavel Alexeev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.