NSAttributedString, изменить шрифт в целом, но сохранить все остальные атрибуты?

скажем, у меня есть NSMutableAttributedString .

в строке разнообразная смесь форматирование во всем:

вот пример:

эта строка черт изменить в iOS, это действительно отстой.

шрифт как таковой не нужный шрифт.

Я хочу

измените каждый символ на определенный шрифт (скажем, Авенир)

а,

keep все другое атрибуты (полужирный, курсив, цвет и т. д.), которые происходят по всей строке, в разных местах.

Как, черт возьми, вы это делаете?


Примечание:

Если вы тривиально добавляете атрибут "Avenir" по всему диапазону: это просто удаляет все остальные диапазоны атрибутов, вы потеряете все форматирование

чтобы быть ясным: к сожалению, атрибуты не по сути "аддитивные".

5 ответов


так как ответ 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) это то, что он удаляет символические черты, такие как italic, bold или compressed, так как он по какой-то причине переопределит те, у кого есть черты по умолчанию этого шрифта. Почему это так полностью ускользает от меня, это может быть даже упущение со стороны Apple; или это "не ошибка, а функция", потому что мы получаем черты нового шрифта бесплатно.

Итак, что нам нужно сделать, это создать дескриптор шрифта, который имеет символические черты из дескриптора шрифта исходного шрифта:.withSymbolicTraits(f.fontDescriptor.symbolicTraits). Реквизит для rmaddy для исходного кода, на котором я повторял.

Я уже отправил это в производственном приложении, где мы анализируем строку HTML через NSAttributedString.DocumentType.html а затем измените шрифт и цвет с помощью расширения выше. Пока никаких проблем.


вот гораздо более простая реализация, которая сохраняет все атрибуты на месте, включая все атрибуты шрифта, кроме того, что она позволяет изменить лицо шрифта.

обратите внимание, что это использует только лицо шрифта (имя) переданного шрифта. Размер сохраняется из существующего шрифта. Если вы хотите также изменить все существующие размеры шрифта на новый размер, изменение f.pointSize to 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))

важно

rmaddy изобрел совершенно новую технику для этой раздражающей проблемы в iOS.

ответ manmal является окончательной усовершенствованной версией.

чисто истории вот примерно, как вы собираетесь делать это в старые времена...


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

.

даже эта часть работы нетривиальна, для ее обработки требуется некоторое время. На практике вы должны фон чтобы избежать мерцания.


параметр Obj-C версия ответы @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

было бы допустимо, чтобы UITextField выполнял работу?

вот так, учитывая attributedString и newfont:

let textField = UITextField()
textField.attributedText = attributedString
textField.font = newFont
let resultAttributedString = textField.attributedText

Извините, я ошибся, он сохраняет "атрибуты символов", такие как NSForegroundColorAttributeName, например, цвет, но не UIFontDescriptorSymbolicTraits, которые описывают жирный, курсив, конденсированный и т. д.

Они принадлежат шрифту, а не "атрибутам символов". Поэтому, если вы меняете шрифт,вы также меняете черты. Извините, но мое предлагаемое решение не работает. Целевой шрифт должен иметь все черты, доступные в качестве исходного шрифта, чтобы это работало.