Análisis de HTML en NSAttributedText: ¿cómo configurar la fuente?

133

Estoy tratando de obtener un fragmento de texto formateado en html para que se vea bien en un iPhone en un UITableViewCell.

Hasta ahora tengo esto:

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

Este tipo de trabajos. ¡Recibo un texto que tiene 'Nice' en negrita! Pero ... ¡también establece que la fuente sea Times Roman! Esta no es la fuente que quiero. Estoy pensando que necesito establecer algo en el documento Atributos, pero no puedo encontrar ningún ejemplo en ningún lado.

phil
fuente
1
Nota: NSHTMLTextDocumentType puede ser potencialmente lento. Ver stackoverflow.com/questions/21166752/…
finneycanhelp
IMPORTANTE: Si está utilizando una fuente personalizada, necesita ver esta respuesta stackoverflow.com/a/60786178/1223897
Yuvrajsinh

Respuestas:

118

Versión Swift 2 , basada en la respuesta de 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 y 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 y 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
    }
}
Víctor Albertos
fuente
1
Sin cambiar las fuentes actuales, esto es lo que estaba buscando, ¡gracias hombre!
Mohammad Zaid Pathan
2
Esto funciona. Puede establecer la cadena modificada en una cadena de inmediato y omitir la inicialización de NSString, es decir, "<span style = \" font-family: (self.font! .FontName); tamaño de fuente: (self.font! .pointSize) \ "> (texto) </span>"
Matthew Korporaal
2
Para que esto funcione, (que funciona realmente muy bien) tuve que agregar comillas simples alrededor del valor de font-family para que <div style = \ "font-family: '(self.font! .FontName)'; ....
geraldcor
44
Creo que, desde iOS9, es mejor usarlo font-family: '-apple-system', 'HelveticaNeue';(que funciona y también es compatible con versiones anteriores). Si solo es compatible con iOS9 font-family: -apple-system;se puede usar
Daniel
1
También es útil la capacidad de establecer el color del texto, solo agregue color al atributo de estilo con valor en formato de cadena hexadecimal color: #000000. Vea este enlace para convertir UIColor a cadena hexadecimal: 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

De esta manera, no necesita especificar qué fuente desea, tomará la fuente y el tamaño de la etiqueta.

Javier Querol
fuente
2
¡Esto es muy elegante!
Merlevede
2
Agradable. Aunque tiene más sentido como categoría en NSAttributedString imo.
Dimitris
@Javier Querol Entonces, ¿cómo manejar los enlaces de enlaces?
KarenAnne
Codifica la cadena de datos con NSUnicodeStringEncodingy luego codifica los datos de nuevo a los caracteres con NSUTF8StringEncoding. ¿Está bien?
Timur Bernikovich
1
lo siento, esta solución NO me funciona, la fuente no está configurada para la fuente deseada. - en lugar de usar self.font.fontName y en su lugar usar self.font.familyName establece la fuente deseada pero las etiquetas HTML no se conservan. vea la solución a continuación que funciona y no depende del uso de estilos HTML de ningún tipo. -rrh
Richie Hyatt
49

En realidad encontré una solución funcional a este problema:

Cambiar la fuente en su cadena de respuesta HTML antes de que se analice.

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

Ejemplo:

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

Versión rápida:

let aux = "<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">\(htmlResponse)</span>"
Teodor Ciuraru
fuente
44
La solución más fácil ... Otras respuestas pueden corregirse, pero hacer las cosas de la manera más difícil no es inteligente ... :)
Sameera Chathuranga
2
La mejor y más inteligente respuesta
Tariq
La respuesta más inteligente, de acuerdo! Saludos
Jim Tierney
hola, en realidad esto funciona muy bien, pero si vuelvo a convertir este texto atribuido a html, el tamaño de la fuente aumenta en ese html
Mehul Thakkar
1
En realidad de la ayuda de otros mensajes más stackoverflow .. soy capaz de convertir el texto attriubuted a html y todo funciona bien, aparte de tamaño de fuente, que está consiguiendo casi se duplicó
Mehul Thakkar
41

Lo averigué. Poco oso, y tal vez no sea la mejor respuesta.

Este código pasará por todos los cambios de fuente. Sé que está usando "Times New Roman" y "Times New Roman BoldMT" para las fuentes. Pero independientemente, esto encontrará las fuentes en negrita y me permitirá restablecerlas. También puedo restablecer el tamaño mientras estoy en eso.

Sinceramente, espero / creo que hay una manera de configurar esto en el momento del análisis, pero no puedo encontrarlo si lo hay.

    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];
    }];
phil
fuente
2
¡Buscar la palabra "BOLD" en el nombre de la fuente es un error horrible! Esto también rompe otros atributos de fuente, como cursiva.
HughHughTeotl
1
Un enfoque más genérico es mirar los rasgos de fuente mientras se enumera y crear una fuente con los mismos rasgos. Publicaré mi código a continuación.
markiv
33

Un enfoque más genérico es mirar los rasgos de la fuente mientras se enumera y crear una fuente con los mismos rasgos (negrita, cursiva, etc.):

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()
    }
}
markiv
fuente
Aunque esto no es muy conciso, parece más estable que hackear el problema de envolver html con más html.
syvex
23

Sí, hay una solución más fácil. ¡Establezca la fuente en la fuente 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];

Espero que esto ayude.

Max
fuente
23

Actualización rápida de 4+ de la extensión 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
    }
}
Rafat touqir Rafsun
fuente
8

Las respuestas sobre todo funcionan bien si está haciendo la conversión al mismo tiempo que crea la NSAttributedString. Pero creo que una mejor solución, que funciona en la cadena en sí y, por lo tanto, no necesita acceso a la entrada, es la siguiente categoría:

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

Usar como:

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

Funciona en iOS 7+

HughHughTeotl
fuente
Busqué por todas partes para esto ... !! Gracias..!
Irshad Qureshi
5

Mejora en la solución de Victor, incluido el color:

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

Para que esto funcione, también necesitará YLColor.swift de la conversión de uicolor a hexadecimal https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7

Juan Carlos Ospina Gonzalez
fuente
4

El uso de NSHTMLTextDocumentType es lento y difícil de controlar. Te sugiero que pruebes mi biblioteca que se llama Atributika. Tiene su propio analizador muy rápido. También puede tener cualquier nombre de etiqueta y definir cualquier estilo para ellos.

Ejemplo:

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

label.attributedText = str

Lo puedes encontrar aquí https://github.com/psharanda/Atributika

Pavel Sharanda
fuente
4

Uniendo las respuestas de todos, hice dos extensiones que permiten configurar una etiqueta con texto html. Algunas respuestas anteriores no interpretaron correctamente la familia de fuentes en las cadenas atribuidas. Otros estaban incompletos para mis necesidades o fallaron de otras maneras. Avíseme si hay algo que le gustaría que mejorara.

Espero que esto ayude a alguien.

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)
        }
    }
}
dwsolberg
fuente
La única solución completa que funciona para UILabely UITextView. ¡Gracias!
Radu Ursache
3

Gracias por las respuestas, me gustó mucho la extensión pero aún no me he convertido a swift. Para aquellos viejos estudiantes que todavía están en Objective-C, esto debería ayudar un poco: 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];

}];    } 

¡Feliz codificación! --Greg Frame

Greg Frame
fuente
1
¡Gracias a Dios por los viejos escolares! :-)
Josef Rysanek
1

Extensión Swift 3 String que incluye una fuente nula. La propiedad sin fuente se toma de otra pregunta SO, no recuerdo cuál :(

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
        }
    }
}
shelll
fuente
0

Aquí hay una extensión para NSString que devuelve una NSAttributedString usando Objective-C.

Maneja correctamente una cadena con etiquetas HTML y establece la fuente y el color de fuente deseados al tiempo que conserva las etiquetas HTML, incluidas BOLD, ITALICS ...

Lo mejor de todo es que no se basa en ningún marcador HTML para establecer los atributos de fuente.

@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
Richie Hyatt
fuente
-3

En realidad, existe una forma aún más fácil y limpia. Simplemente configure la fuente después de analizar el 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)];
Erik
fuente
14
Eso funciona, pero perderá negrita y cursiva <b> y <u> porque la fuente las sobrescribe.
Sr. Zystem