Centra l'immagine NSTextAttachment accanto a UILabel a riga singola


117

Vorrei aggiungere NSTextAttachmentun'immagine alla mia stringa attribuita e centrarla verticalmente.

Ho usato il seguente codice per creare la mia stringa:

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

Tuttavia, l'immagine sembra allinearsi alla parte superiore della cella textLabel.

screenshot del problema di offset dell'allegato di testo

Come posso cambiare il rettangolo in cui viene disegnato l'allegato?


Ho una classe di categoria per avere NSString con UIImage e viceversa. github.com/Pradeepkn/TextWithImage Divertiti.
PradeepKN

Risposte:


59

È possibile modificare il rect creando sottoclassi NSTextAttachmente sovrascrivendo attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:. Esempio:

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

Non è una soluzione perfetta. Devi capire l'origine Y "a occhio" e se cambi il carattere o la dimensione dell'icona, probabilmente vorrai cambiare l'origine Y. Ma non sono riuscito a trovare un modo migliore, se non mettendo l'icona in una vista immagine separata (che ha i suoi svantaggi).


2
Non ho idea del motivo per cui l'hanno votato, mi ha aiutato così +1
Jasper

L'origine Y è il discendente del carattere. Vedi la mia risposta di seguito.
phatmann

4
La risposta di Travis è una soluzione più pulita senza sottoclassi.
SwampThingTom

Per maggiori dettagli, controlla la risposta di @ mg-han su stackoverflow.com/a/45161058/280503 (A mio parere dovrebbe essere la risposta selezionata alla domanda.)
gardenofwine

191

Puoi usare capHeight del carattere.

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

veloce

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)

L'immagine dell'allegato viene visualizzata sulla linea di base del testo. E l'asse y è invertito come il sistema di coordinate grafico principale. Se vuoi spostare l'immagine verso l'alto, imposta bounds.origin.ysu positivo.

L'immagine dovrebbe essere allineata verticalmente al centro con il capHeight del testo. Quindi dobbiamo impostare il filebounds.origin.y su (capHeight - imageHeight)/2.

Evitando qualche effetto frastagliato sull'immagine, dovremmo arrotondare la parte frazione di y. Ma i caratteri e le immagini sono generalmente piccoli, anche 1 pixel di differenza fa sembrare l'immagine disallineata. Quindi ho applicato la funzione round prima di dividere. Rende la parte frazione del valore y a .0 o .5

Nel tuo caso, l'altezza dell'immagine è maggiore del capHeight del carattere. Ma puoi usare allo stesso modo. Il valore di offset y sarà negativo. E sarà disposto dal basso della linea di base.

inserisci qui la descrizione dell'immagine


GRAZIE!!!!!!!!!!!!!!
Alex

107

Provare - [NSTextAttachment bounds] . Nessuna sottoclasse richiesta.

Per il contesto, eseguo il rendering di un UILabelda utilizzare come immagine dell'allegato, quindi imposto i limiti in questo modo: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)e le linee di base del testo all'interno dell'immagine dell'etichetta e il testo nella stringa attribuita si allineano come desiderato.


Funziona finché non è necessario ridimensionare l'immagine.
phatmann

12
Per Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Andy

Fantastico, grazie! Non conoscevo la descenderproprietà di UIFont!
Ben

61

Ho trovato una soluzione perfetta a questo, funziona a meraviglia per me però, tuttavia devi provarlo tu stesso (probabilmente la costante dipende dalla risoluzione del dispositivo e forse qualunque;)

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
}

Dovrebbe funzionare e non dovrebbe essere sfocato in alcun modo (grazie a CGRectIntegral)


Grazie per aver postato questo, mi ha portato a un approccio abbastanza buono. Ho notato che stai aggiungendo un 2 un po 'magico al tuo calcolo delle coordinate y.
Ben

2
Ecco cosa ho usato per il mio calcolo y: descender + (abs (descender) + capHeight) / 2 - iconHeight / 2
Ben

Perché il +2 per l'origine Y?
William LeGate,

@WilliamLeGate Non lo so davvero, l'ho appena provato e ha funzionato per tutte le dimensioni dei caratteri per cui ho testato (quelli di cui avevo bisogno) ..
borchero

Dannazione ... Questa risposta è sorprendente.
GGirotto

38

Che dire:

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

Nessuna sottoclasse necessaria


2
Funziona meglio rispetto all'utilizzo di self.font.descender (che ha un valore predefinito di ~ 4 sul simulatore di iPhone 4s con iOS 8). -10 sembra una migliore approssimazione per lo stile / dimensione del carattere predefinito.
Kedar Paranjape

10

@Travis è corretto che l'offset è il discendente del carattere. Se è necessario ridimensionare anche l'immagine, sarà necessario utilizzare una sottoclasse di NSTextAttachment. Di seguito è riportato il codice, che è stato ispirato da questo articolo . L'ho postato anche come sintesi .

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

Usa come segue:

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

10

Se hai un ascendente molto grande e vuoi centrare l'immagine (centro dell'altezza del cappuccio) come me prova questo

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
}

Il calcolo y è come nell'immagine qui sotto

inserisci qui la descrizione dell'immagine

Nota che il valore y è 0 perché vogliamo che l'immagine si sposti verso il basso rispetto all'origine

Se vuoi che sia al centro dell'intera etichetta, usa questo valore y:

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

7

Possiamo creare un'estensione in swift 4 che genera un allegato con un'immagine centrata come questa:

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

Quindi puoi effettuare la chiamata inviando il nome dell'immagine e il carattere:

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

E quindi aggiungi imageAttachment a attributeString


1

Nel mio caso la chiamata a sizeToFit () ha aiutato. In swift 5.1

All'interno della tua etichetta personalizzata:

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

Si prega di utilizzare -lineFrag.size.height / 5.0 per l'altezza dei limiti. Questo centra esattamente l'immagine e allineato con il testo per tutte le dimensioni dei caratteri

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.0non è corretto. Invece è il discendente del carattere.
phatmann
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.