ios 如何让 UILabel 显示轮廓文本?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1103148/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:08:11  来源:igfitidea点击:

How do I make UILabel display outlined text?

iosobjective-ciphonecocoa-touchcore-graphics

提问by Monte Hurd

All I want is a one pixel black border around my white UILabel text.

我想要的只是我的白色 UILabel 文本周围的一个像素的黑色边框。

I got as far as subclassing UILabel with the code below, which I clumsily cobbled together from a few tangentially related online examples. And it works but it's very, very slow (except on the simulator) and I couldn't get it to center the text vertically either (so I hard-coded the y value on the last line temporarily). Ahhhh!

我用下面的代码对 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]);
}

I just have a nagging feeling that I'm overlooking a simpler way to do this. Perhaps by overriding "drawTextInRect", but I can't seem to get drawTextInRect to bend to my will at all despite staring at it intently and frowning really really hard.

我只是有一种唠叨的感觉,我忽略了一种更简单的方法来做到这一点。也许通过覆盖“drawTextInRect”,但我似乎无法让 drawTextInRect 屈服于我的意愿,尽管我专心地盯着它并且真的很难皱眉。

回答by kprevas

I was able to do it by overriding drawTextInRect:

我能够通过覆盖 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;

}

回答by Axel Guilmin

A simpler solution is to use an Attributed Stringlike so:

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

Swift 4:

斯威夫特 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:

斯威夫特 4.2:

let strokeTextAttributes: [NSAttributedString.Key : Any] = [
    .strokeColor : UIColor.black,
    .foregroundColor : UIColor.white,
    .strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)


On a UITextFieldyou can set the defaultTextAttributesand the attributedPlaceholderas well.

在 a 上,UITextField您也可以设置defaultTextAttributesattributedPlaceholder

Note that the NSStrokeWidthAttributeNamehas to be negativein this case, i.e. only the inner outlines work.

请注意,在这种情况下NSStrokeWidthAttributeName必须为负,即只有内部轮廓有效。

UITextFields with an outline using attributed texts

带有使用属性文本的大纲的 UITextFields

回答by Lachezar

After reading the accepted answer and the two corrections to it and the answer from Axel Guilmin, I decided to compile an overall solution in Swift, that suits me:

在阅读了接受的答案及其两个更正以及 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)
    }
}

You can add this custom UILabel class to an existing label in the Interface Builder and change the thickness of the border and its color by adding User Defined Runtime Attributes like this: Interface Builder setup

您可以将此自定义 UILabel 类添加到 Interface Builder 中的现有标签,并通过添加用户定义的运行时属性来更改边框的粗细和颜色,如下所示: 界面生成器设置

Result:

结果:

big red G with outline

带轮廓的大红色 G

回答by tobihagemann

There is one issue with the answer's implementation. Drawing a text with stroke has a slightly different character glyph width than drawing a text without stroke, which can produce "uncentered" results. It can be fixed by adding an invisible stroke around the fill text.

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

Replace:

代替:

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

with:

和:

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

I'm not 100% sure, if that's the real deal, because I don't know if self.textColor = textColor;has the same effect as [textColor setFill], but it should work.

我不是 100% 确定,如果那是真的,因为我不知道是否与self.textColor = textColor;具有相同的效果[textColor setFill],但它应该有效。

Disclosure: I'm the developer of THLabel.

披露:我是 THLabel 的开发者。

I've released a UILabel subclass a while ago, which allows an outline in text and other effects. You can find it here: https://github.com/tobihagemann/THLabel

不久前我发布了一个 UILabel 子类,它允许文本和其他效果的轮廓。你可以在这里找到它:https: //github.com/tobihagemann/THLabel

回答by Suran

This won't create an outline per-se, but it will put a shadow around the text, and if you make the shadow radius small enough it could resemble an outline.

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

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

I don't know whether it is compatible with older versions of iOS..

我不知道它是否兼容旧版本的iOS..

Anyway, I hope it helps...

无论如何,我希望它有帮助...

回答by Nick Cartwright

If you want to animate something complicated, the best way is to programmaticly take a screenshot of it an animate that instead!

如果你想为复杂的东西制作动画,最好的方法是以编程方式截取它的屏幕截图,而不是动画!

To take a screenshot of a view, you'll need code a little like this:

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

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

Where mainContentView is the view you want to take a screenshot of. Add viewImage to a UIImageView and animate that.

其中 mainContentView 是您要截取屏幕截图的视图。将 viewImage 添加到 UIImageView 并为其设置动画。

Hope that speeds up your animation!!

希望能加快你的动画速度!!

N

N

回答by ufmike

As MuscleRumblementioned, the accepted answer's border is a bit off center. I was able to correct this by setting the stroke width to zero instead of changing the color to clear.

正如MuscleRumble 所提到的,接受的答案的边界有点偏离中心。我能够通过将笔触宽度设置为零而不是将颜色更改为清除来纠正此问题。

i.e. replacing:

即替换:

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

with:

和:

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

I would've just commented on his answer but apparently I'm not "reputable" enough.

我只是对他的回答发表评论,但显然我不够“有信誉”。

回答by Chris Stillwell

A Swift 4 class version based off the answer by kprevas

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

It can be implemented entirely in the Interface Builder by setting the UILabel's custom class to OutlinedText. You will then have the ability to set the outline's width and color from the Properties pane.

通过将 UILabel 的自定义类设置为 OutlinedText,它可以完全在 Interface Builder 中实现。然后,您将能够从“属性”窗格中设置轮廓的宽度和颜色。

enter image description here

在此处输入图片说明

回答by kent

if ALL you want is a one pixel black border around my white UILabel text,

如果你想要的只是我的白色 UILabel 文本周围的一个像素的黑色边框,

then i do think you're making the problem harder than it is... I don't know by memory which 'draw rect / frameRect' function you should use, but it will be easy for you to find. this method just demonstrates the strategy (let the superclass do the work!):

那么我确实认为你让问题变得比现在更难......我不知道你应该使用哪个'draw rect / frameRect'函数,但你很容易找到它。这个方法只是演示了策略(让超类来做!):

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

回答by Robotbugs

I found an issue with the main answer. The text position is not necessarily centered correctly to sub-pixel location, so that the outline can be mismatched around the text. I fixed it using the following code, which uses CGContextSetShouldSubpixelQuantizeFonts(ctx, false):

我发现主要答案有问题。文本位置不一定正确居中到亚像素位置,因此文本周围的轮廓可能不匹配。我使用以下代码修复了它,该代码使用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];
}

This assumes that you defined textOutlineColorand textOutlineWidthas properties.

这假设您已将textOutlineColor和定义textOutlineWidth为属性。