Как сделать так, чтобы UILabel отображал выделенный текст?

Все, что мне нужно, это черная рамка в один пиксель вокруг моего белого текста UILabel.

Я дошел до подкласса UILabel с помощью приведенного ниже кода, который я неуклюже сколотил из нескольких косвенно связанных онлайн-примеров. И это работает, но это очень, очень медленно (кроме симулятора), и я не мог заставить его центрировать текст по вертикали (поэтому я временно жестко закодировал значение y в последней строке). Аааааааааааааааааааааа!

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

У меня просто неприятное ощущение, что я упускаю из виду более простой способ сделать это. Возможно, переопределив "drawTextInRect", но я вообще не могу заставить drawTextInRect подчиняться моей воле, несмотря на то, что я пристально смотрел на него и очень сильно хмурился.


person Monte Hurd    schedule 09.07.2009    source источник
comment
Уточнение - в моем приложении заметна медленность, потому что я анимирую метку, когда ее значение изменяется с небольшим увеличением и уменьшением. Без подкласса это плавно, но с кодом выше анимация метки очень прерывистая. Должен ли я просто использовать UIWebView? Я чувствую себя глупо, потому что на этикетке отображается только одно число ...   -  person Monte Hurd    schedule 09.07.2009
comment
Хорошо, похоже, что проблема с производительностью, с которой я столкнулся, не была связана с кодом структуры, но я все еще не могу заставить ее выровняться по вертикали. pt.y по какой-то причине всегда равен нулю.   -  person Monte Hurd    schedule 10.07.2009
comment
Это очень медленно для таких шрифтов, как Chalkduster.   -  person jjxtra    schedule 08.08.2013


Ответы (15)


Мне удалось это сделать, переопределив 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;

}
person kprevas    schedule 15.09.2009
comment
Я пробовал эту технику, но результаты неутешительны. На своем iPhone 4 я попытался использовать этот код, чтобы нарисовать белый текст с черным контуром. Я получил черные контуры слева и справа от каждой буквы в тексте, но не контуры вверху или внизу. Любые идеи? - person Mike Hershberg; 26.01.2011
comment
извините, я пытаюсь использовать ваш код в своем проекте, но ничего не происходит! см. это изображение: ссылка - person Momi; 18.02.2011
comment
@Momeks: Вы должны создать подкласс UILabel и поместить туда реализацию -drawTextInRect:. - person Jeff Kelley; 23.02.2011
comment
@ Джефф, пожалуйста, помогите мне еще? Точно не знаю Создать подкласс и поставить дратекст. это правильно: @interface CustomLabel : UILabel { } @end - person Momi; 23.02.2011
comment
@Jeff и еще кое-что, как можно использовать drawTextInrect с каким-нибудь выходом uilabel? на другом viewcontroller - person Momi; 23.02.2011
comment
@Momeks: если вы не знаете, как создать подкласс в Objective-C, вам следует погуглить; У Apple есть руководство по языку Objective-C. - person Jeff Kelley; 23.02.2011
comment
@Jeff, я так близок к решению этой проблемы, я был бы признателен, если бы вы ответили на мой последний вопрос в этой теме: ссылка - person Momi; 23.02.2011
comment
Почему бы не использовать CGContextSetTextDrawingMode (c, kCGTextFillStroke) вместо CGContextSetTextDrawingMode (c, kCGTextStroke)? Разве это не сработает с одним вызовом [super drawTextInRect: rect]? - person Ricardo Sanchez-Saez; 13.02.2012
comment
Наверное - я не думаю, что такого существовало, когда писал это 2,5 года назад :) - person kprevas; 15.02.2012
comment
Чтобы сделать это правильно, без графических артефактов, вам действительно нужно сделать немного больше. Я сделал класс, который сделал это, включая мягкие тени, с частотой около 200 строк. Для iOS6 я нашел очень приятное решение, которое позволило мне заменить его несколькими строками. Если кому-то интересно, я могу разместить это в блоге. - person Nuoji; 13.07.2012
comment
@morcutt вот сообщение в блоге: aegik.se/226/outlined-text- with-uilabel - person Nuoji; 16.07.2012
comment
На самом деле вы получите более хорошие результаты, если поменяете местами заливку и обводку рисунка. В противном случае ход может быть немного неправильным ... - person Stefan; 13.07.2013
comment
Это очень медленно для таких шрифтов, как Chalkduster. - person jjxtra; 08.08.2013
comment
Как бы вы это сделали в UITextView? - person bitops; 19.01.2015
comment
Отлично работал у меня как есть на UILabel. Спасибо за ваш вклад! - person Ron B.; 29.05.2019

Более простым решением является использование строки с атрибутами. вот так:

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)

На UITextField вы также можете установить defaultTextAttributes и attributedPlaceholder.

Обратите внимание, что NSStrokeWidthAttributeName в этом случае должно быть отрицательным, т.е. работают только внутренние контуры.

UITextFields с контуром с использованием текстов с атрибутами

person Axel Guilmin    schedule 05.05.2015
comment
Для удобства в Objective-C это будет: label.attributedText = [[NSAttributedString alloc] initWithString: @String attributes: @ {NSStrokeColorAttributeName: [UIColor blackColor], NSForegroundColorAttributeName: [UIColidth whiteColor]; - person Leszek Szary; 04.10.2015
comment
Использование этого мема эффективно передало нюансы этого подхода. - person woody121; 05.04.2017
comment
Swift 4.2: let strokeTextAttributes: [String: Any] = [NSStrokeColorAttributeName: UIColor.black, NSForegroundColorAttributeName: UIColor.white, NSStrokeWidthAttributeName: -4.0,] consoleView.typingAttributes = strokeText - person Teo Sartori; 10.09.2018
comment
Спасибо @TeoSartori, я добавил в свой ответ синтаксис Swift 4.2;) - person Axel Guilmin; 11.09.2018

Прочитав принятый ответ и два исправления к нему, а также ответ Акселя Гильмина, я решил скомпилировать общее решение на Swift, которое мне подходит:

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

Вы можете добавить этот настраиваемый класс UILabel к существующей метке в Interface Builder и изменить толщину границы и ее цвет, добавив определяемые пользователем атрибуты времени выполнения следующим образом:  Настройка Interface Builder

Результат:

большая красная буква G с контуром

person Lachezar    schedule 18.06.2015
comment
Хороший. Я сделаю это IBInspectable :) - person Mário Carvalho; 14.01.2016
comment
@Lachezar, как это можно сделать с текстом UIButton? - person CrazyOne; 09.04.2019

Есть одна проблема с реализацией ответа. Рисование текста с обводкой имеет немного другую ширину символа символа, чем рисование текста без обводки, что может привести к "нецентрированным" результатам. Это можно исправить, добавив невидимую обводку вокруг текста заливки.

Заменять:

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

с участием:

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

Я не уверен на 100%, так ли это на самом деле, потому что я не знаю, имеет ли self.textColor = textColor; тот же эффект, что и [textColor setFill], но это должно сработать.

Раскрытие информации: я разработчик THLabel.

Некоторое время назад я выпустил подкласс UILabel, который позволяет использовать текст и другие эффекты. Вы можете найти его здесь: https://github.com/tobihagemann/THLabel

person tobihagemann    schedule 07.10.2013
comment
только что использовал THLabel, жизнь и так достаточно сложна - person Prat; 04.06.2014
comment
Благодаря THLabel я смог нарисовать контур вокруг текста, добавив всего две строчки кода. Спасибо за решение проблемы, которую я слишком долго пытался решить! - person Alan Kinnaman; 04.09.2016
comment
Я удивлен, что никто не заметил, но встроенная команда обводки текста не создает контур, а вместо этого создает встроенный - т.е. обводка рисуется на внутри букв, а не снаружи. С THLabel мы можем выбрать, чтобы обводка была нарисована снаружи. Прекрасная работа! - person lenooh; 04.11.2018

Это не создаст контура как такового, но создаст тень вокруг текста, и если вы сделаете радиус тени достаточно маленьким, он может напоминать контур.

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

Не знаю, совместим ли он со старыми версиями iOS ..

В любом случае, я надеюсь, что это поможет ...

person Suran    schedule 21.06.2012
comment
Это не однопиксельная рамка вокруг метки. Это просто отбрасывающая тень вниз. - person Tim; 30.01.2014
comment
это неправильное решение. Боже, кто это разметил ?! - person Sam B; 20.06.2014
comment
они сказали довольно четко «очерчено», это тень. не отвечает на вопрос - person hitme; 03.03.2015

Версия класса Swift 4, основанная на ответе 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
    }
}

Его можно полностью реализовать в Интерфейсном Разработчике, установив для пользовательского класса UILabel значение OutlinedText. После этого у вас будет возможность установить ширину и цвет контура на панели «Свойства».

введите описание изображения здесь

person Chris Stillwell    schedule 02.08.2018
comment
Идеально подходит и для Swift 5. - person Antee86; 25.06.2020
comment
Текст отрывается по бокам. - person dub; 28.09.2020

Если вы хотите анимировать что-то сложное, лучший способ - это программно сделать снимок экрана, а вместо этого анимировать!

Чтобы сделать снимок экрана представления, вам понадобится примерно такой код:

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

Где mainContentView - это представление, снимок экрана которого вы хотите сделать. Добавьте viewImage в UIImageView и оживите его.

Надеюсь, это ускорит вашу анимацию !!

N

person Nick Cartwright    schedule 09.07.2009
comment
Это потрясающий трюк, который я могу придумать в нескольких местах для использования и, вероятно, решит проблему с производительностью, но я все еще надеюсь, что есть более простой способ, чем мой дерьмовый подкласс, чтобы заставить текст uilabel отображать контур. А пока я поиграю с вашим примером, спасибо! - person Monte Hurd; 09.07.2009
comment
Я чувствую твою боль. Я не уверен, что вы найдете для этого хороший способ. Я часто пишу кучу кода для эффектов продукта, а затем снимаю их (как показано выше) для плавной анимации! В приведенном выше примере вам нужно включить ‹QuartzCore / QuartzCore.h› в свой код! Удачи! N - person Nick Cartwright; 09.07.2009

Как упоминалось в MuscleRumble, граница принятого ответа немного не по центру. Я смог исправить это, установив ширину штриха на ноль вместо того, чтобы менять цвет на прозрачный.

т.е. замена:

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

с участием:

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

Я бы просто прокомментировал его ответ, но, видимо, я недостаточно "уважаемый".

person ufmike    schedule 18.07.2014

если ВСЕ, что вы хотите, это черная рамка в один пиксель вокруг моего белого текста UILabel,

тогда я действительно думаю, что вы усложняете проблему, чем она есть ... Я не знаю по памяти, какую функцию 'draw rect / frameRect' вы должны использовать, но вам будет легко найти. этот метод просто демонстрирует стратегию (пусть за дело берется суперкласс!):

- (void)drawRect:(CGRect)rect
{
  [super drawRect:rect];
  [context frameRect:rect]; // research which rect drawing function to use...
}
person kent    schedule 09.07.2009
comment
я не понимаю. наверное, потому что я смотрел на экран около 12 часов и пока не понимаю ... - person Monte Hurd; 09.07.2009
comment
вы создаете подкласс UILabel - класса, который уже рисует текст. и причина, по которой вы подклассифицируете, заключается в том, что вы хотите добавить черную рамку вокруг текста. так что если вы позволите суперклассу рисовать текст, тогда все, что вам нужно сделать, это нарисовать границу, не так ли? - person kent; 09.07.2009
comment
Это хороший момент ... хотя, если он хочет добавить черную рамку в 1 пиксель, подкласс, вероятно, будет рисовать буквы слишком близко друг к другу! .... Я бы сделал что-то вроде отправленного в исходный вопрос и оптимизирую (как указано в моем сообщении)! N - person Nick Cartwright; 09.07.2009
comment
@nickcartwright: если случится так, что суперкласс работает так, как вы говорите, вы можете вставить CGRect перед вызовом [super drawRect]. все же проще и безопаснее, чем переписывать функциональность суперкласса. - person kent; 09.07.2009
comment
А затем @kent в отчаянии покинул ТАК. Отличный ответ, +1. - person Dan Rosenstark; 12.07.2010

Если ваша цель примерно такая:

введите описание изображения здесь

Вот как я этого добился: я добавил новый label пользовательского класса в качестве Subview в мой текущий UILabel (вдохновленный этим ответом ).

Просто скопируйте и вставьте его в свой проект, и все готово:

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

ИСПОЛЬЗОВАНИЕ:

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

он также работает для UIButton со всеми его анимациями:

yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)
person Gasper J.    schedule 16.02.2020
comment
эта опция как есть разрывает многострочность - person Gonçalo Gaspar; 03.11.2020

Я нашел проблему с основным ответом. Положение текста не обязательно правильно центрировано относительно положения субпикселя, поэтому контур может не совпадать с текстом. Я исправил это с помощью следующего кода, в котором используется 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];
}

Это предполагает, что вы определили textOutlineColor и textOutlineWidth как свойства.

person Robotbugs    schedule 06.07.2013

Вот еще один ответ, чтобы разместить на этикетке обведенный текст.

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
 }

}

установить выделенный текст, просто используя метод расширения.

lblTitle.setOutLinedText("Enter your email address or username")
person Aman Pathak    schedule 16.04.2019

также возможно создать подкласс UILabel со следующей логикой:

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

и если вы установите текст в раскадровке, тогда:

- (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;
}
person dollar2048    schedule 12.05.2019

Почему бы вам не создать границу UIView в 1 пиксель в Photoshop, затем установить UIView с изображением и расположить его позади вашего UILabel?

Код:

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

Это избавит вас от подкласса объекта, и это довольно просто сделать.

Не говоря уже о том, что если вы хотите делать анимацию, она встроена в класс UIView.

person Brock Woolf    schedule 09.07.2009
comment
Текст на этикетке изменится, и мне нужно, чтобы сам текст имел черный контур в 1 пиксель. (Остальной фон UILabel прозрачен.) - person Monte Hurd; 09.07.2009
comment
Я думал, что понял, как это приведет к выделению любого текста, который я помещаю в UILabel, но я безумно устал, и теперь я не могу понять, как это можно сделать. Я собираюсь прочитать об initWithPatternImage. - person Monte Hurd; 10.07.2009

Чтобы поместить границу с закругленными краями вокруг UILabel, я делаю следующее:

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

(не забудьте включить QuartzCore / QuartzCore.h)

person mike    schedule 25.08.2010
comment
Это добавляет большую рамку вокруг всей этикетки, а не вокруг текста. - person Pavel Alexeev; 07.12.2010