使用attributedString

问题描述

我正在尝试实现一个可以在输入时处理主题标签的编辑器。

extension UITextView {

func resolveHashTags() {
    if self.text.isEmpty {
        let emptyString = NSMutableAttributedString(string: " ",attributes: [NSAttributedString.Key.foregroundColor: UIColor.black,NSAttributedString.Key.font: self.font!])
        self.attributedText = emptyString
        self.textColor = .black
        self.text = ""
        return
    }
    let cursorRange = selectedRange
    let nsText = Nsstring(string: self.text)
    let words = nsText.components(separatedBy: CharacterSet(charactersIn: "@#ABCDEFGHIJKLMnopQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted).filter({!$0.isEmpty})

    self.textColor = .black
    let attrString = NSMutableAttributedString()
    attrString.setAttributedString(self.attributedText)
    attrString.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.black],range: nsText.range(of: self.text))
    
    var anchor: Int = 0

    for word in words {

        // found a word that is prepended by a hashtag!
        // homework for you: implement @mentions here too.
        let matchRange:NSRange = nsText.range(of: word as String,range: NSRange(location: anchor,length: nsText.length - anchor))
        anchor = matchRange.location + matchRange.length
        if word.hasPrefix("#") {

            // a range is the character position,followed by how many characters are in the word.
            // we need this because we staple the "href" to this range.
            

            // drop the hashtag
            let stringifiedWord = word.dropFirst()
            if let firstChar = stringifiedWord.unicodeScalars.first,NSCharacterSet.decimalDigits.contains(firstChar) {
                // hashtag contains a number,like "#1"
                // so don't make it clickable
            } else {
                // set a link for when the user clicks on this word.
                // it's not enough to use the word "hash",but you need the url scheme Syntax "hash://"
                // note:  since it's a URL Now,the color is set to the project's tint color
                attrString.addAttribute(NSAttributedString.Key.link,value: "hash:\(stringifiedWord)",range: matchRange)
            }

        } else if !word.hasPrefix("@") {
            
        }
    }
    self.attributedText = attrString
    self.selectedRange = cursorRange
}
}

所以这是我用来在 UITextView 中创建超链接的扩展。在 func textViewDidChange(_ textView: UITextView)

调用

因此,在键入时是否有任何以 # 开头的单词。它将打开超链接并将颜色更改为蓝色。输入想要的单词后,如果按空格,它会返回黑色文本。这是预期的行为。

enter image description here

但是如果你清除文本并将你的课程移回像这样的标签

enter image description here

它也不断地将超链接扩展到下一个词。

enter image description here

仅保留指向该词的超链接的任何解决方案。在主题标签之后输入的任何内容都应该是普通文本

解决方法

我终于明白了。

func textView(_ textView: UITextView,shouldChangeTextIn range: NSRange,replacementText text: String) -> Bool {
    var shouldReturn = true
    let selectedRange = textView.selectedRange
    let attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
    if !text.isEmpty && text != " " {
        var userAttributes = [(NSAttributedString.Key,Any,NSRange)]()
        attributedText.enumerateAttribute(.link,in: _NSRange(location: 0,length: textView.text.count),options: .longestEffectiveRangeNotRequired) { (value,range,stop) in
            if let url = value as? String,url.hasPrefix("user:") {
                userAttributes.append((.link,value!,range))
            }
        }
        if let userLink = userAttributes.first(where: {$0.2.contains(range.location - 1)}) {
            attributedText.replaceCharacters(in: range,with: NSAttributedString(string: text,attributes: [NSAttributedString.Key.link : userLink.1,NSAttributedString.Key.font : textView.font as Any]))
            textView.attributedText = attributedText
            shouldReturn = false
        } else {
            attributedText.replaceCharacters(in: range,attributes: [NSAttributedString.Key.font : textView.font as Any]))
            textView.attributedText = attributedText
            textDidChange?(textView)
            shouldReturn = false
        }
        textView.selectedRange = _NSRange(location: selectedRange.location + text.count,length: 0)
        textViewDidChange(textView)
    }
    return shouldReturn
}

通过这种方式,我可以控制更新单词之间的链接,并且之后不会扩展到新单词。