Центрировать изображение NSTextAttachment рядом с однострочным UILabel

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 01

Ответы:

59

Вы можете изменить прямоугольник путем создания подклассов 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
Джаспер
Y-начало - это нижний элемент шрифта. Смотрите мой ответ ниже.
phatmann
4
Ответ Трэвиса - более чистое решение без подклассов.
SwampThingTom
Для получения дополнительной информации ознакомьтесь с ответом @mg-han stackoverflow.com/a/45161058/280503 (на мой взгляд, это должен быть выбранный ответ на вопрос.)
gardenofwine
192

Вы можете использовать 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,0 или 0,5

В вашем случае высота изображения больше, чем capHeight шрифта. Но можно использовать так же. Значение смещения y будет отрицательным. И он будет выложен снизу базовой линии.

введите описание изображения здесь

MG Хан
источник
СПАСИБО!!!!!!!!!!!!!!
Алекс
107

Попробуй - [NSTextAttachment bounds]. Создание подклассов не требуется.

Для контекста я визуализирую a UILabelдля использования в качестве изображения вложения, а затем устанавливаю границы следующим образом: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)и базовые линии текста в изображении метки и текст в атрибутированной строке выстраиваются по желанию.

Travis
источник
Это работает до тех пор, пока вам не нужно масштабировать изображение.
phatmann
12
Для Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Энди,
Отлично, спасибо! Не знал о descenderсобственности UIFont!
Бен
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)

borchero
источник
Спасибо за публикацию, это привело меня к довольно хорошему подходу. Я заметил, что вы добавляете магическую цифру 2 к вычислению координаты y.
Бен,
2
Вот то, что я использовал для расчета оси Y: descender + (abs (descender) + capHeight) / 2 - iconHeight / 2
Ben
Почему +2 для начала Y?
Уильям Легейт
@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 (которое имеет значение по умолчанию ~ 4 на симуляторе iPhone 4s под управлением iOS 8). -10 выглядит как лучшее приближение для стиля / размера шрифта по умолчанию.
Kedar Paranjape
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
phatmann
источник
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)
IndyZa
источник
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 (). В быстрой 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не является правильным. Вместо этого это дескриптор шрифта.
phatmann