読者です 読者をやめる 読者になる 読者になる

酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

SwiftでNSAttributedStringを使って文字列を装飾する(UILabelに画像を表示する)

iPhone

直近でやっていたプロジェクトでNSAttributedStringに関する知識が少なくて苦労したので忘れないようにメモしておきます。

NSAttributedStringに関してはすでに色んなブログやサイトで紹介されているのでご存知の方も多いかもしれませんが文字列を装飾してくれるクラスです。AndroidでいうところのImageSpanに相当します。

従来は単一のプロパティしか設定できず*1文字通りテキストを表示するラベルだったUILabelでしたが、iOS 6のころに複雑な表現ができるようにUILabelにNSAttributedStringを設定することができるようになりました。

当時のことはほとんど覚えていない……というよりもUILabelのサブクラスで自前描画*2することが多くてそれらの過去の遺産を継承していたので、僕の場合はNSAttributedStringクラスを本格的に使い出すようになったのはiOS 7.0からだと思います。

本記事ではNSAttributedStringを使って文字列を装飾する方法を紹介していきます。

まずはプロジェクトを適当に作っていただいて、ViewControllerにUILabelを貼り付けておいてください。名前はlabelとしましょう。UILabelはデフォルトの設定のままとしています。

f:id:ch3cooh393:20150716111625p:plain

UILabelにテキストを簡単に装飾して表示する

UILabelのプロパティにテキストカラーやフォントを指定します。UILabelに最も簡単にテキストを装飾して表示させる方法です。

self.label.text = "てすと"
self.label.textColor = UIColor.whiteColor()
self.label.textAlignment = NSTextAlignment.Center
self.label.font = UIFont(name: "HiraKakuProN-W3", size: 10) ?? UIFont.systemFontOfSize(10)

下図のように表示されます。

f:id:ch3cooh393:20150716111614p:plain

iOSの各バージョンで使えるフォントに関しては「iOS 7 で使えるフォント名一覧 - Over&Out その後」で一覧を見ていただくと良いと思います。前職では「ヒラギノ角ゴシック」をよく使っていました。

NSAttributedStringを使って文字を装飾する(基本形)

UILabelのプロパティを指定せずにNSAttributedStringを使って、先ほどと同じように文字列を装飾してみましょう。

private func attribute1() -> NSAttributedString? {
    let font = UIFont(name: "HiraKakuProN-W3", size: 10) ?? UIFont.systemFontOfSize(10)
    
    let style = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
    style.alignment = NSTextAlignment.Center
    
    let attr = [
        NSForegroundColorAttributeName: UIColor.whiteColor(),
        NSFontAttributeName: font,
        NSParagraphStyleAttributeName: style,
    ]
    
    return NSAttributedString(string: "てすと", attributes: attr)
}

先ほどのUILabelのプロパティを設定していた部分をコメントアウトしてしまって、UILabelのattributedTextプロパティにNSAttributedStringオブジェクトを設定します。

self.label.attributedText = attribute1()

下図のように表示されます。

f:id:ch3cooh393:20150716111705p:plain

NSAttributedStringの第二引数のattributesの指定に使っているNSForegroundColorAttributeNameのようなキーは下記のようにたくさんあります。(虫食いになっているところは後で埋める)

キー 説明
NSFontAttributeName フォント。デフォルトではHelvetica(Neue) 12pt が使われる
NSParagraphStyleAttributeName パラグラフ(段落)。デフォルトではdefaultParagraphStyleが使われる
NSForegroundColorAttributeName 文字色。デフォルトでは黒色が使われる
NSBackgroundColorAttributeName 背景色。デフォルトでは背景が存在しない(nil)
NSLigatureAttributeName
NSKernAttributeName 文字間。
NSStrikethroughStyleAttributeName
NSUnderlineStyleAttributeName
NSStrokeColorAttributeName
NSStrokeWidthAttributeName
NSShadowAttributeName 影。デフォルトでは表示しない(nil)
NSTextEffectAttributeName テキストエフェクト。デフォルトでは表示しない(nil)
NSAttachmentAttributeName
NSLinkAttributeName
NSBaselineOffsetAttributeName
NSUnderlineColorAttributeName
NSStrikethroughColorAttributeName
NSObliquenessAttributeName
NSExpansionAttributeName
NSWritingDirectionAttributeName
NSVerticalGlyphFormAttributeName

UILabelに画像を表示する

NSAttributedStringを使ってUILabelに画像を表示させます。

private func attributeWithImage1() -> NSAttributedString? {
    let font = UIFont(name: "HiraKakuProN-W3", size: 10) ?? UIFont.systemFontOfSize(10)
    
    let style = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
    style.alignment = NSTextAlignment.Center
    
    let attr = [
        NSForegroundColorAttributeName: UIColor.whiteColor(),
        NSFontAttributeName: font,
        NSParagraphStyleAttributeName: style,
    ]
    
    var attrString = NSMutableAttributedString(string: "てすと", attributes: attr)
    
    // 画像はNSTextAttachmentで
    let attachment = NSTextAttachment()
    attachment.image = UIImage(named: "master")
    attachment.bounds = CGRect(x: 0, y: -2, width: 19, height: 19)
    attrString.appendAttributedString(NSAttributedString(attachment: attachment))
    
    return attrString
}

f:id:ch3cooh393:20150716111717p:plain

長いテキスト表示させた時に画像を残して文字列は省略する

とても長い文字列を表示させた時には下図のように表示されてしまいます。

f:id:ch3cooh393:20150716115147p:plain

lineBreakModeNSLineBreakMode.ByTruncatingTailを設定するとお尻が省略されてしまうので、テキストを省略する必要のある場合にはNSLineBreakMode.ByTruncatingMiddleを設定して、テキストの中央部分を省略します。

private func attributeWithImage2() -> NSAttributedString? {
    let font = UIFont(name: "HiraKakuProN-W3", size: 10) ?? UIFont.systemFontOfSize(10)
    
    let style = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
    style.alignment = NSTextAlignment.Center
    style.lineBreakMode = NSLineBreakMode.ByTruncatingMiddle
    
    let attr = [
        NSForegroundColorAttributeName: UIColor.whiteColor(),
        NSFontAttributeName: font,
        NSParagraphStyleAttributeName: style,
    ]
    
    // 長いテキスト
    var text = ""
    for i in 0..<20 {
        text += "てすと"
    }
    var attrString = NSMutableAttributedString(string: text, attributes: attr)
    
    // 画像はNSTextAttachmentで
    let attachment = NSTextAttachment()
    attachment.image = UIImage(named: "master")
    attachment.bounds = CGRect(x: 0, y: -2, width: 19, height: 19)
    attrString.appendAttributedString(NSAttributedString(attachment: attachment))
    
    return attrString
}

下図のように表示されます。

f:id:ch3cooh393:20150716115128p:plain

画像部分がハミ出しているのでテキストの高さと合わせたい

画像が大きかったのでテキストの高さに合わせます。

private func attributeWithImage3() -> NSAttributedString? {
    let font = UIFont(name: "HiraKakuProN-W3", size: 10) ?? UIFont.systemFontOfSize(10)
    
    let style = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
    style.alignment = NSTextAlignment.Center
    style.lineBreakMode = NSLineBreakMode.ByTruncatingMiddle
    
    let attr = [
        NSForegroundColorAttributeName: UIColor.whiteColor(),
        NSFontAttributeName: font,
        NSParagraphStyleAttributeName: style,
    ]
    
    var attrString = NSMutableAttributedString(string: "てすと", attributes: attr)
    
    // 画像はNSTextAttachmentで
    let attachment = NSTextAttachment()
    attachment.image = UIImage(named: "master")
    attachment.bounds = CGRect(x: 0, y: -1, width: font.leading, height: font.leading)
    attrString.appendAttributedString(NSAttributedString(attachment: attachment))
    
    return attrString
}

下図のように表示されます。

f:id:ch3cooh393:20150716122052p:plain

ちなみにこの画像のキャラはJenkinsくん(master)です。

*1:例えば、複数のテキストカラーを使ったりできず

*2:オーナードロー……じゃなくてiOSだったら何て言うんだろうdrawRectで自前描画すること