Bagaimana cara membuat UILabel menampilkan teks yang diuraikan?

108

Yang saya inginkan hanyalah batas hitam satu piksel di sekitar teks UILabel putih saya.

Saya sampai pada subclassing UILabel dengan kode di bawah ini, yang dengan kikuk saya susun dari beberapa contoh online yang berhubungan secara tangensial. Dan itu berhasil tetapi sangat, sangat lambat (kecuali di simulator) dan saya juga tidak bisa membuatnya memusatkan teks secara vertikal (jadi saya melakukan hardcode nilai y pada baris terakhir untuk sementara). 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]);
}

Saya hanya memiliki perasaan yang mengganggu bahwa saya mengabaikan cara yang lebih sederhana untuk melakukan ini. Mungkin dengan menimpa "drawTextInRect", tapi sepertinya saya tidak bisa membuat drawTextInRect tunduk pada keinginan saya sama sekali meskipun menatapnya dengan saksama dan mengerutkan kening sangat keras.

Monte Hurd
sumber
Klarifikasi - kelambatan terlihat di aplikasi saya karena saya menganimasikan label ketika nilainya berubah dengan sedikit tumbuh dan menyusut. Tanpa subclass, itu mulus, tetapi dengan kode di atas animasi label sangat berombak. Haruskah saya menggunakan UIWebView? Saya merasa konyol melakukannya karena label hanya menampilkan satu nomor ...
Monte Hurd
Oke, sepertinya masalah kinerja yang saya alami tidak terkait dengan kode garis besar, tetapi saya masih tidak bisa membuatnya sejajar secara vertikal. pt.y selalu nol untuk beberapa alasan.
Monte Hurd
Ini sangat lambat untuk font seperti
Chalkduster

Jawaban:

164

Saya bisa melakukannya dengan mengganti 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;

}
kprevas.dll
sumber
3
Teknik ini sudah saya coba tapi hasilnya kurang memuaskan. Di iPhone 4 saya, saya mencoba menggunakan kode ini untuk menggambar teks putih dengan garis hitam. Apa yang saya dapatkan adalah garis hitam di tepi kiri dan kanan setiap huruf dalam teks tetapi tidak garis besar di bagian atas atau bawah. Ada ide?
Mike Hershberg
maaf saya mencoba menggunakan kode Anda pada proyek saya tetapi tidak ada yang terjadi! lihat foto ini: tautan
Momi
@Momeks: Anda harus membuat subclass UILabeldan -drawTextInRect:menerapkan implementasinya di sana.
Jeff Kelley
1
@Momeks: Jika Anda tidak tahu cara membuat subclass di Objective-C, Anda harus melakukan Googling; Apple memiliki panduan untuk bahasa Objective-C.
Jeff Kelley
1
Mungkin - Saya tidak berpikir itu ada ketika saya menulis ini 2,5 tahun yang lalu :)
kprevas
114

Solusi yang lebih sederhana adalah dengan menggunakan Attributed String seperti ini:

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

Di a UITextFieldAnda dapat mengatur defaultTextAttributesdan attributedPlaceholderjuga.

Perhatikan bahwa dalam kasus ini NSStrokeWidthAttributeName harus negatif , yaitu hanya garis dalam yang bekerja.

UITextFields dengan kerangka menggunakan teks yang diatribusikan

Axel Guilmin
sumber
17
Untuk kenyamanan dalam Objective-C ini akan menjadi: label.attributedText = [[NSAttributedString alokasi] initWithString: @ "String" atribut: @ {NSStrokeColorAttributeName: [UIColor blackColor], NSForegroundColorAttributeName: [UIColor whiteColor], NSStrokeWidthAttributeName: [UIColor blackColor], NSForegroundColorAttributeName: [UIColor whiteColor], NSStrokeWidthAttributeName: @ -1.0} ;
Leszek Szary
8
Penggunaan meme ini secara efektif mengkomunikasikan nuansa pendekatan ini
woody121
Swift 4.2: biarkan strokeTextAttributes: [String: Any] = [NSStrokeColorAttributeName: UIColor.black, NSForegroundColorAttributeName: UIColor.white, NSStrokeWidthAttributeName: -4.0,] consoleView.typingAttributes = strokeTextAttributes
Teo Sartori
Terima kasih @TeoSartori, saya menambahkan sintaks Swift 4.2 dalam jawaban saya;)
Axel Guilmin
25

Setelah membaca jawaban yang diterima dan dua koreksi dan jawaban dari Axel Guilmin, saya memutuskan untuk mengumpulkan solusi keseluruhan di Swift, yang cocok untuk saya:

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

Anda dapat menambahkan kelas UILabel kustom ini ke label yang ada di Interface Builder dan mengubah ketebalan batas dan warnanya dengan menambahkan Atribut Waktu Proses yang Ditentukan Pengguna seperti ini: Pengaturan Interface Builder

Hasil:

G merah besar dengan garis luar

Lachezar
sumber
1
Bagus. Saya akan membuat IBInspectable dengan ini :)
Mário Carvalho
@Lachezar bagaimana ini bisa dilakukan dengan teks UIButton?
CrazyOne
8

Ada satu masalah dengan implementasi jawabannya. Menggambar teks dengan guratan memiliki lebar mesin terbang karakter yang sedikit berbeda daripada menggambar teks tanpa guratan, yang bisa menghasilkan hasil "tidak terpusat". Itu bisa diperbaiki dengan menambahkan goresan tak terlihat di sekitar teks isian.

Menggantikan:

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

dengan:

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

Saya tidak 100% yakin, apakah itu real deal, karena saya tidak tahu apakah self.textColor = textColor;memiliki efek yang sama [textColor setFill], tetapi itu akan berhasil.

Pengungkapan: Saya pengembang THLabel.

Saya telah merilis subclass UILabel beberapa waktu lalu, yang memungkinkan garis besar dalam teks dan efek lainnya. Anda dapat menemukannya di sini: https://github.com/tobihagemann/THLabel

tobihagemann
sumber
4
baru saja menggunakan THLabel, hidup ini cukup rumit
Prat
1
Berkat THLabel, saya dapat menggambar garis besar di sekitar teks hanya dengan menambahkan dua baris kode. Terima kasih telah memberikan solusi untuk masalah yang telah saya coba pecahkan terlalu lama!
Alan Kinnaman
Saya terkejut tidak ada yang memperhatikan, tetapi perintah goresan teks bawaan, tidak membuat garis besar , melainkan menciptakan " sebaris " - yaitu guratan digambar di bagian dalam huruf, bukan di luar. Dengan THLabel kita dapat memilih agar goresan benar-benar digambar di luar. Kerja bagus!
lenooh
5

Ini tidak akan membuat garis luar saja, tetapi itu akan membuat bayangan di sekitar teks, dan jika Anda membuat radius bayangan cukup kecil itu bisa menyerupai garis besar.

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

Saya tidak tahu apakah itu kompatibel dengan versi iOS yang lebih lama ..

Bagaimanapun, saya harap ini membantu ...

Suran
sumber
9
Ini bukan batas satu piksel di sekitar label. Ini adalah bayangan drop sederhana ke bawah.
Tim
1
ini adalah solusi yang salah. Ya Tuhan yang menandainya ?!
Sam B
kata mereka cukup jelas 'diuraikan', ini adalah bayangan. tidak menjawab pertanyaan
hitme
5

Versi kelas Swift 4 berdasarkan jawaban oleh 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
    }
}

Ini dapat diimplementasikan sepenuhnya di Interface Builder dengan menyetel kelas khusus UILabel ke OutlinedText. Anda kemudian akan memiliki kemampuan untuk mengatur lebar garis dan warna dari panel Properties.

masukkan deskripsi gambar di sini

Chris Stillwell
sumber
Sempurna untuk Swift 5 juga.
Antee86
4

Jika Anda ingin menganimasikan sesuatu yang rumit, cara terbaik adalah dengan mengambil tangkapan layar secara terprogram dan menganimasikannya!

Untuk mengambil tangkapan layar tampilan, Anda memerlukan kode seperti ini:

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

Di mana mainContentView adalah tampilan yang ingin Anda ambil tangkapan layarnya. Tambahkan viewImage ke UIImageView dan animasikan itu.

Semoga mempercepat animasi Anda !!

N

Nick Cartwright
sumber
Itu adalah trik luar biasa yang dapat saya pikirkan tentang beberapa tempat untuk digunakan, dan mungkin memecahkan masalah kinerja, tetapi saya masih berharap bahwa ada cara yang lebih sederhana daripada subkelas saya yang jelek untuk membuat teks uilabel menunjukkan garis besar. Sementara itu, saya akan bermain dengan teladan Anda, terima kasih!
Monte Hurd
Saya merasakan sakit Anda. Saya tidak yakin Anda akan menemukan cara yang baik untuk melakukan itu. Saya sering menulis rim kode untuk efek produk kemudian memotretnya (seperti di atas) untuk animasi yang mulus! Untuk contoh di atas, Anda perlu menyertakan <QuartzCore / QuartzCore.h> dalam kode Anda! Semoga berhasil! N
Nick Cartwright
4

Seperti yang disebutkan MuscleRumble , batas jawaban yang diterima agak jauh dari tengah. Saya bisa memperbaiki ini dengan mengatur lebar goresan ke nol alih-alih mengubah warna menjadi jelas.

yaitu mengganti:

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

dengan:

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

Saya hanya akan mengomentari jawabannya tetapi tampaknya saya tidak cukup "memiliki reputasi".

ufmike
sumber
2

jika SEMUA yang Anda inginkan adalah batas hitam satu piksel di sekitar teks UILabel putih saya,

maka saya pikir Anda membuat masalah lebih sulit daripada itu ... Saya tidak tahu dengan memori mana fungsi 'draw rect / frameRect' yang harus Anda gunakan, tetapi akan mudah bagi Anda untuk menemukannya. metode ini hanya mendemonstrasikan strateginya (biarkan superclass yang bekerja!):

- (void)drawRect:(CGRect)rect
{
  [super drawRect:rect];
  [context frameRect:rect]; // research which rect drawing function to use...
}
kent
sumber
saya tidak mengerti. mungkin karena saya telah menatap layar selama sekitar 12 jam dan saya tidak mendapatkan banyak hal pada saat ini ...
Monte Hurd
Anda subclass UILabel, kelas yang sudah menggambar teks. dan alasan Anda membuat subclass adalah karena Anda ingin menambahkan batas hitam di sekitar teks. jadi jika Anda membiarkan superclass menggambar teks, yang harus Anda lakukan adalah menggambar batasnya, bukan?
kent
Ini adalah poin yang bagus ... meskipun, jika dia ingin menambahkan batas hitam 1 piksel, subkelas mungkin akan menggambar huruf terlalu berdekatan! .... Saya akan melakukan sesuatu seperti yang diposting di pertanyaan asli dan mengoptimalkan (seperti yang dinyatakan dalam posting saya)! N
Nick Cartwright
@nickcartwright: jika terjadi bahwa superclass melakukan apa yang Anda katakan, Anda dapat menyisipkan CGRect sebelum memanggil [super drawRect]. masih lebih mudah dan lebih aman daripada menulis ulang fungsionalitas superclass.
kent
Dan kemudian @kent meninggalkan SO dengan frustrasi. Jawaban bagus, +1.
Dan Rosenstark
1

Saya menemukan masalah dengan jawaban utama. Posisi teks tidak selalu tepat berada di tengah ke lokasi sub-piksel, sehingga garis tepi dapat menjadi tidak cocok di sekitar teks. Saya memperbaikinya menggunakan kode berikut, yang menggunakan 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];
}

Ini mengasumsikan bahwa Anda mendefinisikan textOutlineColordan textOutlineWidthsebagai properti.

Robotbugs
sumber
0

Berikut adalah jawaban lain untuk menguraikan teks yang diuraikan pada label.

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
 }

}

setel teks yang diuraikan hanya dengan menggunakan metode ekstensi.

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

juga dimungkinkan untuk membuat subclass UILabel dengan logika berikut:

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

dan jika Anda mengatur teks di Storyboard maka:

- (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;
}
dolar2048
sumber
0

Jika tujuan Anda adalah seperti ini:

masukkan deskripsi gambar di sini

Berikut adalah cara saya mencapainya: Saya menambahkan kelas baru labelkhusus sebagai Subview ke UILabel saya saat ini (terinspirasi oleh jawaban ini ).

Cukup salin & tempel ke proyek Anda dan Anda siap melakukannya:

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

PEMAKAIAN:

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

itu juga berfungsi untuk a UIButtondengan semua animasinya:

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

Mengapa Anda tidak membuat UIView perbatasan 1px di Photoshop, lalu mengatur UIView dengan gambar, dan memposisikannya di belakang UILabel Anda?

Kode:

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

Ini akan menyelamatkan Anda dari subclass objek dan itu cukup mudah dilakukan.

Belum lagi jika Anda ingin membuat animasi, itu dibangun ke dalam kelas UIView.

Brock Woolf
sumber
Teks pada label berubah dan saya ingin teks itu sendiri memiliki garis luar hitam 1 piksel. (Latar belakang UILabel lainnya transparan.)
Monte Hurd
Saya pikir saya mengerti bagaimana ini akan menyebabkan teks apa pun yang saya tempatkan di UILabel digarisbawahi, tetapi saya sangat lelah dan sekarang saya sepertinya tidak bisa memikirkan bagaimana itu bisa mencapai itu. Saya akan membaca di initWithPatternImage.
Monte Hurd
-3

Untuk meletakkan perbatasan dengan tepi bulat di sekitar UILabel saya melakukan hal berikut:

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

(jangan lupa untuk menyertakan QuartzCore / QuartzCore.h)

mike
sumber
5
Ini menambahkan batas besar di sekeliling label, bukan di sekitar teks
Pavel Alexeev