UILabel'in anahatlı metni göstermesini nasıl sağlayabilirim?


108

Tek istediğim beyaz UILabel metnimin etrafındaki bir piksellik siyah kenarlık.

UILabel'i aşağıdaki kodla alt sınıfa ayırmaya kadar gittim, bunu birkaç teğetsel olarak ilgili çevrimiçi örnekten beceriksizce bir araya getirdim. Ve çalışıyor ama çok, çok yavaş (simülatör dışında) ve metni dikey olarak ortalayamıyorum (bu yüzden son satırdaki y değerini geçici olarak sabit kodladım). 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]);
}

Bunu yapmanın daha basit bir yolunu gözden kaçırdığıma dair dırdırcı bir his var. Belki de "drawTextInRect" i geçersiz kılarak, ama buna dikkatle bakıp gerçekten sert bir şekilde kaşlarını çatmasına rağmen drawTextInRect'in irademe boyun eğmesini sağlayamıyorum.


Açıklama - Yavaşlık uygulamamda belirgindir çünkü etiketi küçük bir büyüme ve küçülmeyle değiştiğinde etiketi canlandırıyorum. Alt sınıfa ayırmadan pürüzsüzdür, ancak kodun üstündeki kodla birlikte, animasyon çok dalgalıdır. Bir UIWebView kullanmalı mıyım? Etiket sadece tek bir numara gösterdiğinden bunu yapmak aptalca hissediyorum ...
Monte Hurd

Tamam, yaşadığım performans sorunu ana hat koduyla ilgisiz gibi görünüyor, ancak yine de dikey olarak hizalayamıyorum. pt.y bazı nedenlerden dolayı her zaman sıfırdır.
Monte Hurd

Bu, Chalkduster gibi yazı tipleri için çok yavaş
jjxtra

Yanıtlar:


164

Bunu drawTextInRect'i geçersiz kılarak yapabildim:

- (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
Bu tekniği denedim ama sonuçlar tatmin edici değil. İPhone 4'ümde bu kodu siyah çerçeveli beyaz metin çizmek için kullanmayı denedim. Elde ettiğim şey metindeki her harfin sol ve sağ kenarlarında siyah anahatlardı, ancak üstte veya altta anahatlar yoktu. Herhangi bir fikir?
Mike Hershberg

üzgünüm kodunuzu projemde kullanmaya çalışıyorum ama hiçbir şey olmuyor! bu resme bakın: bağlantı
Momi

@Momeks: Altsınıflandırmalı UILabelve -drawTextInRect:uygulamayı oraya koymalısınız .
Jeff Kelley

1
@Momeks: Objective-C'de nasıl alt sınıf yapılacağını bilmiyorsanız, biraz Googling yapmalısınız; Apple'ın Objective-C dili için bir kılavuzu var.
Jeff Kelley

1
Muhtemelen - bunu 2,5 yıl önce yazdığımda var olduğunu sanmıyorum :)
kprevas

114

Daha basit bir çözüm, aşağıdaki gibi bir İlişkilendirilmiş Dize kullanmaktır :

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)

Bir Açık UITextFieldayarlayabileceğiniz defaultTextAttributesve attributedPlaceholdersıra.

Bu durumda NSStrokeWidthAttributeName negatif olması gerektiğine dikkat edin , yani sadece iç hatlar işe yarar.

UITextFields atıfta bulunulan metinleri kullanan bir taslak ile


17
Objective-C'de kolaylık sağlamak için, bu şöyle olacaktır: label.attributedText = [[NSAttributedString ayırma] initWithString: @ "String" öznitelikleri: @ {NSStrokeColorAttributeName: [UIColor blackColor], NSForegroundColorAttributeName: [UIColor whiteColor.0} ;
Leszek Szary


Swift 4.2: strokeTextAttributes let: [Dize: Herhangi] = [NSStrokeColorAttributeName: UIColor.black, NSForegroundColorAttributeName: UIColor.white, NSStrokeWidthAttributeName: -4.0,] consoleView.typingAttributes = strokeTextAttributes
Teo Sartori

Teşekkür ederim @TeoSartori, cevabıma Swift 4.2 sözdizimini ekledim;)
Axel Guilmin

25

Kabul edilen yanıtı ve ona yönelik iki düzeltmeyi ve Axel Guilmin'den gelen yanıtı okuduktan sonra, Swift'de bana uyan genel bir çözüm derlemeye karar verdim:

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

Bu özel UILabel sınıfını, Arayüz Oluşturucu'daki mevcut bir etikete ekleyebilir ve aşağıdaki gibi Kullanıcı Tanımlı Çalışma Zamanı Nitelikleri ekleyerek kenarlığın kalınlığını ve rengini değiştirebilirsiniz: Arayüz Oluşturucu kurulumu

Sonuç:

anahat ile büyük kırmızı G


1
Güzel. Bununla IBInspectable yapacağım :)
Mário Carvalho

@Lachezar Bu bir UIButton metni ile nasıl yapılabilir?
CrazyOne

8

Cevabın uygulanmasında bir sorun var. Konturlu bir metin çizmek, kontursuz bir metni çizmekten biraz farklı bir karakter glif genişliğine sahiptir ve bu da "ortalanmamış" sonuçlar üretebilir. Doldurma metninin etrafına görünmez bir kontur eklenerek düzeltilebilir.

Değiştirin:

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

ile:

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

Gerçek anlaşma buysa% 100 emin değilim, çünkü bununla self.textColor = textColor;aynı etkiye sahip olup olmadığını bilmiyorum [textColor setFill]ama işe yaramalı.

Açıklama: THLabel'in geliştiricisiyim.

Bir süre önce, metinde ve diğer efektlerde ana hatlara izin veren bir UILabel alt sınıfı yayınladım. Burada bulabilirsiniz: https://github.com/tobihagemann/THLabel


4
THLabel'i az önce kullandım, hayat zaten yeterince karmaşık
Prat

1
THLabel sayesinde, sadece iki satır kod ekleyerek metnin etrafına bir taslak çizebildim. Çözmek için çok uzun süredir uğraştığım bir soruna çözüm sağladığınız için teşekkürler!
Alan Kinnaman

Kimsenin fark etmediğine şaşırdım, ancak yerleşik metin kontur komutu bir taslak oluşturmaz , bunun yerine bir " satır içi " oluşturur - yani kontur harflerin dışına değil, iç tarafına çizilir. THLabel ile vuruşun gerçekten dışarıdan çizilmesini seçebiliriz. İyi iş!
lenooh

5

Bu kendi başına bir taslak oluşturmaz, ancak metnin etrafına bir gölge koyar ve eğer gölge yarıçapını yeterince küçük yaparsanız, bir taslağa benzeyebilir.

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

İOS'un eski sürümleriyle uyumlu olup olmadığını bilmiyorum ..

Her neyse, umarım yardımcı olur ...


9
Bu, etiketin etrafındaki tek piksellik bir kenarlık değildir. Bu, aşağıya doğru basit bir gölge.
Tim

1
bu yanlış çözüm. İyi tanrı kim işaretledi ?!
Sam B

oldukça net bir şekilde 'ana hatları çizilmiş' dediler, bu bir gölge. soru cevaplamaz
hitme

5

Kprevas'ın cevabına dayalı bir Swift 4 sınıfı versiyonu

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

Tamamen Arayüz Oluşturucu'da UILabel'in özel sınıfını OutlinedText olarak ayarlayarak uygulanabilir. Ardından, Özellikler bölmesinden anahattın genişliğini ve rengini ayarlama olanağına sahip olacaksınız.

görüntü açıklamasını buraya girin


Swift 5 için de mükemmel.
Antee86

4

Karmaşık bir şeyi canlandırmak istiyorsanız, bunun en iyi yolu programlı bir şekilde ekran görüntüsünü almak ve bunun yerine canlandırmaktır!

Bir görünümün ekran görüntüsünü almak için şuna benzer bir koda ihtiyacınız olacak:

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

MainContentView, ekran görüntüsünü almak istediğiniz görünümdür. ViewImage'ı bir UIImageView'a ekleyin ve onu canlandırın.

Umarım bu animasyonunuzu hızlandırır !!

N


Bu, kullanabileceğim birkaç yer düşünebileceğim harika bir numara ve muhtemelen performans sorununu çözüyor, ancak yine de uilabel metninin bir taslağı göstermesini sağlamanın berbat alt sınıfımdan daha basit bir yolu olduğunu umuyorum. Bu arada senin örneğinle oynayacağım teşekkürler!
Monte Hurd

Acını hissediyorum. Bunu yapmanın iyi bir yolunu bulacağından emin değilim. Sık sık ürün efektlerine kod yığınları yazıyorum, ardından düzgün bir animasyon için (yukarıdaki gibi) anlık görüntülerini alıyorum! Yukarıdaki örnek için kodunuza <QuartzCore / QuartzCore.h> eklemeniz gerekir! İyi şanslar! N
Nick Cartwright

4

As MuscleRumble bahsedildiği, kabul cevabım sınır merkezinde kapalı biraz. Rengi temizlemek için değiştirmek yerine kontur genişliğini sıfıra ayarlayarak bunu düzeltebildim.

yani, değiştirme:

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

ile:

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

Cevabı hakkında sadece yorum yapardım ama görünüşe göre yeterince "saygın" değilim.


2

TÜM istediğiniz beyaz UILabel metnimin etrafında bir piksellik siyah bir kenarlıksa,

o zaman sorunu olduğundan daha zor hale getirdiğinizi düşünüyorum ... Hafızamdan hangi 'draw rect / frameRect' fonksiyonunu kullanmanız gerektiğini bilmiyorum, ancak bulmanız kolay olacaktır. bu yöntem sadece stratejiyi gösterir (bırakın işi üst sınıf yapsın!):

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

anlamadım. i 12 hakkında saatlerce ekrana bakarken oldum ve ben ... Bu noktada çok alamadım muhtemelen çünkü
Monte Hurd

zaten metin çizen bir sınıf olan UILabel'i alt sınıflara alıyorsunuz. ve alt sınıflandırma yapmanızın nedeni, metnin etrafına siyah bir kenarlık eklemek istemenizdir. yani üst sınıfın metni çizmesine izin verirseniz, yapmanız gereken tek şey sınırı çizmektir, değil mi?
kent

Bu iyi bir nokta ... Bununla birlikte, eğer 1 piksellik bir siyah kenarlık eklemek isterse, alt sınıf muhtemelen harfleri birbirine çok yakın çizecektir! .... Orijinal soruda yayınlanmış gibi bir şey yaparım ve optimize ederim (yazımda belirtildiği gibi)! N
Nick Cartwright

@nickcartwright: Eğer süper sınıf sizin söylediğiniz gibi davranıyorsa, CGRect'i [super drawRect] 'i çağırmadan önce ekleyebilirsiniz. üst sınıfın işlevselliğini yeniden yazmaktan daha kolay ve daha güvenlidir.
kent

Ve sonra @kent hayal kırıklığı içinde SO'dan ayrıldı. Harika yanıt, +1.
Dan Rosenstark

1

Ana cevapla ilgili bir sorun buldum. Metin konumunun, alt piksel konumuna doğru şekilde ortalanması gerekmez, böylece anahat metnin etrafında uyumsuz olabilir. Bunu kullanan aşağıdaki kodu kullanarak düzelttim 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];
}

Bu, tanımladığınızı textOutlineColorve textOutlineWidthözellikler olarak varsayar .


0

Etikette özetlenen metni koymanın başka bir cevabı da burada.

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
 }

}

özetlenen metni yalnızca uzantı yöntemini kullanarak ayarlayın.

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

0

Aşağıdaki mantıkla UILabel'i alt sınıflara ayırmak da mümkündür:

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

ve Storyboard'da metin ayarlarsanız:

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

Amacınız böyle bir şeyse:

görüntü açıklamasını buraya girin

İşte bunu nasıl başardım: labelMevcut UILabel'ime bir Alt Görünüm olarak yeni bir özel sınıf ekledim (bu cevaptan esinlenerek ).

Sadece kopyalayıp projenize yapıştırın ve gitmekte fayda var:

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

KULLANIM:

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

ayrıca UIButtontüm animasyonlarıyla a için de çalışır :

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

-3

Neden Photoshop'ta 1 piksellik bir UIView oluşturmuyorsunuz, ardından görüntüyle bir UIView ayarlayıp UILabel'inizin arkasına yerleştirmiyorsunuz?

Kod:

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

Sizi bir nesnenin alt sınıflarından kurtaracak ve yapılması oldukça basit.

Animasyon yapmak isteyip istemediğinizden bahsetmiyorum bile, UIView sınıfına dahil edilmiştir.


Etiketteki metin değişir ve metnin kendisinin 1 piksel siyah dış hatlara sahip olmasına ihtiyacım var. (UILabel'in arka planının geri kalanı şeffaftır.)
Monte Hurd

Bunun UILabel'e yerleştirdiğim herhangi bir metnin ana hatlarının çizilmesine nasıl yol açacağını anladığımı sanıyordum, ama çılgınca yorgundum ve şimdi bunu nasıl başarabileceğime karar veremiyorum. Ben initWithPatternImage'da okuyacağım.
Monte Hurd

-3

Bir UILabel'in etrafına yuvarlatılmış kenarlı bir kenarlık koymak için aşağıdakileri yapıyorum:

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

(QuartzCore / QuartzCore.h'yi eklemeyi unutmayın)


5
Bu, metnin etrafına değil, tüm etiketin etrafına büyük bir sınır ekler
Pavel Alexeev
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.