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