Pusatkan gambar NSTextAttachment di sebelah baris UILabel

117

Saya ingin menambahkan NSTextAttachmentgambar ke string atribut saya dan membuatnya berada di tengah secara vertikal.

Saya telah menggunakan kode berikut untuk membuat string saya:

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];

Namun, gambar tampak sejajar dengan bagian atas sel textLabel.

tangkapan layar masalah offset lampiran teks

Bagaimana saya bisa mengubah persegi di mana lampiran digambar?

Sean Danzeiser
sumber
Saya memiliki kelas kategori untuk memiliki NSString dengan UIImage dan sebaliknya. github.com/Pradeepkn/TextWithImage Nikmati.
PradeepKN

Jawaban:

59

Anda dapat mengubah rect dengan subclassing NSTextAttachmentdan overriding attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:. Contoh:

- (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;
}

Ini bukanlah solusi yang sempurna. Anda harus mencari tahu asal Y "dengan mata" dan jika Anda mengubah font atau ukuran ikon, Anda mungkin ingin mengubah Y-origin. Tetapi saya tidak dapat menemukan cara yang lebih baik, kecuali dengan meletakkan ikon dalam tampilan gambar terpisah (yang memiliki kekurangan).

merampok mayoff
sumber
2
Tidak tahu mengapa mereka tidak memilih ini, itu membantu saya jadi +1
Jasper
Y-origin adalah font descender. Lihat jawaban saya di bawah.
phatmann
4
Jawaban Travis adalah solusi yang lebih bersih tanpa subclassing.
SwampThingTom
Untuk lebih jelasnya, lihat jawaban @ mg-han stackoverflow.com/a/45161058/280503 (Menurut pendapat saya itu harus menjadi jawaban yang dipilih untuk pertanyaan itu.)
gardenofwine
191

Anda dapat menggunakan capHeight font.

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];

Cepat

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)

Gambar lampiran dirender pada garis dasar teks. Dan sumbu y dibalik seperti sistem koordinat grafik inti. Jika Anda ingin memindahkan gambar ke atas, setel bounds.origin.yke positif.

Gambar harus diratakan secara vertikal di tengah dengan capHeight teks. Jadi kita perlu menyetel bounds.origin.yke (capHeight - imageHeight)/2.

Untuk menghindari beberapa efek bergerigi pada gambar, kita harus membulatkan bagian pecahan dari y. Tapi font dan gambar biasanya kecil, bahkan perbedaan 1px membuat gambar terlihat tidak sejajar. Jadi saya menerapkan fungsi bulat sebelum membagi. Itu membuat bagian pecahan dari nilai y menjadi 0,0 atau 0,5

Dalam kasus Anda, tinggi gambar lebih besar dari capHeight font. Tapi Anda bisa menggunakan cara yang sama. Nilai offset y akan menjadi negatif. Dan itu akan ditata dari bawah garis dasar.

masukkan deskripsi gambar di sini

MG Han
sumber
TERIMA KASIH!!!!!!!!!!!!!!
Alex
107

Coba - [NSTextAttachment bounds]. Tidak diperlukan subclass.

Untuk konteks, saya merender a UILabeluntuk digunakan sebagai gambar lampiran, kemudian mengatur batas seperti ini: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)dan garis dasar teks dalam gambar label dan teks dalam baris atribut yang diatribusikan sesuai keinginan.

Travis
sumber
Ini berfungsi selama Anda tidak perlu mengubah skala gambar.
phatmann
12
Untuk Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Andy
Keren terima kasih! Tidak tahu tentang descenderproperti UIFont!
Ben
61

Saya menemukan solusi sempurna untuk ini, berfungsi seperti pesona bagi saya, namun Anda harus mencobanya sendiri (mungkin konstanta tergantung pada resolusi perangkat dan mungkin apa pun;)

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
}

Harus berfungsi dan tidak boleh buram dengan cara apa pun (terima kasih CGRectIntegral)

borchero.dll
sumber
Terima kasih telah memposting ini, ini mengarahkan saya pada pendekatan yang cukup bagus. Saya perhatikan bahwa Anda menambahkan angka 2 yang agak ajaib ke perhitungan koordinat y Anda.
Ben
2
Inilah yang saya gunakan untuk perhitungan y saya: descender + (abs (descender) + capHeight) / 2 - iconHeight / 2
Ben
Mengapa +2 untuk asal Y?
William LeGate
@WilliamLeGate Saya benar-benar tidak tahu, baru saja mencobanya dan berhasil untuk semua ukuran font yang saya uji (yang saya butuhkan) ..
borchero
Sial ... Jawaban ini luar biasa.
GGirotto
38

Bagaimana dengan:

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);

Tidak perlu subclass

Jakub Truhlář
sumber
2
Bekerja lebih baik daripada menggunakan self.font.descender (yang memiliki nilai default ~ 4 pada simulator iPhone 4 yang menjalankan iOS 8). -10 terlihat seperti perkiraan yang lebih baik untuk gaya / ukuran font default.
Kedar Paranjape
10

@Travis benar bahwa offsetnya adalah font descender. Jika Anda juga perlu menskalakan gambar, Anda perlu menggunakan subclass NSTextAttachment. Di bawah ini adalah kode yang terinspirasi dari artikel ini . Saya juga mempostingnya sebagai intinya .

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)
    }
}

Gunakan sebagai berikut:

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
phatmann.dll
sumber
10

Jika Anda memiliki ascendent yang sangat besar dan ingin memusatkan gambar (pusat ketinggian topi) seperti saya coba ini

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
}

Perhitungan y seperti gambar di bawah ini

masukkan deskripsi gambar di sini

Perhatikan bahwa nilai y adalah 0 karena kita ingin gambar bergeser ke bawah dari aslinya

Jika Anda ingin berada di tengah seluruh label. Gunakan nilai y ini:

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

Kita bisa membuat ekstensi di swift 4 yang menghasilkan lampiran dengan gambar di tengah seperti ini:

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

Kemudian Anda dapat melakukan panggilan dengan mengirimkan nama gambar dan font:

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

Dan kemudian tambahkan imageAttachment ke atributedString

Oscar
sumber
1

Dalam kasus saya, memanggil sizeToFit () membantu. Dalam 5.1 cepat

Di dalam label kustom Anda:

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()
}
Bukit Reimond
sumber
0

Harap gunakan -lineFrag.size.height / 5.0 untuk tinggi batas. Ini persis memusatkan gambar dan sejajar dengan teks untuk semua ukuran font

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;
}
Harish Kumar Kailas
sumber
-lineFrag.size.height/5.0tidak benar. Sebaliknya itu adalah font descender.
phatmann