Как сделать так, чтобы UILabel отображал выделенный текст? - PullRequest
98 голосов
/ 09 июля 2009

Все, что я хочу, это черная граница в один пиксель вокруг моего белого текста 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 согнуться под мою волю, несмотря на то, что пристально смотрю на него и хмуро нахмуриваюсь.

Ответы [ 14 ]

162 голосов
/ 15 сентября 2009

Я смог сделать это, переопределив 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;

}
100 голосов
/ 05 мая 2015

Более простое решение - использовать Attributed String примерно так:

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 with an outline using attributed texts

24 голосов
/ 18 июня 2015

Прочитав принятый ответ и два исправления к нему, а также ответ Акселя Гильмина, я решил составить общее решение в 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 setup

Результат:

big red G with outline

8 голосов
/ 07 октября 2013

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

Заменить:

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

6 голосов
/ 21 июня 2012

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

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

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

4 голосов
/ 09 июля 2009

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

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

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

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

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

N

3 голосов
/ 18 июля 2014

Как упоминалось 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];

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

2 голосов
/ 02 августа 2018

Версия класса 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. После этого вы сможете установить ширину и цвет контура на панели свойств.

enter image description here

2 голосов
/ 09 июля 2009

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

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

- (void)drawRect:(CGRect)rect
{
  [super drawRect:rect];
  [context frameRect:rect]; // research which rect drawing function to use...
}
1 голос
/ 06 июля 2013

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

...