web-dev-qa-db-fra.com

NSAttributedString, changez la police globale MAIS garder tous les autres attributs?

Dites que j'ai une NSMutableAttributedString.

La chaîne a un mélange varié de mise en forme tout au long:

Voici un exemple:

Cette chaîne est diable pour changerdans iOS, il vraiment aspks .

Cependant, la police en elle-même n'est pas celle que vous voulez.

Je veux

change chaque caractère en une police spécifique (par exemple, Avenir)

mais,

garde chacune des autres attributions (gras, italique, couleurs, etc.) qui apparaissent tout au long de la chaîne, à divers endroits.

Comment faites-vous ça?


Remarque:

si vous ajoutez trivialement un attribut "Avenir" sur toute la plage: simplement supprime toutes les autres plages d'attributs , vous perdez toute mise en forme 

Pour être clair: malheureusement, les attributs sont pas , en fait "additifs".

20
Fattie

La réponse de rmaddy n'ayant pas fonctionné pour moi (f.fontDescriptor.withFace(font.fontName) ne conserve pas les traits gras), voici une version mise à jour de Swift 4 qui inclut également la mise à jour des couleurs:

extension NSMutableAttributedString {
    func setFontFace(font: UIFont, color: UIColor? = nil) {
        beginEditing()
        self.enumerateAttribute(.font, in: NSRange(location: 0, length: self.length)) { (value, range, stop) in
            if let f = value as? UIFont, let newFontDescriptor = f.fontDescriptor.withFamily(font.familyName).withSymbolicTraits(f.fontDescriptor.symbolicTraits) {
                let newFont = UIFont(descriptor: newFontDescriptor, size: font.pointSize)
                removeAttribute(.font, range: range)
                addAttribute(.font, value: newFont, range: range)
                if let color = color {
                    removeAttribute(.foregroundColor, range: range)
                    addAttribute(.foregroundColor, value: color, range: range)
                }
            }
        }
        endEditing()
    }
}

Remarques

Le problème avec f.fontDescriptor.withFace(font.fontName) est qu’il supprime les traits symboliques tels que italic, bold ou compressed, car il remplace pour une raison quelconque les traits avec des traits par défaut de cette police. Pourquoi cela m’échappe totalement, cela pourrait même être un oubli de la part d’Apple; ou ce n'est "pas un bug, mais une fonctionnalité", car nous obtenons gratuitement les traits de la nouvelle police. 

Nous devons donc créer un descripteur de police qui présente les traits symboliques du descripteur de police d'origine: .withSymbolicTraits(f.fontDescriptor.symbolicTraits). Props à rmaddy pour le code initial sur lequel j'ai itéré.

J'ai déjà envoyé cela dans une application de production dans laquelle nous analysons une chaîne HTML via NSAttributedString.DocumentType.html, puis en modifiant la police et la couleur via l'extension ci-dessus. Aucun problème jusqu'à présent.

29
manmal

Voici une implémentation beaucoup plus simple qui maintient tous les attributs en place, y compris tous les attributs de police, sauf que cela vous permet de changer la face de la police.

Notez que cela n’utilise que la police (nom) de la police transmise. La taille est conservée à partir de la police existante. Si vous souhaitez également remplacer toutes les tailles de police existantes par la nouvelle taille, remplacez f.pointSize par font.pointSize.

extension NSMutableAttributedString {
    func replaceFont(with font: UIFont) {
        beginEditing()
        self.enumerateAttribute(.font, in: NSRange(location: 0, length: self.length)) { (value, range, stop) in
            if let f = value as? UIFont {
                let ufd = f.fontDescriptor.withFamily(font.familyName).withSymbolicTraits(f.fontDescriptor.symbolicTraits)!
                let newFont = UIFont(descriptor: ufd, size: f.pointSize)
                removeAttribute(.font, range: range)
                addAttribute(.font, value: newFont, range: range)
            }
        }
        endEditing()
    }
}

Et pour l'utiliser:

let someMutableAttributedString = ... // some attributed string with some font face you want to change
someMutableAttributedString.replaceFont(with: UIFont.systemFont(ofSize: 12))
6
rmaddy

Important -

rmaddy a inventé une technique entièrement nouvelle pour résoudre ce problème gênant sous iOS.

La réponse de manmal est la version finale perfectionnée.

À titre purement pour le dossier historique , voici comment vous vous y prendrez pour le faire au bon vieux temps ...


// carefully convert to "our" font - "re-doing" any other formatting.
// change each section BY HAND.  total PITA.

func fixFontsInAttributedStringForUseInApp() {

    cachedAttributedString?.beginEditing()

    let rangeAll = NSRange(location: 0, length: cachedAttributedString!.length)

    var boldRanges: [NSRange] = []
    var italicRanges: [NSRange] = []

    var boldANDItalicRanges: [NSRange] = [] // WTF right ?!

    cachedAttributedString?.enumerateAttribute(
            NSFontAttributeName,
            in: rangeAll,
            options: .longestEffectiveRangeNotRequired)
                { value, range, stop in

                if let font = value as? UIFont {

                    let bb: Bool = font.fontDescriptor.symbolicTraits.contains(.traitBold)
                    let ii: Bool = font.fontDescriptor.symbolicTraits.contains(.traitItalic)

                    // you have to carefully handle the "both" case.........

                    if bb && ii {

                        boldANDItalicRanges.append(range)
                    }

                    if bb && !ii {

                        boldRanges.append(range)
                    }

                    if ii && !bb {

                        italicRanges.append(range)
                    }
                }
            }

    cachedAttributedString!.setAttributes([NSFontAttributeName: font_f], range: rangeAll)

    for r in boldANDItalicRanges {
        cachedAttributedString!.addAttribute(NSFontAttributeName, value: font_fBOTH, range: r)
    }

    for r in boldRanges {
        cachedAttributedString!.addAttribute(NSFontAttributeName, value: font_fb, range: r)
    }

    for r in italicRanges {
        cachedAttributedString!.addAttribute(NSFontAttributeName, value: font_fi, range: r)
    }

    cachedAttributedString?.endEditing()
}


Note de bas de page. Juste pour plus de clarté sur un point connexe. Ce genre de chose commence inévitablement par une chaîne HTML. Voici une remarque sur la façon de convertir une chaîne au format HTML en une variable NSattributedString .... vous vous retrouverez avec des plages d'attributs Nice (italique, gras, etc.) MAIS les polices seront des polices que vous ne voulez pas.

fileprivate extension String {
    func htmlAttributedString() -> NSAttributedString? {
        guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
        guard let html = try? NSMutableAttributedString(
            data: data,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
            documentAttributes: nil) else { return nil }
        return html
    }
}  

Même si cette partie du travail n’est pas anodine, le traitement prend un certain temps. En pratique, vous devez pour éviter cela pour éviter le scintillement.

2
Fattie

Obj-C version de la réponse de @ manmal

@implementation NSMutableAttributedString (Additions)

- (void)setFontFaceWithFont:(UIFont *)font color:(UIColor *)color {
    [self beginEditing];
    [self enumerateAttribute:NSFontAttributeName
                     inRange:NSMakeRange(0, self.length)
                     options:0
                  usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
                      UIFont *oldFont = (UIFont *)value;
                      UIFontDescriptor *newFontDescriptor = [[oldFont.fontDescriptor fontDescriptorWithFamily:font.familyName] fontDescriptorWithSymbolicTraits:oldFont.fontDescriptor.symbolicTraits];
                      UIFont *newFont = [UIFont fontWithDescriptor:newFontDescriptor size:font.pointSize];
                      if (newFont) {
                          [self removeAttribute:NSFontAttributeName range:range];
                          [self addAttribute:NSFontAttributeName value:newFont range:range];
                      }

                      if (color) {
                          [self removeAttribute:NSForegroundColorAttributeName range:range];
                          [self addAttribute:NSForegroundColorAttributeName value:newFont range:range];
                      }
                  }];
    [self endEditing];
}

@end
0
landonandrey