ios objective-c swift uilabel nstextattachment

ios - Imagen de adjunto NSText Center al lado de una línea UILabel



objective-c swift (9)

Me gustaría agregar una imagen NSTextAttachment a mi cadena atribuida y tenerla centrada 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 etiqueta de texto de la celda.

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


@Travis tiene razón en que el desplazamiento es la fuente descendente. 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 gist .

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

Use 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


Encontré una solución perfecta para esto, funciona de maravilla 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 )


Podemos hacer una extensión en Swift 4 que genera 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 puede hacer la llamada enviando el nombre de la imagen y la fuente:

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

Y luego agregue el adjunto de imagen al atributo atribuido


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

Para el contexto, estoy renderizando un UILabel para usar como imagen adjunta, luego establezco los límites de esta manera: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height) y las líneas de base del texto dentro de la imagen de la etiqueta y el texto en la cadena de cadena atribuida, según se desee


Puede cambiar el NSTextAttachment subclasificando NSTextAttachment y anulando NSTextAttachment 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 simple vista" y si cambias la fuente o el tamaño del icono, probablemente quieras cambiar el origen Y. Pero no pude encontrar una mejor manera, excepto poniendo el icono en una vista de imagen separada (que tiene sus propias desventajas).


Puede usar 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 del archivo adjunto se representa en la línea de base del texto. Y el eje y se invierte como el sistema de coordenadas de gráficos principales. Si desea mover la imagen hacia arriba, establezca los bounds.origin.y en positivo.

La imagen debe estar alineada verticalmente al centro con la altura del texto. Por lo tanto, debemos establecer los bounds.origin.y en (capHeight - imageHeight)/2 .

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

En su caso, la altura de la imagen es mayor que la altura de la fuente. Pero puedes usarlo de la misma manera. El desplazamiento y el valor será negativo. Y se presentará desde abajo de la línea de base.


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


Si tiene un ascendente muy grande y desea centrar la imagen (centro de la altura de la tapa) como yo, intente 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 de y es como la imagen de abajo

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

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

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


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 todo el tamaño de las 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; }