Centro NSTextAttachment imagen junto a una sola línea UILabel

117

Me gustaría agregar una NSTextAttachmentimagen a mi cadena atribuida y centrarla verticalmente.

He usado el siguiente código para crear mi cadena:

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

Sin embargo, la imagen parece alinearse con la parte superior de la celda textLabel.

Captura de pantalla del problema de compensación de archivos adjuntos de texto

¿Cómo puedo cambiar el rect en el que se dibuja el adjunto?

Sean Danzeiser
fuente
Tengo una clase de categoría para tener NSString con UIImage y viceversa. github.com/Pradeepkn/TextWithImage Disfrute.
PradeepKN

Respuestas:

59

Puede cambiar el rect subclasificando NSTextAttachmenty anulando attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:. Ejemplo:

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

No es una solución perfecta. Tienes que averiguar el origen Y "a ojo" y si cambias la fuente o el tamaño del icono, probablemente querrás cambiar el origen Y. Pero no pude encontrar una mejor manera, excepto colocando el ícono en una vista de imagen separada (que tiene sus propias desventajas).

Rob Mayoff
fuente
2
No tengo idea de por qué votaron en contra, me ayudó mucho +1
Jasper
El origen Y es el descendiente de fuentes. Vea mi respuesta a continuación.
phatmann
4
La respuesta de Travis es una solución más limpia sin subclases.
SwampThingTom
Para obtener más detalles, consulte la respuesta de @ mg-han stackoverflow.com/a/45161058/280503 (en mi opinión, debería ser la respuesta seleccionada a la pregunta).
gardenofwine
191

Puede utilizar el capHeight de la fuente.

C objetivo

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

Rápido

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)

La imagen adjunta se representa en la línea de base del texto. Y el eje y está invertido como el sistema de coordenadas de los gráficos centrales. Si desea mover la imagen hacia arriba, establezca bounds.origin.yen positivo.

La imagen debe alinearse verticalmente en el centro con el capHeight del texto. Así que necesitamos configurar el bounds.origin.yto (capHeight - imageHeight)/2.

Para evitar algún efecto irregular en la imagen, debemos redondear la parte fraccionaria de la y. Pero las fuentes y las imágenes suelen ser pequeñas, incluso una diferencia de 1 px hace que la imagen parezca desalineada. Entonces apliqué la función redonda antes de dividir. Hace que la fracción sea parte del valor de y en .0 o .5

En su caso, la altura de la imagen es mayor que la altura capHeight de la fuente. Pero puedes usar la misma forma. El valor de la compensación y será negativo. Y se presentará desde abajo de la línea de base.

ingrese la descripción de la imagen aquí

MG Han
fuente
¡¡¡¡¡¡¡¡¡¡¡¡¡¡GRACIAS!!!!!!!!!!!!!!
Alex
107

Prueba - [NSTextAttachment bounds]. No se requieren subclases.

Para el contexto, estoy renderizando a UILabelpara usar como imagen adjunta, luego estableciendo los límites así: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)y líneas de base de texto dentro de la imagen de la etiqueta y el texto en la línea de cadena atribuida como se desee.

Travis
fuente
Esto funciona siempre que no necesite escalar la imagen.
phatmann
12
Para Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Andy
¡Genial gracias! ¡No sabía nada de la descenderpropiedad de UIFont!
Ben
61

Encontré una solución perfecta para esto, aunque funciona como un encanto para mí, sin embargo, debes probarlo tú mismo (probablemente la constante depende de la resolución del dispositivo y tal vez lo que sea;)

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
}

Debería funcionar y no debería estar borroso de ninguna manera (gracias a CGRectIntegral)

borchero
fuente
Gracias por publicar esto, me llevó a un enfoque bastante bueno. Noté que está agregando un 2 algo mágico al cálculo de la coordenada y.
Ben
2
Esto es lo que usé para mi cálculo y: descender + (abs (descender) + capHeight) / 2 - iconHeight / 2
Ben
¿Por qué el +2 para el origen Y?
William LeGate
@WilliamLeGate Realmente no lo sé, solo lo probé y funcionó para todos los tamaños de fuente que probé (los que necesitaba) ..
borchero
Maldita sea ... Esta respuesta es asombrosa.
GGirotto
38

Qué pasa:

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

No se necesitan subclases

Jakub Truhlář
fuente
2
Funciona mejor que usar self.font.descender (que tiene un valor predeterminado de ~ 4 en el simulador de iPhone 4s con iOS 8). -10 parece una mejor aproximación del estilo / tamaño de fuente predeterminado.
Kedar Paranjape
10

@Travis tiene razón en que el desplazamiento es el descendiente de fuentes. Si también necesita escalar la imagen, deberá usar una subclase de NSTextAttachment. A continuación se muestra el código, que se inspiró en este artículo . También lo publiqué como una esencia .

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

Úselo de la siguiente manera:

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
fuente
10

Si tienes un ascendente muy grande y quieres centrar la imagen (centro de la altura de la tapa) como yo, prueba esto

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
}

El cálculo y es como la imagen de abajo

ingrese la descripción de la imagen aquí

Tenga en cuenta que el valor de y es 0 porque queremos que la imagen se desplace hacia abajo desde el origen

Si desea que esté en el medio de toda la etiqueta, use este valor y:

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

Podemos hacer una extensión en swift 4 que genere un archivo adjunto con una imagen centrada como esta:

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

Luego puedes realizar la llamada enviando el nombre de la imagen y la fuente:

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

Y luego agregue el archivo imageAttachment a la cadena atribuida

Oscar
fuente
1

En mi caso, llamar a sizeToFit () ayudó. En Swift 5.1

Dentro de su etiqueta personalizada:

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()
}
Reimond Hill
fuente
0

Utilice -lineFrag.size.height / 5.0 para la altura de los límites. Esto centra exactamente la imagen y se alinea con el texto para todos los tamaños de fuentes.

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
fuente
-lineFrag.size.height/5.0no es correcto. En cambio, es el descendiente de fuentes.
phatmann