web-dev-qa-db-ja.com

NSAttributedString、フォント全体を変更しますが、他のすべての属性を保持しますか?

NSMutableAttributedStringがあるとします。

文字列にはさまざまな組み合わせ全体のフォーマットがあります:

以下に例を示します。

この文字列はhell tochangein iOS、それreallysucks.

ただし、フォント自体は必要なフォントではありません。

したい:

すべての文字について、その文字を特定のfont(たとえば、Avenir)に変更します

だが、

各文字、keepother属性(太字、斜体、色など)、以前はその文字に配置されていました。

一体どうやってこれをするの?


注意:

範囲全体に属性「Avenir」を簡単に追加すると、単純に他のすべての属性範囲を削除になり、すべてのフォーマットが失われます。残念ながら、属性ではない、実際には「加算的」です。

27
Fattie

Rmaddyの答えは私には役に立たなかったので(f.fontDescriptor.withFace(font.fontName)は太字のような特徴を保持しません)、ここに色の更新を含む更新されたSwift 4バージョンがあります:

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

ノート

f.fontDescriptor.withFace(font.fontName)の問題は、italicbold、またはcompressedなどのシンボリックな特性を削除することです。そのフォントフェイス。なぜこれが完全に私を免れるのか、それはApple側の見落としかもしれない。または、新しいフォントの特性を無料で入手できるため、「バグではなく機能」です。

したがって、私たちがしなければならないことは、元のフォントのフォント記述子からのシンボリック特性を持つフォント記述子を作成することです:.withSymbolicTraits(f.fontDescriptor.symbolicTraits)。私がイテレートした最初のコードのrmaddyに小道具。

これは既に_NSAttributedString.DocumentType.html_を介してHTML文字列を解析し、上記の拡張機能を使用してフォントと色を変更する本番アプリでこれを出荷しました。これまでのところ問題ありません。

41
manmal

これは、フォントフェイスを変更できる以外のすべてのフォント属性を含む、すべての属性を適切に保持する、はるかに単純な実装です。

これは、渡されたフォントのフォントフェース(名前)のみを使用することに注意してください。サイズは既存のフォントから保持されます。既存のすべてのフォントサイズを新しいサイズに変更する場合は、f.pointSizeから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()
    }
}

そしてそれを使用するには:

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

重要-

rmaddyは、iOSのこの厄介な問題に対してまったく新しい手法を発明しました。

マンマによる答えは、最終的な完成版です。

純粋に履歴レコードの場合、これはおおよそ昔のやり方です...


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


脚注。関連する点を明確にするためだけに。この種のものは必然的にHTML文字列として始まります。ここでは、htmlである文字列をNSattributedStringに変換する方法に関する注意事項を示します。

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

ジョブの一部が重要な部分であっても、処理には時間がかかります。実際には、ちらつきを避けるためにバックグラウンドitが必要です。

2
Fattie

In Swift次のNSMutableAttributedString Extensionを使用して、既存の属性でフォントを変更します。NSAttributedString.Key.font属性を設定するだけでかなり簡単です。

extension NSMutableAttributedString {
    @discardableResult
    func setFont(_ font: UIFont, range: NSRange? = nil)-> NSMutableAttributedString {
        let range = range ?? NSMakeRange(0, self.length)
        self.addAttributes([.font: font], range: range)
        return self
    }
}

使用法:

let attrString = NSMutableAttributedString("Hello...")
attrString.setFont(UIFont.systemFont(ofSize: 20)

Challenge:これは、他の属性を反映せずに属性を変更/設定するのは非常に簡単です。問題は、ユーザーが他の段落属性値を変更せずに段落スタイル属性[配列値プロパティの場合]を編集する場合です。

0
Gurjit Singh

@manmalの答えのObj-Cバージョン

@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