単一行のUILabelの隣にNSTextAttachment画像を中央配置


117

NSTextAttachment属性付きの文字列に画像を追加して、垂直方向の中央に配置したいと思います。

次のコードを使用して文字列を作成しました。

NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:DDLocalizedString(@"title.upcomingHotspots") attributes:attrs];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = [[UIImage imageNamed:@"help.png"] imageScaledToFitSize:CGSizeMake(14.f, 14.f)];
cell.textLabel.attributedText = [str copy];

ただし、画像はセルの上部に配置されているように見えますtextLabel

テキスト添付オフセット問題のスクリーンショット

添付ファイルが描画される四角形を変更するにはどうすればよいですか?


私はNSStringにUIImageを使用するためのカテゴリクラスを持っています。github.com/Pradeepkn/TextWithImageお楽しみください。
PradeepKN、2015年

回答:


59

rectはサブクラス化NSTextAttachmentしてオーバーライドすることで変更できますattachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:。例:

- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex {
    CGRect bounds;
    bounds.origin = CGPointMake(0, -5);
    bounds.size = self.image.size;
    return bounds;
}

それは完璧な解決策ではありません。Y原点を「目で」把握する必要があり、フォントまたはアイコンのサイズを変更する場合は、Y原点を変更する必要があります。しかし、アイコンを別の画像ビューに配置することを除いて、それ以上の方法を見つけることができませんでした(独自の欠点があります)。


2
彼らがなぜこれに反対票を投じたのかわからない、それは私にとても役立った+1
Jasper

Y原点はフォントのディセンダーです。以下の私の答えを参照してください。
ファットマン、2015年

4
トラビスの答えは、サブクラス化なしのよりクリーンなソリューションです。
SwampThingTom

詳細については、@ MG藩の回答チェックアウトstackoverflow.com/a/45161058/280503を(私の意見では、質問への回答選択する必要があります。)
gardenofwine

191

フォントのcapHeightを使用できます。

Objective-C

NSTextAttachment *icon = [[NSTextAttachment alloc] init];
UIImage *iconImage = [UIImage imageNamed:@"icon.png"];
[icon setBounds:CGRectMake(0, roundf(titleFont.capHeight - iconImage.size.height)/2.f, iconImage.size.width, iconImage.size.height)];
[icon setImage:iconImage];
NSAttributedString *iconString = [NSAttributedString attributedStringWithAttachment:icon];
[titleText appendAttributedString:iconString];

迅速

let iconImage = UIImage(named: "icon.png")!
var icon = NSTextAttachment()
icon.bounds = CGRect(x: 0, y: (titleFont.capHeight - iconImage.size.height).rounded() / 2, width: iconImage.size.width, height: iconImage.size.height)
icon.image = iconImage
let iconString = NSAttributedString(attachment: icon)
titleText.append(iconString)

添付画像はテキストのベースラインにレンダリングされます。また、コアグラフィックス座標系と同様に、Y軸が反転しています。画像を上に移動する場合はbounds.origin.y、正に設定します。

画像はテキストのcapHeightと垂直方向の中央に配置する必要があります。だから我々は、設定する必要がありますbounds.origin.y(capHeight - imageHeight)/2

画像のギザギザな影響を避けるために、yの小数部分を丸める必要があります。ただし、フォントと画像は通常小さいため、1ピクセルの違いでも画像がずれているように見えます。そのため、除算する前にラウンド関数を適用しました。y値の小数部を.0または.5にします

あなたの場合、画像の高さはフォントのcapHeightよりも大きいです。ただし、同じ方法で使用できます。オフセットyの値は負になります。そして、ベースラインの下からレイアウトされます。

ここに画像の説明を入力してください


ありがとうございました!!!!!!!!!!!!!!
アレックス

107

お試しください- [NSTextAttachment bounds]。サブクラス化は必要ありません。

コンテキストについてUILabelは、添付画像として使用するためにをレンダリングしてから、境界を次のように設定 attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)しています。ラベル画像内のテキストと属性付き文字列内のテキストのベースラインは、必要に応じて整列しています。


これは、画像を拡大縮小する必要がない限り機能します。
ファットマン、2015年

12
Swift 3.0の場合:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
アンディ

素晴らしいです、ありがとう!descenderUIFont のプロパティを知りませんでした!
Ben

61

私はこれに対する完璧な解決策を見つけましたが、私にとっては魅力のように機能しますが、自分で試してみる必要があります(おそらく定数はデバイスの解像度とおそらく何にでも依存します;)

func textAttachment(fontSize: CGFloat) -> NSTextAttachment {
    let font = UIFont.systemFontOfSize(fontSize) //set accordingly to your font, you might pass it in the function
    let textAttachment = NSTextAttachment()
    let image = //some image
    textAttachment.image = image
    let mid = font.descender + font.capHeight
    textAttachment.bounds = CGRectIntegral(CGRect(x: 0, y: font.descender - image.size.height / 2 + mid + 2, width: image.size.width, height: image.size.height))
    return textAttachment
}

動作するはずであり、決してぼやけるべきではありません(おかげでCGRectIntegral


これを投稿してくれてありがとう、それは私にかなり良いアプローチにつながります。私はあなたがあなたのy座標計算にいくぶん魔法2を追加していることに気づきました。
2016

2
これが、y計算に使用したものです。descender+(abs(descender)+ capHeight)/ 2-iconHeight / 2
Ben

Y原点に+2を使用する理由
William LeGate 2016

@WilliamLeGate本当にわからない。試してみて、テストしたすべてのフォントサイズ(必要なフォントサイズ)で機能した..
borchero

いいですね…この答えは素晴らしいです。
GGirotto

38

何について:

CGFloat offsetY = -10.0;

NSTextAttachment *attachment = [NSTextAttachment new];
attachment.image = image;
attachment.bounds = CGRectMake(0.0, 
                               offsetY, 
                               attachment.image.size.width, 
                               attachment.image.size.height);

サブクラス化は不要


2
self.font.descenderを使用するよりも適切に機能します(iOS 8を実行しているiPhone 4sシミュレーターのデフォルト値は〜4です)。-10は、デフォルトのフォントスタイル/サイズのより良い近似のように見えます。
Kedar Paranjape 2015年

10

@Travisは、オフセットがフォントのディセンダーであることは正しいです。画像も拡大縮小する必要がある場合は、NSTextAttachmentのサブクラスを使用する必要があります。以下は、この記事から着想を得たコードです。要旨も掲載しました。

import UIKit

class ImageAttachment: NSTextAttachment {
    var verticalOffset: CGFloat = 0.0

    // To vertically center the image, pass in the font descender as the vertical offset.
    // We cannot get this info from the text container since it is sometimes nil when `attachmentBoundsForTextContainer`
    // is called.

    convenience init(_ image: UIImage, verticalOffset: CGFloat = 0.0) {
        self.init()
        self.image = image
        self.verticalOffset = verticalOffset
    }

    override func attachmentBoundsForTextContainer(textContainer: NSTextContainer, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
        let height = lineFrag.size.height
        var scale: CGFloat = 1.0;
        let imageSize = image!.size

        if (height < imageSize.height) {
            scale = height / imageSize.height
        }

        return CGRect(x: 0, y: verticalOffset, width: imageSize.width * scale, height: imageSize.height * scale)
    }
}

次のように使用します。

var text = NSMutableAttributedString(string: "My Text")
let image = UIImage(named: "my-image")!
let imageAttachment = ImageAttachment(image, verticalOffset: myLabel.font.descender)
text.appendAttributedString(NSAttributedString(attachment: imageAttachment))
myLabel.attributedText = text

10

非常に大きな子孫があり、私のように画像(キャップ​​の高さの中心)を中央に配置したい場合は、これを試してください

let attachment: NSTextAttachment = NSTextAttachment()
attachment.image = image
if let image = attachment.image{
    let y = -(font.ascender-font.capHeight/2-image.size.height/2)
    attachment.bounds = CGRect(x: 0, y: y, width: image.size.width, height: image.size.height).integral
}

yの計算は次の図のようになります

ここに画像の説明を入力してください

画像を原点から下に移動させるため、y値は0であることに注意してください。

ラベル全体の中央に配置する場合は、次のy値を使用します。

let y = -((font.ascender-font.descender)/2-image.size.height/2)

7

次のように中央に配置された画像の添付ファイルを生成するSwift 4の拡張を作成できます。

extension NSTextAttachment {
    static func getCenteredImageAttachment(with imageName: String, and 
    font: UIFont?) -> NSTextAttachment? {
        let imageAttachment = NSTextAttachment()
    guard let image = UIImage(named: imageName),
        let font = font else { return nil }

    imageAttachment.bounds = CGRect(x: 0, y: (font.capHeight - image.size.height).rounded() / 2, width: image.size.width, height: image.size.height)
    imageAttachment.image = image
    return imageAttachment
    }
}

次に、画像の名前とフォントを送信する呼び出しを行うことができます。

let imageAttachment = NSTextAttachment.getCenteredImageAttachment(with: imageName,
                                                                   and: youLabel?.font)

そして、imageAttachmentをattributedStringに追加します


1

私の場合、sizeToFit()の呼び出しが役立ちました。Swift 5.1では

カスタムラベルの内部:

func updateUI(text: String?) {
    guard let text = text else {
        attributedText = nil
        return
    }

    let attributedString = NSMutableAttributedString(string:"")

    let textAttachment = NSTextAttachment ()
    textAttachment.image = image

    let sizeSide: CGFloat = 8
    let iconsSize = CGRect(x: CGFloat(0),
                           y: (font.capHeight - sizeSide) / 2,
                           width: sizeSide,
                           height: sizeSide)
    textAttachment.bounds = iconsSize

    attributedString.append(NSAttributedString(attachment: textAttachment))
    attributedString.append(NSMutableAttributedString(string: text))
    attributedText = attributedString

    sizeToFit()
}

0

境界の高さには-lineFrag.size.height / 5.0を使用してください。これは画像を正確に中央に配置し、すべてのサイズのフォントのテキストと揃えます

override func attachmentBoundsForTextContainer(textContainer: NSTextContainer, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect
{
    var bounds:CGRect = CGRectZero

    bounds.size = self.image?.size as CGSize!
    bounds.origin = CGPointMake(0, -lineFrag.size.height/5.0);

    return bounds;
}

-lineFrag.size.height/5.0不正解です。代わりに、フォントのディセンダーです。
ファットマン、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.