如何使UILabel显示轮廓文本?


108

我只需要在白色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]);
}

我只是有点feeling愧,觉得自己正在忽略一种更简单的方法来执行此操作。也许是通过覆盖“ drawTextInRect”,但是尽管专心地盯着它,而且皱着眉头真的很努力,但我似乎根本无法使drawTextInRect屈服于我的意志。


澄清-在我的应用中速度很慢,因为当标签的值随着轻微的增长和收缩而变化时,我正在为其设置动画。没有子类,它很平滑,但是带有标签动画的代码很不稳定。我应该只使用UIWebView吗?我觉得这样做很愚蠢,因为标签只显示一个数字...
Monte Hurd

好的,看起来我遇到的性能问题与大纲代码无关,但是我似乎仍然无法使其垂直对齐。出于某些原因,pt.y始终为零。
蒙德·赫德2009年

对于像Chalkduster
jjxtra

Answers:


164

我能够通过重写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;

}

3
我尝试了这种技术,但结果并不令人满意。在我的iPhone 4上,我尝试使用此代码绘制带有黑色轮廓的白色文本。我得到的是文本中每个字母左右两边的黑色轮廓,但顶部或底部没有轮廓。有任何想法吗?
Mike Hershberg

抱歉,我正在我的项目上尝试使用您的代码,但没有任何反应!看到这张照片:链接
Momi

@Momeks:您必须将其子类化UILabel,并将-drawTextInRect:实现放在那里。
Jeff Kelley

1
@Momeks:如果您不知道如何在Objective-C中创建子类,则应该执行Google谷歌搜索;Apple提供了Objective-C语言指南。
Jeff Kelley

1
可能-我
kprevas 2012年

114

一个更简单的解决方案是使用属性字符串,如下所示:

斯威夫特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您还可以设置defaultTextAttributesattributedPlaceholder

注意,在这种情况下,NSStrokeWidthAttributeName 必须为负,即仅内部轮廓起作用。

具有使用属性文本的轮廓的UITextFields


17
为了方便在Objective-C中使用,它是:label.attributedText = [[NSAttributedString alloc] initWithString:@“ String”属性:@ {NSStrokeColorAttributeName:[UIColor blackColor],NSForegroundColorAttributeName:[UIColor whiteColor],NSStrokeWidthAttributeName:@-1.0}] ;
Leszek Szary

8
这种模因的使用有效地传达了这种方法的细微差别
woody121 '17

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

谢谢@TeoSartori,我在答案中添加了Swift 4.2语法;)
Axel Guilmin

25

在阅读了接受的答案以及对它的两个更正以及Axel Guilmin的答案之后,我决定在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中的现有标签,并通过添加用户定义的运行时属性来更改边框的厚度及其颜色,如下所示: 界面生成器设置

结果:

大红色G轮廓


1
真好 我会让这个:)的IBInspectable
马里奥·卡瓦略

@Lachezar如何用UIButton的文本完成此操作?
CrazyOne

8

答案的实现存在一个问题。绘制带有笔划的文本的字符字形宽度与绘制不带有笔划的文本略有不同,这会产生“未居中”的结果。可以通过在填充文本周围添加不​​可见的笔触来解决此问题。

更换:

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


4
刚刚使用过THLabel,生活就已经很复杂了
Prat

1
感谢THLabel,我仅需添加两行代码就可以在文本周围绘制轮廓。感谢您提供解决问题的方法,我花了很长时间尝试解决!
艾伦·金纳曼

令我惊讶的是,没有人注意到,但是内置的文本笔触命令不会创建轮廓,而是会创建“ 内联 ”-即笔划绘制在字母的内部,而不是外部。使用THLabel,我们可以选择在外部实际绘制笔触。很好!
lenooh

5

这本身不会创建轮廓,但是会在文本周围放置阴影,如果将阴影半径设置得足够小,它可能类似于轮廓。

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兼容。

无论如何,我希望它能对您有所帮助...


9
标签周围不是一个像素边框。这是简单的下拉阴影。
2014年

1
这是错误的解决方案。标记它的好上帝?
山姆B

他们说的很清楚“概述”,这是一个阴影。没有回答问题
Hitme 2015年

5

基于kprevas答案的 Swift 4类版本

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,可以完全在Interface Builder中实现它。然后,您可以从“属性”窗格中设置轮廓的宽度和颜色。

在此处输入图片说明


同样适用于Swift 5。
Antee86

4

如果要制作复杂的动画,最好的方法是以编程方式对其截图进行动画处理!

要截取视图的屏幕截图,您将需要如下代码:

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

其中mainContentView是您要为其截屏的视图。将viewImage添加到UIImageView并对其进行动画处理。

希望可以加快动画的速度!!

ñ


这是一个很棒的技巧,我可以考虑在几个地方使用,并且可能解决了性能问题,但是我仍然希望,有一种比我cr脚的子类更简单的方法来使uilabel文本显示轮廓。同时,我要和你一起玩,谢谢!
蒙德·赫德,2009年

我感到你很痛苦。我不确定您会找到一个好的方法。我经常为产品效果编写大量代码,然后将它们快照(如上)以使动画流畅!对于上面的示例,您需要在代码中包含<QuartzCore / QuartzCore.h>!祝你好运!N
尼克·卡特赖特

4

正如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

如果您要的是我的白色UILabel文字周围有一个像素的黑色边框,

那么我确实认为您正在使问题变得更加棘手...我不知道内存应该使用哪个“ draw rect / frameRect”函数,但您会很容易找到它。此方法仅演示了策略(让超类完成工作!):

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

我不明白。可能是因为我一直盯着屏幕看了12个小时,而此时我却收视不多……
蒙德·赫德2009年

您在继承UILabel,这是一个已经绘制文本的类。进行子类化的原因是要在文本周围添加黑色边框。因此,如果让超类绘制文本,那么您要做的就是绘制边框,不是吗?
肯特,2009年

这是个好点……尽管,如果他想添加一个1像素的黑色边框,则子类可能会将字母拉得太近!....我会做类似原始问题中的内容并进行优化(如我的文章中所述)!N
尼克·卡特赖特2009年

@nickcartwright:如果发生超类如您所说的情况,则可以在调用[super drawRect]之前插入CGRect。比重写超类的功能还容易和安全。
肯特,2009年

然后@kent感到沮丧。好的答案,+ 1。
Dan Rosenstark 2010年

1

我发现主要答案有问题。文本位置不一定正确地居中于子像素位置,因此轮廓可能在文本周围不匹配。我使用以下代码修复了该问题,该代码使用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为属性。


0

这是在标签上设置轮廓文字的另一个答案。

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

0

也可以使用以下逻辑将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;
}

0

如果您的目标是这样的:

在此处输入图片说明

这是我如何实现的:我向label当前的UILabel 添加了一个新的自定义类作为Subview(受此答案启发)。

只需将其复制并粘贴到您的项目中,您就可以开始了:

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)

-3

为什么不在Photoshop中创建1px边框的UIView,然后设置带有图像的UIView,并将其放置在UILabel的后面?

码:

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

这样可以节省您对对象的子类化,并且操作非常简单。

更不用说是否要制作动画了,它内置在UIView类中。


标签上的文本会更改,我需要文本本身具有1像素的黑色轮廓。(UILabel的其余背景是透明的。)
Monte Hurd

我以为我知道这会导致我放置在UILabel中的任何文本都被勾勒出来,但是我非常烦躁,现在我似乎无法全神贯注于它如何实现这一目标。我将继续阅读initWithPatternImage。
蒙德·赫德

-3

要在UILabel周围放置带有圆角边缘的边框,请执行以下操作:

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

(不要忘了包含QuartzCore / QuartzCore.h)


5
这会在整个标签周围(而不是文本周围)添加大边框
Pavel Alexeev 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.