Walker
Walker

Reputation: 1147

resolving hashtags in textview

I'm setting the text on a textview and then calling this method of the UITextView extension in order to create links out of the words that are hashtags and mentions (Swift 3)

extension UITextView {
    func resolveHashTags(font: UIFont =  UIFont.systemFont(ofSize: 17.0)){
        if let text = self.text {

            let words:[String] = text.components(separatedBy: " ")

            let attrs = [ NSFontAttributeName : font ]
            let attrString = NSMutableAttributedString(string: text, attributes:attrs)
            for word in words {
                if (word.characters.count > 1 && ((word.hasPrefix("#") && word[1] != "#") || (word.hasPrefix("@") && word[1] != "@"))) {

                    let matchRange = text.range(of: word)
                    let newWord = String(word.characters.dropFirst())

                    if let matchRange = matchRange {

                        attrString.addAttribute(NSLinkAttributeName, value: "\(word.hasPrefix("#") ? "hashtag:" : "mention:")\(newWord)", range: text.NSRangeFromRange(range: matchRange))
                    }
                }
            }

            self.attributedText = attrString
        }
    }
}

My issue is very simple. I have no way to create a link for something like this "helloworld#hello" simply because my word does not have a prefix of "#"

Another scenario I can't figure out is when the user puts multi hashtags together for example "hello world, how are you? #success#moments#connect" as this would all be considered 1 hashtag with the current logic when it should be 3 different links.

How do i correct? thank you

Upvotes: 1

Views: 1604

Answers (2)

unniverzal
unniverzal

Reputation: 803

func resolveHashTags(text : String) -> NSAttributedString{
    var length : Int = 0
    let text:String = text
    let words:[String] = text.separate(withChar: " ")
    let hashtagWords = words.flatMap({$0.separate(withChar: "#")})
    let attrs = [NSFontAttributeName : UIFont.systemFont(ofSize: 17.0)]
    let attrString = NSMutableAttributedString(string: text, attributes:attrs)
    for word in hashtagWords {
        if word.hasPrefix("#") {
                let matchRange:NSRange = NSMakeRange(length, word.characters.count)
                let stringifiedWord:String = word

                attrString.addAttribute(NSLinkAttributeName, value: "hash:\(stringifiedWord)", range: matchRange)
        }
        length += word.characters.count
    }
    return attrString
}

To separate words I used a string Extension

extension String {
    public func separate(withChar char : String) -> [String]{
    var word : String = ""
    var words : [String] = [String]()
    for chararacter in self.characters {
        if String(chararacter) == char && word != "" {
            words.append(word)
            word = char
        }else {
            word += String(chararacter)
        }
    }
    words.append(word)
    return words
}

}

   func textViewDidChange(_ textView: UITextView) {
        textView.attributedText = resolveHashTags(text: textView.text)
        textView.linkTextAttributes = [NSForegroundColorAttributeName : UIColor.red]
    }

I hope this is what you are looking for. Tell me if it worked out for you.

Upvotes: 2

MathewS
MathewS

Reputation: 2307

If you're willing to use a 3rd party library, you could try using Mustard (disclaimer: I'm the author).

You could match hashtag tokens using this tokenizer:

struct HashtagTokenizer: TokenizerType, DefaultTokenizerType {

    // start of token is identified by '#'
    func tokenCanStart(with scalar: UnicodeScalar) -> Bool {
        return scalar == UnicodeScalar(35) // ('35' is scalar value for the # character)
    }

    // all remaining characters must be letters
    public func tokenCanTake(_ scalar: UnicodeScalar) -> Bool {
        return CharacterSet.letters.contains(scalar)
    }
}

Which can then be used to match the hashtags in the text:

let hashtags = "hello world, how are you? #success#moments#connect".tokens(matchedWith: HashtagTokenizer())
// hashtags.count -> 3
// hashtags[0].text -> "#success"
// hashtags[0].range -> 26..<34
// hashtags[1].text -> "#moments"
// hashtags[1].range -> 34..<42
// hashtags[2].text -> "#connect"
// hashtags[2].range -> 42..<50

The array returned is an array of tokens, where each one contains a text property of the matched text, and a range property of the range of that matched text in the original string that you can use to create a link on the text view.

Upvotes: 1

Related Questions