在某些情况下,NSTextStorage子类无法处理表情符号字符并更改字体

时间:2016-04-27 19:28:48

标签: ios objective-c swift nsmutableattributedstring nstextstorage

我正在对NSTextStorage进行子类化以进行一些链接突出显示,并且我已经阅读I can on the topic。一切正常,直到我输入表情符号字符。

我的子类:

private let ims = NSMutableAttributedString()

override var string: String {
    return ims.string
}

override func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] {
    return ims.attributesAtIndex(location, effectiveRange: range)
}

override func replaceCharactersInRange(range: NSRange, withString str: String) {
    ims.replaceCharactersInRange(range, withString: str)
    self.edited(.EditedCharacters, range: range, changeInLength:(str as NSString).length - range.length)
}

override func setAttributes(attrs: [String : AnyObject]?, range: NSRange) {
    ims.setAttributes(attrs, range: range)
    self.edited(.EditedAttributes, range: range, changeInLength: 0)
}

没什么复杂的。然后,当输入臭名昭着的角色时,由于某些随机原因,它会切换到Courier New:

Anything but Courier New!

现在我选择角色,还有其他人也会引起这种疯狂。我打字时查询了字体,它来自System> Apple Emoji>快递新。

我还尝试在processEditing()内设置字体,半解决了问题,它会导致额外的空间被添加(尽管不在模拟器中)。我正在硬编码一个值==坏。

终极问题:

我做错了什么?我没有在其他人的实施中看到这个问题,我确定某些开发人员已将NSTextStorage子类化。

注意:我可以确认在objc.io's demo app中存在同样的问题。

3 个答案:

答案 0 :(得分:1)

这是我的外行人的理解。大多数表情符号仅存在于Apple的AppleColorEmoji字体中。当您键入表情符号字符时,您的NSTextStorage会调用processEditing,然后调用fixAttributesInRange。此方法可确保字符串中的任何缺少的字符都替换为支持它们的字体。如果您的字符串包含表情符号,则所有包含表情符号的范围都将获得AppleColorEmoji字体属性。

不幸的是,没有什么能阻止这个新字体属性被“感染”后面输入的字符。 AppleColorEmoji似乎不包含通常的ASCII集,因此后续字符会以等宽字体“固定”。

该怎么办?在我的程序中,我想手动管理文本存储的属性,因为我不希望复制粘贴的文本为我的文本添加新样式。这意味着我可以这样做:

override func setAttributes(attrs: [String : AnyObject]?, range: NSRange) {
    if self.isFixingAttributes {
        self.attributedString.setAttributes(attrs, range: range)
        self.edited(NSTextStorageEditActions.EditedAttributes, range: range, changeInLength: 0)
    }
}

override func fixAttributesInRange(range: NSRange) {
    self.isFixingAttributes = true
    super.fixAttributesInRange(range)
    self.isFixingAttributes = false
}

override func processEditing() {
    // not really fixing -- just need to make sure setAttributes follows orders
    self.isFixingAttributes = true
    self.setAttributes(nil, range: self.editedRange)
    self.setAttributes(self.dynamicType.defaultAttributes(), range: self.editedRange)
    self.isFixingAttributes = false

    super.processEditing()
}

每当输入新文本时,我只需清除其属性(以防任何先前固定的范围“感染”它)并用默认属性替换它们。在那之后,super.processEditing()完成它并修复该范围内的任何新缺失字符(如果有的话)。

另一方面,如果您希望能够将样式文本粘贴到文本视图中,则应该可以通过比较fixAttributesInRange之前/之后跟踪您的固定范围,然后阻止这些转换为processEditing中的新输入文字的样式。

答案 1 :(得分:0)

实际上在我的情况下我只需改变:

self.edited(.editedCharacters, range: range, changeInLength: str.characters.count-range.length)

为:

self.edited(.editedCharacters, range: range, changeInLength: (str as NSString).length-range.length)

很遗憾,获取String的长度与NSString的长度不一样

fixAttributes(in range: NSRange)不需要

答案 2 :(得分:0)

我已经调查了好几个小时。因此,总而言之,在某个表情符号字符(例如☺️)之后插入(键入或粘贴)表情符号字符或将光标放置在导致输入字体更改为“ AppleColorEmoji”的地方,最终又退回到“ Courier New ”。仅在使用NSTextStorage子类的情况下才会发生这种情况,否则键入字体永远不会更改为“ AppleColorEmoji”。因此,我们通过将AppleColorEmoji的键入字体重置为开发人员设置的默认字体来解决此问题。在插入文本的之前之后应用此修复程序。前者修复了由于将光标放置在表情符号字符之后而导致的键入字体更改,而后者修复了由于插入了表情符号字符而导致的键入字体更改(键入字体更改以某种方式反映在UITextView.font参数中)。

请参见https://github.com/CosmicMind/Material/pull/1117

class EmojiFixedTextView: UITextView {
    private var _font: UIFont?

    override var font: UIFont? {
        didSet {
            _font = font
        }
    }

    override func insertText(_ text: String) {
        fixTypingFont()
        super.insertText(text)
        fixTypingFont()
    }

    override func paste(_ sender: Any?) {
        fixTypingFont()
        super.paste(sender)
        fixTypingFont()
    }

    private func fixTypingFont() {
        let fontAttribute = NSAttributedStringKey.font.rawValue
        guard (typingAttributes[fontAttribute] as? UIFont)?.fontName == "AppleColorEmoji" else {
            return
        }

        typingAttributes[fontAttribute] = _font
    }
}