Analyser le HTML dans NSAttributedText - comment définir la police?

133

J'essaie d'obtenir un extrait de texte formaté en html pour s'afficher correctement sur un iPhone dans un UITableViewCell.

Jusqu'à présent, j'ai ceci:

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

Ce genre de travaux. J'obtiens un texte qui a «Nice» en gras! Mais ... cela définit également la police comme Times Roman! Ce n'est pas la police que je veux. Je pense que j'ai besoin de définir quelque chose dans le documentAttributes, mais je ne trouve aucun exemple nulle part.

phil
la source
1
Remarque: NSHTMLTextDocumentType peut être lent. Voir stackoverflow.com/questions/21166752/…
finneycanhelp
IMPORTANT: Si vous utilisez une police personnalisée, vous devez voir cette réponse stackoverflow.com/a/60786178/1223897
Yuvrajsinh

Réponses:

118

Version Swift 2 , basée sur la réponse 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 et 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 et 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
la source
1
Ne pas changer les polices actuelles, c'est ce que je cherchais, merci mec!
Mohammad Zaid Pathan
2
Cela marche. Vous pouvez définir la chaîne modifiée sur une chaîne tout de suite et omettre l'initialisation NSString, c'est-à-dire "<span style = \" font-family: (self.font! .FontName); font-size: (self.font! .pointSize) \ "> (text) </span>"
Matthew Korporaal
2
Pour faire ce travail, (qui fonctionne vraiment très bien), j'ai dû ajouter des guillemets simples autour de la valeur font-family donc <div style = \ "font-family: '(self.font! .FontName)'; ....
geraldcor
4
Je pense que, depuis iOS9, il est préférable d'utiliser font-family: '-apple-system', 'HelveticaNeue';(ce qui fonctionne et est également rétrocompatible). Si vous ne supportez que iOS9 font-family: -apple-system;peut être utilisé
Daniel
1
La possibilité de définir la couleur du texte est également pratique, il suffit d'ajouter de la couleur à l'attribut de style avec une valeur au format de chaîne hexadécimale color: #000000. Voir ce lien pour convertir UIColor en chaîne hexadécimale: 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 cette façon, vous n'avez pas besoin de spécifier la police de votre choix, elle prendra la police et la taille de l'étiquette.

Javier Querol
la source
2
C'est très élégant!
Merlevede
2
Agréable. Bien que cela ait plus de sens en tant que catégorie sur NSAttributedString imo.
Dimitris
@Javier Querol Alors comment gérer les clinks de lien?
KarenAnne
Vous encodez une chaîne en données avec NSUnicodeStringEncoding, puis vous encodez les données en caractères avec NSUTF8StringEncoding. Est-ce que c'est bon?
Timur Bernikovich
1
désolé - cette solution ne fonctionne PAS pour moi, la police n'est pas définie sur la police souhaitée. - au lieu d'utiliser self.font.fontName et à la place d'utiliser self.font.familyName définit la police souhaitée, mais les balises HTML ne sont pas conservées. voir la solution ci-dessous qui fonctionne et ne repose pas sur l'utilisation de styles HTML d'aucune sorte. -rrh
Richie Hyatt
49

J'ai en fait trouvé une solution de travail à ce problème:

Changer la police de votre chaîne de réponse HTML avant qu'elle ne soit analysée.

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

Exemple:

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

Version Swift:

let aux = "<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">\(htmlResponse)</span>"
Teodor Ciuraru
la source
4
Solution la plus simple .. D'autres réponses peuvent corriger mais faire les choses de manière plus difficile n'est pas intelligent .. :)
Sameera Chathuranga
2
Meilleure réponse intelligente
Tariq
Réponse la plus intelligente, d'accord! Cheers
Jim Tierney
bonjour, en fait, cela fonctionne très bien, mais si je convertis ce texte attribué en html, la taille de la police augmente dans ce html
Mehul Thakkar
1
En fait, grâce à l'aide d'autres publications sur stackoverflow .. Je suis capable de convertir du texte attriubué en HTML et tout fonctionne bien à part la taille de la police, qui est presque doublée
Mehul Thakkar
41

Deviner. Un peu d'ours, et peut-être pas la meilleure réponse.

Ce code passera en revue toutes les modifications de police. Je sais qu'il utilise "Times New Roman" et "Times New Roman BoldMT" pour les polices. Mais peu importe, cela trouvera les polices en gras et me permettra de les réinitialiser. Je peux également réinitialiser la taille pendant que j'y suis.

J'espère / pense honnêtement qu'il existe un moyen de configurer cela au moment de l'analyse, mais je ne peux pas le trouver s'il y en a.

    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
la source
2
La recherche du mot "BOLD" dans le nom de la police est un horrible kludge! Cela casse également d'autres attributs de police tels que l'italique.
HughHughTeotl
1
Une approche plus générique consiste à examiner les traits de police lors de l'énumération et à créer une police avec les mêmes traits. Je posterai mon code ci-dessous.
markiv
33

Une approche plus générique consiste à examiner les traits de police lors de l'énumération et à créer une police avec les mêmes traits (gras, italique, 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
la source
Même si ce n'est pas très concis, cela semble plus stable que le piratage du problème lié à l'enveloppement du HTML avec plus de HTML.
syvex du
23

Oui, il existe une solution plus simple. Définissez la police dans la source 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];

J'espère que cela t'aides.

Max
la source
23

Mise à jour Swift 4+ de l' extension 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
la source
8

Les réponses avant tout fonctionnent bien si vous effectuez la conversion en même temps que la création du fichier NSAttributedString. Mais je pense qu'une meilleure solution, qui fonctionne sur la chaîne elle-même et n'a donc pas besoin d'accéder à l'entrée, est la catégorie suivante:

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

Utilisé comme:

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

Fonctionne sur iOS 7+

HughHughTeotl
la source
J'ai cherché partout pour ça ... !! Merci..!
Irshad Qureshi
5

Amélioration de la solution Victor, y compris la couleur:

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

Pour que cela fonctionne, vous aurez également besoin de YLColor.swift de la conversion uicolor en hexadécimal https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7

Juan Carlos Ospina Gonzalez
la source
4

L'utilisation de NSHTMLTextDocumentType est lente et difficile à contrôler les styles. Je vous suggère d'essayer ma bibliothèque qui s'appelle Atributika. Il a son propre analyseur très rapide. Vous pouvez également avoir n'importe quel nom de balise et définir n'importe quel style pour eux.

Exemple:

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

label.attributedText = str

Vous pouvez le trouver ici https://github.com/psharanda/Atributika

Pavel Sharanda
la source
4

En joignant les réponses de chacun, j'ai créé deux extensions qui permettent de définir une étiquette avec du texte html. Certaines réponses ci-dessus n'ont pas interprété correctement la famille de polices dans les chaînes attribuées. D'autres étaient incomplets pour mes besoins ou ont échoué d'une autre manière. Faites-moi savoir s'il y a quelque chose que vous aimeriez que j'améliore.

J'espère que ça aidera quelqu'un.

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
la source
la seule solution complète qui fonctionne pour UILabelet UITextView. Merci!
Radu Ursache
3

Merci pour les réponses, j'ai vraiment aimé l'extension mais je ne me suis pas encore convertie en swift. Pour ces anciens élèves encore en Objective-C, cela devrait aider un peu: 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];

}];    } 

Bon codage! --Greg Cadre

Greg Frame
la source
1
Merci mon Dieu pour les vieux lycéens! :-)
Josef Rysanek
1

Extension de chaîne Swift 3 incluant une police nulle. La propriété sans police est tirée d'une autre question SO, je ne me souviens pas laquelle :(

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
la source
0

Voici une extension pour NSString qui renvoie un NSAttributedString à l'aide d'Objective-C.

Il gère correctement une chaîne avec des balises HTML et définit la police et la couleur de police souhaitées tout en préservant les balises HTML, notamment BOLD, ITALICS ...

Mieux encore, il ne repose sur aucun marqueur HTML pour définir les attributs de police.

@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
la source
-3

En fait, il existe un moyen encore plus simple et plus propre. Définissez simplement la police après avoir analysé le 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
la source
14
Cela fonctionne, mais vous perdrez les caractères gras et italiques <b> et <u> car ceux-ci sont écrasés par la police.
M. Zystem