Analisi HTML in NSAttributedText - come impostare il carattere?


133

Sto cercando di ottenere uno snippet di testo formattato in HTML per visualizzarlo su un iPhone in UITableViewCell.

Finora ho questo:

NSError* error;
NSString* source = @"<strong>Nice</strong> try, Phil";
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
                                                              documentAttributes:nil error:&error];

Questo tipo di opere. Ricevo del testo in grassetto "Nice"! Ma ... imposta anche il carattere come Times Roman! Questo non è il carattere che voglio. Sto pensando di dover impostare qualcosa nel documento Attributi, ma non riesco a trovare alcun esempio da nessuna parte.


1
Nota: NSHTMLTextDocumentType può essere potenzialmente lento. Vedere stackoverflow.com/questions/21166752/...
finneycanhelp

IMPORTANTE: se si utilizza carattere personalizzato avete bisogno di vedere questa risposta stackoverflow.com/a/60786178/1223897
Yuvrajsinh

Risposte:


118

Versione Swift 2 , basata sulla risposta data da Javier Querol

extension UILabel {
    func setHTMLFromString(text: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>", text) as String

        let attrStr = try! NSAttributedString(
            data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

Swift 3.0 e iOS 9+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

Swift 5 e iOS 11+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

1
Non cambiando i caratteri correnti, questo è quello che stavo cercando, grazie amico!
Mohammad Zaid Pathan,

2
Questo funziona È possibile impostare subito la stringa modificata su una stringa e omettere l'inizializzazione di NSString, ad esempio "<span style = \" font-family: (self.font! .FontName); font-size: (self.font! .pointSize) \ "> (testo) </span>"
Matthew Korporaal,

2
Per farlo funzionare (che funziona davvero molto bene) ho dovuto aggiungere virgolette singole attorno al valore della famiglia di caratteri, quindi <div style = \ "font-family: '(self.font! .FontName)'; ....
Geraldcor,

4
Penso che, da iOS9, è meglio usarlo font-family: '-apple-system', 'HelveticaNeue';(che funziona ed è anche compatibile con le versioni precedenti). Se si supporta solo iOS9 font-family: -apple-system;può essere utilizzato
Daniel

1
È utile anche impostare il colore del testo, basta aggiungere il colore all'attributo di stile con valore in formato stringa esadecimale color: #000000. Vedi questo link per convertire UIColor in stringa esadecimale: gist.github.com/yannickl/16f0ed38f0698d9a8ae7
Miroslav Hrivik

115
#import "UILabel+HTML.h"

@implementation UILabel (HTML)

- (void)jaq_setHTMLFromString:(NSString *)string {

    string = [string stringByAppendingString:[NSString stringWithFormat:@"<style>body{font-family: '%@'; font-size:%fpx;}</style>",
                                              self.font.fontName,
                                              self.font.pointSize]];
    self.attributedText = [[NSAttributedString alloc] initWithData:[string dataUsingEncoding:NSUnicodeStringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                                documentAttributes:nil
                                                             error:nil];
}


@end

In questo modo non è necessario specificare il tipo di carattere desiderato, saranno necessari il carattere e la dimensione dell'etichetta.


2
Questo è molto elegante!
Merlevede,

2
Bello. Anche se ha più senso come categoria su NSAttributedString imo.
Dimitris,

@Javier Querol Allora come gestire i link tintinnio?
KarenAnne,

Si codifica la stringa in dati con NSUnicodeStringEncodinge quindi si codificano i dati in caratteri con NSUTF8StringEncoding. Va bene?
Timur Bernikovich,

1
scusa - questa soluzione NON funziona per me, il carattere non è impostato sul carattere desiderato. - Invece di utilizzare self.font.fontName e invece di self.font.familyName viene impostato il carattere desiderato ma i tag HTML non vengono conservati. vedi la soluzione che segue che funziona e non si basa sull'uso di stili HTML di alcun tipo. -rrh
Richie Hyatt

49

In realtà ho trovato una soluzione funzionante a questo problema:

Modifica del carattere nella stringa di risposta HTML prima che venga analizzata.

NSString *aux = [NSString stringWithFormat:@"<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">%@</span>", htmlResponse];

Esempio:

NSString *aux = [NSString stringWithFormat:@"<span style=\"font-family: HelveticaNeue-Thin; font-size: 17\">%@</span>", [response objectForKey:@"content"]];

Versione rapida:

let aux = "<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">\(htmlResponse)</span>"

4
La soluzione più semplice .. Altre risposte potrebbero essere corrette ma fare le cose in modo più difficile non è intelligente .. :)
Sameera Chathuranga,

2
Risposta migliore e intelligente
Tariq

Risposta più intelligente, d'accordo! Saluti
Jim Tierney,

ciao, in realtà funziona alla grande, ma se riconverto questo testo attribuito in html, la dimensione del carattere aumenta in quel html
Mehul Thakkar,

1
In realtà grazie all'aiuto di altri post su StackOverflow .. sono in grado di convertire il testo attribuito in html e tutto ciò che funziona bene a parte le dimensioni del carattere, che viene quasi raddoppiato
Mehul Thakkar

41

Capito. Un po 'di orso, e forse non la risposta migliore.

Questo codice passerà attraverso tutte le modifiche al carattere. So che sta usando "Times New Roman" e "Times New Roman BoldMT" per i caratteri. Ma a prescindere, questo troverà i caratteri in grassetto e mi permetterò di ripristinarli. Posso anche ripristinare le dimensioni mentre ci sono.

Sinceramente spero / penso che ci sia un modo per impostarlo al momento dell'analisi, ma non riesco a trovarlo se c'è.

    NSRange range = (NSRange){0,[str length]};
    [str enumerateAttribute:NSFontAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
        UIFont* currentFont = value;
        UIFont *replacementFont = nil;

        if ([currentFont.fontName rangeOfString:@"bold" options:NSCaseInsensitiveSearch].location != NSNotFound) {
            replacementFont = [UIFont fontWithName:@"HelveticaNeue-CondensedBold" size:25.0f];
        } else {
            replacementFont = [UIFont fontWithName:@"HelveticaNeue-Thin" size:25.0f];
        }

        [str addAttribute:NSFontAttributeName value:replacementFont range:range];
    }];

2
Cercare la parola "BOLD" nel nome del font è un kludge orribile! Ciò rompe anche altri attributi di carattere come il corsivo.
HughHughTeotl,

1
Un approccio più generico è guardare i tratti dei caratteri durante l'enumerazione e creare un carattere con gli stessi tratti. Pubblicherò il mio codice qui sotto.
markiv

33

Un approccio più generico è guardare i tratti dei caratteri durante l'enumerazione e creare un carattere con gli stessi tratti (grassetto, corsivo, ecc.):

extension NSMutableAttributedString {

    /// Replaces the base font (typically Times) with the given font, while preserving traits like bold and italic
    func setBaseFont(baseFont: UIFont, preserveFontSizes: Bool = false) {
        let baseDescriptor = baseFont.fontDescriptor
        let wholeRange = NSRange(location: 0, length: length)
        beginEditing()
        enumerateAttribute(.font, in: wholeRange, options: []) { object, range, _ in
            guard let font = object as? UIFont else { return }
            // Instantiate a font with our base font's family, but with the current range's traits
            let traits = font.fontDescriptor.symbolicTraits
            guard let descriptor = baseDescriptor.withSymbolicTraits(traits) else { return }
            let newSize = preserveFontSizes ? descriptor.pointSize : baseDescriptor.pointSize
            let newFont = UIFont(descriptor: descriptor, size: newSize)
            self.removeAttribute(.font, range: range)
            self.addAttribute(.font, value: newFont, range: range)
        }
        endEditing()
    }
}

Anche se questo non è molto conciso, sembra più stabile che risolvere il problema con il wrapping di HTML con più HTML.
Syvex,

23

Sì, esiste una soluzione più semplice. Imposta il carattere nella fonte html!

NSError* error;
NSString* source = @"<strong>Nice</strong> try, Phil";
source = [source stringByAppendingString:@"<style>strong{font-family: 'Avenir-Roman';font-size: 14px;}</style>"];
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
                                                              documentAttributes:nil error:&error];

Spero che questo ti aiuti.


23

Swift 4+ aggiornamento dell'estensione UILabel

extension UILabel {
    func setHTMLFromString(text: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>" as NSString, text)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: String.Encoding.unicode.rawValue, allowLossyConversion: true)!,
            options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

iOS 9+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>" as NSString, htmlText) as String


        //process collection values
        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
            documentAttributes: nil)


        self.attributedText = attrStr
    }
}

8

Le risposte soprattutto funzionano bene se stai eseguendo la conversione contemporaneamente alla creazione di NSAttributedString. Ma penso che una soluzione migliore, che funziona sulla stringa stessa e quindi non necessita dell'accesso all'input, è la seguente categoria:

extension NSMutableAttributedString
{
    func convertFontTo(font: UIFont)
    {
        var range = NSMakeRange(0, 0)

        while (NSMaxRange(range) < length)
        {
            let attributes = attributesAtIndex(NSMaxRange(range), effectiveRange: &range)
            if let oldFont = attributes[NSFontAttributeName]
            {
                let newFont = UIFont(descriptor: font.fontDescriptor().fontDescriptorWithSymbolicTraits(oldFont.fontDescriptor().symbolicTraits), size: font.pointSize)
                addAttribute(NSFontAttributeName, value: newFont, range: range)
            }
        }
    }
}

Usare come:

let desc = NSMutableAttributedString(attributedString: *someNSAttributedString*)
desc.convertFontTo(UIFont.systemFontOfSize(16))

Funziona su iOS 7+


Ho cercato dappertutto per questo ... !! Grazie..!
Irshad Qureshi,

5

Miglioramento della soluzione di Victor, incluso il colore:

extension UILabel {
      func setHTMLFromString(text: String) {
          let modifiedFont = NSString(format:"<span style=\"color:\(self.textColor.toHexString());font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>", text) as String

          let attrStr = try! NSAttributedString(
              data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
              options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
              documentAttributes: nil)

          self.attributedText = attrStr
      }
  }

Perché questo funzioni, avrai anche bisogno di YLColor.swift della conversione da uicolore a esadecimale https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7


4

L'uso di NSHTMLTextDocumentType è uno stile lento e difficile da controllare. Ti suggerisco di provare la mia biblioteca che si chiama Atributika. Ha il suo parser molto veloce. Inoltre puoi avere qualsiasi nome di tag e definire qualsiasi stile per loro.

Esempio:

let str = "<strong>Nice</strong> try, Phil".style(tags:
    Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString

label.attributedText = str

Puoi trovarlo qui https://github.com/psharanda/Atributika


4

Unendo le risposte di tutti, ho creato due estensioni che consentono di impostare un'etichetta con testo HTML. Alcune risposte sopra non hanno interpretato correttamente la famiglia di caratteri nelle stringhe attribuite. Altri erano incompleti per i miei bisogni o fallivano in altri modi. Fammi sapere se c'è qualcosa su cui vorresti che migliorassi.

Spero che questo aiuti qualcuno.

extension UILabel {
    /// Sets the label using the supplied html, using the label's font and font size as a basis.
    /// For predictable results, using only simple html without style sheets.
    /// See /programming/19921972/parsing-html-into-nsattributedtext-how-to-set-font
    ///
    /// - Returns: Whether the text could be converted.
    @discardableResult func setAttributedText(fromHtml html: String) -> Bool {
        guard let data = html.data(using: .utf8, allowLossyConversion: true) else {
            print(">>> Could not create UTF8 formatted data from \(html)")
            return false
        }

        do {
            let mutableText = try NSMutableAttributedString(
                data: data,
                options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
                documentAttributes: nil)
            mutableText.replaceFonts(with: font)
            self.attributedText = mutableText
            return true
        } catch (let error) {
            print(">>> Could not create attributed text from \(html)\nError: \(error)")
            return false
        }
    }
}

extension NSMutableAttributedString {

    /// Replace any font with the specified font (including its pointSize) while still keeping
    /// all other attributes like bold, italics, spacing, etc.
    /// See /programming/19921972/parsing-html-into-nsattributedtext-how-to-set-font
    func replaceFonts(with font: UIFont) {
        let baseFontDescriptor = font.fontDescriptor
        var changes = [NSRange: UIFont]()
        enumerateAttribute(.font, in: NSMakeRange(0, length), options: []) { foundFont, range, _ in
            if let htmlTraits = (foundFont as? UIFont)?.fontDescriptor.symbolicTraits,
                let adjustedDescriptor = baseFontDescriptor.withSymbolicTraits(htmlTraits) {
                let newFont = UIFont(descriptor: adjustedDescriptor, size: font.pointSize)
                changes[range] = newFont
            }
        }
        changes.forEach { range, newFont in
            removeAttribute(.font, range: range)
            addAttribute(.font, value: newFont, range: range)
        }
    }
}

l'unica soluzione completa che funziona per UILabele UITextView. Grazie!
Radu Ursache,

3

Grazie per le risposte, mi è piaciuta molto l'estensione ma non mi sono ancora convertita in rapido. Per quei vecchi scolari ancora nell'Obiettivo-C questo dovrebbe aiutare un po ': D

-(void) setBaseFont:(UIFont*)font preserveSize:(BOOL) bPreserve {

UIFontDescriptor *baseDescriptor = font.fontDescriptor;

[self enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {

    UIFont *font = (UIFont*)value;
    UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
    UIFontDescriptor *descriptor = [baseDescriptor fontDescriptorWithSymbolicTraits:traits];
    UIFont *newFont = [UIFont fontWithDescriptor:descriptor size:bPreserve?baseDescriptor.pointSize:descriptor.pointSize];

    [self removeAttribute:NSFontAttributeName range:range];
    [self addAttribute:NSFontAttributeName value:newFont range:range];

}];    } 

Buona programmazione! --Greg Frame


1
Grazie a Dio per i vecchi scolari! :-)
Josef Rysanek,

1

Estensione Swift 3 String che include un carattere nullo. La proprietà senza carattere è presa da un'altra domanda SO, non ricordare quale :(

extension String {
    var html2AttributedString: NSAttributedString? {
        guard let data = data(using: .utf8) else {
            return nil
        }

        do {
            return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
        }
        catch {
            print(error.localizedDescription)
            return nil
        }
    }

    public func getHtml2AttributedString(font: UIFont?) -> NSAttributedString? {
        guard let font = font else {
            return html2AttributedString
        }

        let modifiedString = "<style>body{font-family: '\(font.fontName)'; font-size:\(font.pointSize)px;}</style>\(self)";

        guard let data = modifiedString.data(using: .utf8) else {
            return nil
        }

        do {
            return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
        }
        catch {
            print(error)
            return nil
        }
    }
}

0

Ecco un'estensione per NSString che restituisce un NSAttributedString usando Objective-C.

Gestisce correttamente una stringa con tag HTML e imposta il carattere e il colore del carattere desiderati preservando i tag HTML inclusi BOLD, ITALICS ...

Soprattutto, non si basa su alcun marcatore HTML per impostare gli attributi del carattere.

@implementation NSString (AUIViewFactory)

- (NSAttributedString*)attributedStringFromHtmlUsingFont:(UIFont*)font fontColor:(UIColor*)fontColor
{
    NSMutableAttributedString* mutableAttributedString = [[[NSAttributedString alloc] initWithData:[self dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding)} documentAttributes:nil error:nil] mutableCopy]; // parse text with html tags into a mutable attributed string
    [mutableAttributedString beginEditing];
    // html tags cause font ranges to be created, for example "This text is <b>bold</b> now." creates three font ranges: "This text is " , "bold" , " now."
    [mutableAttributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mutableAttributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL* stop)
    { // iterate every font range, change every font to new font but preserve symbolic traits such as bold and italic (underline and strikethorugh are preserved automatically), set font color
        if (value)
        {
            UIFont* oldFont = (UIFont*)value;
            UIFontDescriptor* fontDescriptor = [font.fontDescriptor fontDescriptorWithSymbolicTraits:oldFont.fontDescriptor.symbolicTraits];
            UIFont* newFont = [UIFont fontWithDescriptor:fontDescriptor size:font.pointSize];
            [mutableAttributedString removeAttribute:NSFontAttributeName range:range]; // remove the old font attribute from this range
            [mutableAttributedString addAttribute:NSFontAttributeName value:newFont range:range]; // add the new font attribute to this range
            [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:fontColor range:range]; // set the font color for this range
        }
    }];
    [mutableAttributedString endEditing];
    return mutableAttributedString;
}

@end

-3

In realtà, esiste un modo ancora più semplice e pulito. Basta impostare il carattere dopo aver analizzato l'HTML:

 NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
                                                                     options:@{
                                                                               NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                               NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                                          documentAttributes:nil error:nil];
    [text addAttributes:@{NSFontAttributeName: [UIFont fontWithName:@"Lato-Regular" size:20]} range:NSMakeRange(0, text.length)];

14
Funziona, ma perderai grassetto e corsivo <b> e <u> perché sono sovrascritti dal carattere.
Mr. Zystem,
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.