Qiitaに書いたやつ

Swift5 UILabelに画像(UIImage)を表示する NSAttributedString NSTextAttachment

XcodeiOSSwift
2019年05月10日

qiitaの記事がobjcだったのでサクッとコピペで動かしたい方に最新版のコードを共有

UILabelにUIImageを表示する方法

①NSTextAttachmentを作成する。 ②NSTextAttachmentからNSAttributedStringを作成する。 ③UILabelのattributedTextに指定する。

画像のサイズや表示位置は①で調整します

今回は実際に起こりそうなものとして、ラベルの末尾に画像を表示してみます

スクリーンショット 2019-05-10 11.59.12.png
    override func viewDidLoad() {
        super.viewDidLoad()
               
        let text = "labelに犬を表示する"
        let image = UIImage(named: "dog.jpg")!
        let font: UIFont = .systemFont(ofSize: 30)
        let size = CGSize(width: 30, height: 30)
        
        let attachment = NSTextAttachment()
        attachment.image = image

        let y = (font.capHeight-size.height).rounded() / 2
        attachment.bounds.origin = CGPoint(x: 0, y: y)
        attachment.bounds.size = size
        
        let imageAttribute = NSAttributedString(attachment: attachment)
        let mutableString = NSMutableAttributedString(string: text)
        mutableString.insert(imageAttribute, at: text.count)
        
        let label = UILabel()
        view.addSubview(label)
        label.textAlignment = .center
        label.frame.size = .init(width: view.bounds.width-20, height: 50)
        label.center = view.center
        label.font = font
        label.attributedText = mutableString
    }  

attachmentにsizeを指定すると表示されます。ただそれだと、画像が表示される高さがずれてしまうので、 今回は中央に揃うように高さを調整しました。

当たり前ですがinsertする場所をずらせば中央に表示できます スクリーンショット 2019-05-10 12.00.20.png

デスクトップに犬の写真しかなかったですが、例えば仮想通貨の保有量+💶(通過のアイコン)スコア+👑(王冠やメダル)などが考えられると思います。

結構コード量が多くてとっつきにくい気もします。

Extension

うまい感じに共通化したいですが高さを揃えるにはUILabelのfontと表示したい画像のサイズの情報が必要になってきます。 UILabelのextensionにするのもなんか違う気がしますが一応

    label.text = "labelに犬を表示する"
    label.insertImage(UIImage(named: "dog.jpg")!, at: label.text!.count, alignment: .center)

sizeはデフォルトでimage.sizeを指定します。

    label.insertImage(UIImage(named: "dog.jpg")!, at: label.text!.count, size: CGSize(width: 30, height: 30), alignment: .center) 
extension UILabel {
    func insertImage(_ image: UIImage, at index: Int, size: CGSize? = nil, alignment: NSTextAttachment.VerticalAlignment = .center) {
        let attr = attributedText as? NSMutableAttributedString ?? NSMutableAttributedString(string: text ?? "")
        let attachment = NSTextAttachment(image: image, font: font, size: size ?? image.size, alignment: alignment)
        attr.insert(NSAttributedString(attachment: attachment), at: index)
        attributedText = attr
    }
}

extension NSTextAttachment {
    convenience init(image: UIImage, font: UIFont, size: CGSize, alignment: VerticalAlignment) {
        self.init()
        self.image = image
        let y: CGFloat
        switch alignment {
        case .top:
            y = font.capHeight - size.height
        case .bottom:
            y = font.descender
        case .center:
            y = (font.capHeight - size.height).rounded() / 2
        case .baseline:
            y = 0
        }
        bounds.origin = CGPoint(x: 0, y: y)
        bounds.size = size
    }
    
    enum VerticalAlignment {
        case bottom, baseline, center, top
    }
}

ラベルの上にsizeや表示位置の調整についてはこちらを参考にしました。

引用: (https://stackoverflow.com/questions/26105803/center-nstextattachment-image-next-to-single-line-uilabel)

ちなみにy座標は反転していて、UIFont.desendentは-の値を返します。


画像ともじのマージンを調整したい場合

テキストと画像の間にのマージンを調整したい場合は スペースを入れる、fontSizeを調整する、もしくは最悪の場合Paddingをつけた画像を生成する必要があるのかなと思いました。

    func makeImageWithPadding(image: UIImage, padding: UIEdgeInsets) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(image.size, false, UIScreen.main.scale)
        UIGraphicsBeginImageContext(.init(
                width: image.size.width + padding.left + padding.right,
                height: image.size.height + padding.top + padding.bottom
            )
        )
        image.draw(at: .init(x: padding.left, y: padding.top))
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

(追記) Swift5で強化された機能をつかうと

https://qiita.com/fmtonakai/items/2e685e3c719d7fda7fc7

let attrStr: AttributedString = "今日はいい天気だ\(image)"

こうゆう実装もできるよう

同じタグの投稿

2020 churabou