9

我正在继承NSTextStorage做一些链接突出显示,我已经尽可能多地阅读了这个 主题在我输入表情符号之前,一切正常。

我的子类:

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:

除了快递新品!

现在我正在挑选这个角色,还有其他人也导致了这种疯狂。我在输入时查询了字体,它来自 System > Apple Emoji > Courier New。

我还尝试设置processEditing()半解决问题的字体,它会导致添加额外的空间(虽然不是在模拟器中)。而且我正在硬编码一个值==不好。

终极问题:

我究竟做错了什么?我没有看到其他人的实现存在这个问题,我确信开发人员已经继承了 NSTextStorage。

注意:我可以确认在objc.io 的演示应用程序中存在相同的问题。

4

3 回答 3

3

这是我外行的理解。大多数表情符号仅存在于 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()做它的事情并修复该范围内的任何新的缺失字符(如果有的话)。

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

于 2016-09-12T18:09:30.597 回答
1

我已经调查了这个问题几个小时。因此,总而言之,插入(键入或粘贴)表情符号字符或在某些表情符号字符(例如☺️)之后放置光标会导致键入字体更改为“AppleColorEmoji”,并且当非表情符号字符被插入。只有在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
    }
}
于 2018-07-18T21:03:10.717 回答
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)不需要

于 2017-04-20T23:51:56.223 回答