Wie kann ich UILabel dazu bringen, umrissenen Text anzuzeigen?

108

Ich möchte nur einen schwarzen Rand von einem Pixel um meinen weißen UILabel-Text.

Ich bin so weit gekommen, UILabel mit dem folgenden Code zu unterklassifizieren, den ich aus einigen tangential verwandten Online-Beispielen ungeschickt zusammengeschustert habe. Und es funktioniert, aber es ist sehr, sehr langsam (außer im Simulator) und ich konnte es auch nicht dazu bringen, den Text vertikal zu zentrieren (also habe ich den y-Wert in der letzten Zeile vorübergehend fest codiert). 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]);
}

Ich habe nur das quälende Gefühl, dass ich einen einfacheren Weg übersehe, dies zu tun. Vielleicht durch Überschreiben von "drawTextInRect", aber ich kann drawTextInRect anscheinend nicht dazu bringen, sich meinem Willen zu beugen, obwohl ich es aufmerksam anstarrte und wirklich sehr, sehr hart die Stirn runzelte.

Monte Hurd
quelle
Klarstellung - Die Langsamkeit ist in meiner App offensichtlich, da ich das Etikett animiere, wenn sich sein Wert mit einem leichten Wachstum und Schrumpfen ändert. Ohne Unterklasse ist es glatt, aber mit dem Code über der Etikettenanimation ist es viel abgehackt. Sollte ich nur eine UIWebView verwenden? Ich fühle mich dumm dabei, da das Etikett nur eine einzige Zahl anzeigt ...
Monte Hurd
Ok, es sieht so aus, als hätte das Leistungsproblem, das ich hatte, nichts mit dem Gliederungscode zu tun, aber ich kann es immer noch nicht dazu bringen, es vertikal auszurichten. pt.y ist aus irgendeinem Grund immer Null.
Monte Hurd
Dies ist sehr langsam für Schriftarten wie Chalkduster
jjxtra

Antworten:

164

Ich konnte dies tun, indem ich drawTextInRect überschrieb:

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

}
kprevas
quelle
3
Ich habe diese Technik ausprobiert, aber die Ergebnisse sind weniger als zufriedenstellend. Auf meinem iPhone 4 habe ich versucht, mit diesem Code weißen Text mit einem schwarzen Umriss zu zeichnen. Was ich bekam, waren schwarze Umrisse am linken und rechten Rand jedes Buchstabens im Text, aber keine Umrisse oben oder unten. Irgendwelche Ideen?
Mike Hershberg
Entschuldigung, ich versuche, Ihren Code für mein Projekt zu verwenden, aber es passiert nichts! siehe dieses Bild: Link
Momi
@Momeks: Sie müssen UILabeldie -drawTextInRect:Implementierung unterordnen und dort platzieren.
Jeff Kelley
1
@Momeks: Wenn Sie nicht wissen, wie man eine Unterklasse in Objective-C erstellt, sollten Sie etwas googeln. Apple hat eine Anleitung zur Objective-C-Sprache.
Jeff Kelley
1
Wahrscheinlich - ich glaube nicht, dass es das gab, als ich das vor 2,5 Jahren schrieb :)
kprevas
114

Eine einfachere Lösung besteht darin, einen Attributed String wie folgt zu verwenden :

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)

Auf einem können UITextFieldSie das defaultTextAttributesund das attributedPlaceholderauch einstellen .

Beachten Sie, dass in diesem Fall NSStrokeWidthAttributeName das negativ sein muss, dh nur die inneren Umrisse funktionieren.

UITextFields mit einer Gliederung unter Verwendung von zugeordneten Texten

Axel Guilmin
quelle
17
Zur Vereinfachung in Objective-C lautet dies: label.attributedText = [[NSAttributedString-Zuweisung] initWithString: @ "String" -Attribute: @ {NSStrokeColorAttributeName: [UIColor blackColor], NSForegroundColorAttributeName: [UIColorNameColorAttributeName: ;;
Leszek Szary
8
Die Verwendung dieses Memes vermittelte effektiv die Nuance dieses Ansatzes
woody121
4.2 Swift: lassen strokeTextAttributes: [String: Alle] = [NSStrokeColorAttributeName: UIColor.black, NSForegroundColorAttributeName: UIColor.white, NSStrokeWidthAttributeName: -4,0,] consoleView.typingAttributes = strokeTextAttributes
Teo Sartori
Vielen Dank @TeoSartori, ich habe die Swift 4.2-Syntax in meine Antwort aufgenommen;)
Axel Guilmin
25

Nachdem ich die akzeptierte Antwort und die beiden Korrekturen sowie die Antwort von Axel Guilmin gelesen hatte, beschloss ich, in Swift eine Gesamtlösung zu erstellen, die zu mir passt:

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

Sie können diese benutzerdefinierte UILabel-Klasse zu einer vorhandenen Beschriftung im Interface Builder hinzufügen und die Dicke des Rahmens und seine Farbe ändern, indem Sie benutzerdefinierte Laufzeitattribute wie folgt hinzufügen: Interface Builder-Setup

Ergebnis:

großes rotes G mit Umriss

Lachezar
quelle
1
Nett. Ich werde eine IBInspectable damit machen :)
Mário Carvalho
@Lachezar wie kann das mit dem Text eines UIButton gemacht werden?
CrazyOne
8

Es gibt ein Problem bei der Implementierung der Antwort. Das Zeichnen eines Textes mit Strich hat eine etwas andere Zeichenzeichenbreite als das Zeichnen eines Textes ohne Strich, was zu "nicht zentrierten" Ergebnissen führen kann. Sie kann durch Hinzufügen eines unsichtbaren Strichs um den Fülltext behoben werden.

Ersetzen:

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

mit:

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

Ich bin mir nicht 100% sicher, ob das der wahre Deal ist, weil ich nicht weiß, ob self.textColor = textColor; es den gleichen Effekt hat wie[textColor setFill] , aber es sollte funktionieren.

Offenlegung: Ich bin der Entwickler von THLabel.

Ich habe vor einiger Zeit eine UILabel-Unterklasse veröffentlicht, die eine Gliederung in Text und anderen Effekten ermöglicht. Sie finden es hier: https://github.com/tobihagemann/THLabel

tobihagemann
quelle
4
Ich habe gerade THLabel benutzt, das Leben ist so kompliziert wie es ist
Prat
1
Dank THLabel konnte ich durch Hinzufügen von nur zwei Codezeilen einen Umriss um den Text zeichnen. Vielen Dank, dass Sie eine Lösung für ein Problem gefunden haben, für dessen Lösung ich viel zu lange aufgewendet habe!
Alan Kinnaman
Ich bin überrascht, dass niemand es bemerkt hat, aber der eingebaute Textstrichbefehl erzeugt keinen Umriss , sondern eine " Inline " - dh der Strich wird auf der Innenseite der Buchstaben und nicht auf der Außenseite gezeichnet. Mit THLabel können wir wählen, ob der Strich tatsächlich nach außen gezeichnet werden soll. Gut gemacht!
Lenooh
5

Dadurch wird an sich kein Umriss erstellt, aber es wird ein Schatten um den Text gelegt. Wenn Sie den Schattenradius so klein machen, dass er einem Umriss ähnelt.

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

Ich weiß nicht, ob es mit älteren Versionen von iOS kompatibel ist.

Wie auch immer, ich hoffe es hilft ...

Suran
quelle
9
Dies ist kein Ein-Pixel-Rand um das Etikett. Dies ist ein einfacher Schlagschatten nach unten.
Tim
1
Das ist eine falsche Lösung. Guter Gott, wer hat es markiert?!
Sam B
Sie sagten ganz klar "umrissen", dies ist ein Schatten. beantwortet keine Frage
hitme
5

Eine Swift 4-Klassenversion basierend auf der Antwort von 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
    }
}

Es kann vollständig im Interface Builder implementiert werden, indem die benutzerdefinierte Klasse des UILabels auf OutlinedText gesetzt wird. Anschließend können Sie die Breite und Farbe der Kontur im Eigenschaftenbereich festlegen.

Geben Sie hier die Bildbeschreibung ein

Chris Stillwell
quelle
Perfekt auch für Swift 5.
Antee86
4

Wenn Sie etwas Kompliziertes animieren möchten, ist es am besten, programmgesteuert einen Screenshot davon zu erstellen und stattdessen zu animieren!

Um einen Screenshot einer Ansicht zu erstellen, benötigen Sie einen Code wie den folgenden:

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

Wobei mainContentView die Ansicht ist, von der Sie einen Screenshot machen möchten. Fügen Sie viewImage zu einer UIImageView hinzu und animieren Sie diese.

Hoffe das beschleunigt deine Animation !!

N.

Nick Cartwright
quelle
Das ist ein großartiger Trick, den ich mir ein paar Stellen vorstellen kann, die ich verwenden kann, und der wahrscheinlich das Leistungsproblem löst, aber ich hoffe immer noch, dass es einen einfacheren Weg als meine beschissene Unterklasse gibt, Uilabel-Text einen Umriss zu zeigen. In der Zwischenzeit werde ich mit deinem Beispiel spielen, danke!
Monte Hurd
Ich fühle deinen Schmerz. Ich bin mir nicht sicher, ob Sie dafür einen guten Weg finden werden. Ich schreibe oft Unmengen von Code in Produkteffekte und mache dann Schnappschüsse (wie oben) für eine reibungslose Animation! Für das obige Beispiel müssen Sie <QuartzCore / QuartzCore.h> in Ihren Code aufnehmen! Viel Glück! N
Nick Cartwright
4

Als MuscleRumble erwähnte, liegt der Rand der akzeptierten Antwort etwas außerhalb der Mitte. Ich konnte dies korrigieren, indem ich die Strichbreite auf Null setzte, anstatt die Farbe auf Klar zu ändern.

dh ersetzen:

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

mit:

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

Ich hätte seine Antwort nur kommentiert, aber anscheinend bin ich nicht "seriös" genug.

ufmike
quelle
2

Wenn alles, was Sie wollen, ein schwarzer Rand mit einem Pixel um meinen weißen UILabel-Text ist,

dann denke ich, dass Sie das Problem schwieriger machen als es ist ... Ich weiß nicht aus dem Speicher, welche Funktion 'draw rect / frameRect' Sie verwenden sollten, aber es wird für Sie leicht zu finden sein. Diese Methode demonstriert nur die Strategie (lassen Sie die Superklasse die Arbeit machen!):

- (void)drawRect:(CGRect)rect
{
  [super drawRect:rect];
  [context frameRect:rect]; // research which rect drawing function to use...
}
Kent
quelle
Ich verstehe es nicht. wahrscheinlich, weil ich ungefähr 12 Stunden lang auf den Bildschirm gestarrt habe und zu diesem Zeitpunkt nicht viel bekomme ...
Monte Hurd
Sie unterordnen UILabel, eine Klasse, die bereits Text zeichnet. und der Grund, warum Sie eine Unterklasse erstellen, ist, dass Sie dem Text einen schwarzen Rand hinzufügen möchten. Wenn Sie also die Oberklasse den Text zeichnen lassen, müssen Sie nur den Rand zeichnen, nein?
Kent
Dies ist ein guter Punkt ... obwohl, wenn er einen schwarzen Rand von 1 Pixel hinzufügen möchte, die Unterklasse die Buchstaben wahrscheinlich zu nahe beieinander zeichnen wird! .... ich würde so etwas wie in der ursprünglichen Frage gepostet machen und optimieren (wie in meinem Beitrag angegeben)! N
Nick Cartwright
@nickcartwright: Wenn der Fall auftritt, dass die Superklasse das tut, was Sie sagen, können Sie das CGRect einfügen, bevor Sie [super drawRect] aufrufen. Noch einfacher und sicherer als das Umschreiben der Funktionalität der Oberklasse.
Kent
Und dann hat @kent SO frustriert verlassen. Tolle Antwort, +1.
Dan Rosenstark
1

Ich habe ein Problem mit der Hauptantwort gefunden. Die Textposition ist nicht unbedingt korrekt auf die Subpixelposition zentriert, sodass der Umriss um den Text herum nicht übereinstimmen kann. Ich habe es mit dem folgenden Code behoben, der verwendet 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];
}

Dies setzt voraus, dass Sie textOutlineColorund textOutlineWidthals Eigenschaften definiert haben .

Robotbugs
quelle
0

Hier ist die andere Antwort, um den umrissenen Text auf dem Etikett festzulegen.

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
 }

}

Stellen Sie den umrissenen Text einfach mit der Erweiterungsmethode ein.

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

Es ist auch möglich, UILabel mit der folgenden Logik zu unterordnen:

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

und wenn Sie Text in Storyboard festlegen, dann:

- (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;
}
Dollar2048
quelle
0

Wenn Ihr Ziel so etwas ist:

Geben Sie hier die Bildbeschreibung ein

So habe ich es erreicht: Ich labelhabe meinem aktuellen UILabel eine neue benutzerdefinierte Klasse als Unteransicht hinzugefügt (inspiriert von dieser Antwort) ).

Kopieren Sie es einfach und fügen Sie es in Ihr Projekt ein.

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

VERWENDUNG:

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

es funktioniert auch für a UIButtonmit all seinen animationen:

yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)
Gasper J.
quelle
-3

Warum erstellen Sie in Photoshop keine 1px-Rahmen-UIView, legen dann eine UIView mit dem Bild fest und positionieren es hinter Ihrem UILabel?

Code:

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

Es wird Ihnen die Unterklasse eines Objekts ersparen und es ist ziemlich einfach zu tun.

Ganz zu schweigen davon, ob Sie Animationen erstellen möchten, diese sind in die UIView-Klasse integriert.

Brock Woolf
quelle
Der Text auf dem Etikett ändert sich und ich brauche den Text selbst, um einen schwarzen Umriss von 1 Pixel zu haben. (Der Rest des UILabel-Hintergrunds ist transparent.)
Monte Hurd
Ich dachte, ich hätte verstanden, wie dies dazu führen wird, dass ein Text, den ich in das UILabel einfüge, umrissen wird, aber ich war wahnsinnig müde und jetzt kann ich mich nicht mehr darum kümmern, wie dies erreicht werden kann. Ich werde auf initWithPatternImage nachlesen.
Monte Hurd
-3

Um einen Rand mit abgerundeten Kanten um ein UILabel zu legen, gehe ich wie folgt vor:

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

(Vergessen Sie nicht, QuartzCore / QuartzCore.h einzuschließen.)

Mike
quelle
5
Dies fügt einen großen Rand um das gesamte Etikett, nicht um den Text
Pavel Alexeev