UILabelにアウトラインテキストを表示させるにはどうすればよいですか?


108

私が欲しいのは、白いUILabelテキストの周りの1ピクセルの黒い境界線だけです。

私は以下のコードで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が自分の意志に曲がることがまったくないようです。


明確化-値がわずかに増減して変化するときにラベルをアニメーション化しているため、アプリの速度が遅いことがわかります。サブクラス化しないとスムーズですが、上記のコードを使用すると、ラベルのアニメーションは途切れ途切れになります。UIWebViewを使用する必要がありますか?ラベルが単一の数字しか表示していないので、私は馬鹿げていると感じています...
モンテHurd

わかりました、私が抱えていたパフォーマンスの問題はアウトラインコードとは無関係であるように見えますが、それでも垂直方向に整列させることができないようです。何らかの理由により、pt.yは常にゼロです。
Monte Hurd、

Chalkdusterなどのフォントの場合、これは非常に遅い
jjxtra

回答:


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、2011年

申し訳ありませんが、私のプロジェクトでコードを使用しようとしていますが、何も起こりません!この写真を参照してください:リンク
モミ

@Momeks:あなたがサブクラス化する必要があるUILabelとput -drawTextInRect:の実装が。
ジェフケリー

1
@Momeks:Objective-Cでサブクラスを作成する方法がわからない場合は、グーグル操作を行う必要があります。AppleはObjective-C言語のガイドを持っています。
ジェフケリー

1
たぶん-2.5年前にこれを書いたときは存在しなかったと思います:)
kprevas

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あなたが設定することができますdefaultTextAttributesし、attributedPlaceholder同様に。

この場合、NSStrokeWidthAttributeName である必要があることに注意してください。つまり、内部のアウトラインのみが機能します。

属性付きテキストを使用したアウトラインを持つUITextFields


17
Objective-Cでの便宜のために、これは次のようになります。 ;
Leszek Szary、2015年

8
このミームの使用は、このアプローチのニュアンスを効果的に伝えました
woody121

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

ありがとう@TeoSartori、私は私の回答にSwift 4.2構文を追加しました;)
Axel Guilmin

25

受け入れられた回答とその2つの修正、および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の既存のラベルに追加し、次のようなユーザー定義のランタイム属性を追加して、境界線の太さとその色を変更できます。 Interface Builderのセットアップ

結果:

アウトラインの大きな赤いG


1
いいね。私はこの:)でIBInspectableを作ってあげる
マリオカルバリョ

@Lachezar UIButtonのテキストでこれをどのように行うことができますか?
CrazyOne

8

回答の実装には1つの問題があります。ストロークを使用してテキストを描画すると、ストロークを使用せずにテキストを描画する場合とは文字グリフの幅がわずかに異なります。塗りつぶしテキストの周りに目に見えないストロークを追加することで修正できます。

交換:

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のおかげで、2行のコードを追加するだけでテキストの周囲に輪郭を描くことができました。解決に長い時間を費やしてきた問題の解決策を提供してくれてありがとう!
Alan Kinnaman

私は誰も気づいていないことに驚いていますが、組み込みのテキストストロークコマンドではアウトラインは作成されませんが、代わりに「インライン」が作成されます。つまり、ストロークは文字の外側ではなく内側に描画されます。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
これは、ラベルの周囲の1ピクセルの境界ではありません。これは、下部への単純なドロップシャドウです。
Tim

1
これは間違った解決策です。それをマークアップした良い神?!
Sam 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に追加し、アニメーション化します。

アニメーションがスピードアップすることを願っています!!

N


それは私が使用するいくつかの場所を考えることができる素晴らしいトリックであり、おそらくパフォーマンスの問題を解決しますが、私はuilabelテキストにアウトラインを表示させるために、くだらないサブクラスよりも簡単な方法があることを望んでいます。その間、私はあなたの例に感謝します!
Monte Hurd、

あなたの痛みが分かります。あなたがそれを行うための良い方法を見つけるかどうかはわかりません。一連のコードを製品の効果に書き、それらを(上記のように)スナップショットして、スムーズなアニメーションを作成します!上記の例では、コードに<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テキストの周りの1ピクセルの黒い境界線であるなら、

その後、私はあなたが問題をそれより難しくしていると思います...どのrect / frameRect関数を使用すればよいかはメモリでわかりませんが、簡単に見つけることができます。このメソッドは戦略を示すだけです(スーパークラスに任せてください!):

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

わかりません。おそらく、画面を12時間ほど見つめていて、この時点ではあまり得られないためです...
モンテHurd、

あなたはすでにテキストを描画するクラスであるUILabelをサブクラス化しています。また、サブクラス化する理由は、テキストの周囲に黒い境界線を追加するためです。スーパークラスにテキストを描画させる場合は、境界線を描画するだけです。
ケント

これは良い点ですが、1ピクセルの黒い境界線を追加したい場合、サブクラスは文字を近づけすぎます。....元の質問に投稿されたようなことをして、最適化します(私の投稿で述べたように)!N
ニックカートライト、

@nickcartwright:スーパークラスがあなたの言うとおりに実行している場合、[super drawRect]を呼び出す前にCGRectを挿入できます。スーパークラスの機能を書き直すよりもさらに簡単で安全です。
ケント

そして、@ kentはSOをいらいらさせました。すばらしい答え、+ 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にサブビューとして新しいカスタムクラスを追加しました(この回答に触発されまし)。

それをコピーしてプロジェクトに貼り付けるだけでよいのです。

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について読んでいきます。
Monte Hurd、

-3

UILabelの周りに丸いエッジのボーダーを配置するには、次のようにします。

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

(QuartzCore / QuartzCore.hを含めることを忘れないでください)


5
これにより、テキストではなくラベル全体に大きな境界線が追加されます
Pavel Alexeev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.