just a kid
just a kid

Reputation: 153

swift extract hashtag strings from text

How do I extract hashtag strings from a text in Swift? I've seen some answers but they seem too complicated for what I need and I don't really understand how RegEx works?

E.g.

Text: "This is #something with a lot of #random #hashtags #123yay."

What I want: "something", "random", "hashtags", "123yay".

Thanks!

Upvotes: 5

Views: 5335

Answers (10)

Umair Khan
Umair Khan

Reputation: 1311

Copy paste this extension to your class:

extension UITextView{
func insertTextWithHashtags(text textString: String){

    let nsTextString: NSString = textString as NSString
    
    let simpleTextAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor : UIColor(named: "Black Color")!, NSAttributedString.Key.font : UIFont(name: "Inter-Regular", size: 16.0)!]
    let attributedString = NSMutableAttributedString(string: textString, attributes: simpleTextAttributes)
    
    var word = ""
    
    for text in textString+" "{ //+" " is for loop to run one extra time to complete hashtag
        
        if text == "#" || text == "\n" || text == " "{
            if word.hasPrefix("#"){
                let range = nsTextString.range(of: word)
                let link = [NSAttributedString.Key.link : word]
                attributedString.addAttributes(link, range: range)
                
                if text == "#"{
                    word = "#"
                }else{
                    word = ""
                }
            }else{
                if text == "#"{
                    word = "#"
                }
            }
        }else{
            if word.hasPrefix("#"){
                word.append(text)
            }
        }
    }

    //For for applying attributes to hashtag
    let linkAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor : UIColor(named: "Primary Color")!]
    self.linkTextAttributes = linkAttributes
    
    self.attributedText = attributedString
}

}

and then call it like this:

postTextView.insertTextWithHashtags(text: "#Hello#Hey #Space")

enter image description here

Upvotes: 0

Shree Ranga Raju
Shree Ranga Raju

Reputation: 691

This is how I'm doing it

    private func getHashTags(from caption: String) -> [String] {
    var words: [String] = []
    let texts = caption.components(separatedBy: " ")
    for text in texts.filter({ $0.hasPrefix("#") }) {
        if text.count > 1 {
            let subString = String(text.suffix(text.count - 1))
            words.append(subString.lowercased())
        }
    }
    return words
}

Upvotes: 1

Aleyam
Aleyam

Reputation: 1245

for those who are using swiftUI you can achieve it by using the "+" operator so the final solution will look like this

static func tagHighlighter(description : String , previousText : Text = Text("") , tag : String = "#") -> Text {


        var t : Text = Text("")

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

        for  word in words {

            if !word.isEmpty {

                let tag = word[word.startIndex]

                if tag == "@" {

                    t = t + Text("\(word) ").foregroundColor(Color("tag_color"))

                } else if tag == "#" {

                    t = t + Text("\(word) ").foregroundColor(Color("tag_color"))

                } else {
                    t = t + Text("\(word) ")
                }

            }

        }

        return t
    }

Upvotes: 0

Pablo Sanchez Gomez
Pablo Sanchez Gomez

Reputation: 1518

My clean solution: We will return PrefixesDetected to the view. And the view will format it as he wants. (So we will execute yourString.resolvePrefixes()) in the viewModel and we will be able to test it.

struct PrefixesDetected {
   let text: String
   let prefix: String?
}


extension String {
    func resolvePrefixes(_ prefixes: [String] = ["#", "@"]) -> [PrefixesDetected] {

        let words = self.components(separatedBy: " ")

        return words.map { word -> PrefixesDetected in
            PrefixesDetected(text: word,
                             prefix: word.hasPrefix(prefixes: prefixes))
        }
    }

    func hasPrefix(prefixes: [String]) -> String? {
        for prefix in prefixes {
            if hasPrefix(prefix) {
                return prefix
            }
        }
        return nil
    }
}

Then in the view we can format it as for example: (In this case we want both in the same color but in this way you can give them different behaviors)

Here I do with reduce but this is just to show an example, you can format it as you want! :)

titleDetectedPrefixes.reduce(NSAttributedString(), { result, prefixDectedWord in

        let wordColor: UIColor = prefixDectedWord.prefix != nil ? .highlightTextMain : .mainText
        let attributedWord = NSAttributedString(string: prefixDectedWord.text)

        { Add desired attributes }
    })

Upvotes: 0

Wanbok Choi
Wanbok Choi

Reputation: 5452

I just changed @JayDeep 's answer to more swifty style.

extension String {
  var tags: [String] {
    let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
    let nsRange: NSRange = .init(location: 0, length: self.count)
    guard let matches = regex?.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: nsRange)
      else { return [] }
    return matches
      .map { match in
        let startIndex = self.index(self.startIndex, offsetBy: match.range.location)
        let endIndex = self.index(startIndex, offsetBy: match.range.length)
        let range = startIndex ..< endIndex
        return String(self[range])
      }
  }
}

Upvotes: 0

Jaydeep Vora
Jaydeep Vora

Reputation: 6223

here is the helper method to convert your string into hash detection string

this extension find the # words from sting also including arabic words.

extension String {
    func findMentionText() -> [String] {
        var arr_hasStrings:[String] = []
        let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
        if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
            for match in matches {
                arr_hasStrings.append(NSString(string: self).substring(with: NSRange(location:match.range.location, length: match.range.length )))
            }
        }
        return arr_hasStrings
    }
}

And below method converts your string into Reach colorful hash string.

func convert(_ hashElements:[String], string: String) -> NSAttributedString {

    let hasAttribute = [NSAttributedStringKey.foregroundColor: UIColor.orange]

    let normalAttribute = [NSAttributedStringKey.foregroundColor: UIColor.black]

    let mainAttributedString = NSMutableAttributedString(string: string, attributes: normalAttribute)

    let txtViewReviewText = string as NSString

    hashElements.forEach { if string.contains($0) {
        mainAttributedString.addAttributes(hasAttribute, range: txtViewReviewText.range(of: $0))
        }
    }
    return mainAttributedString
}

i.e

let text = "#Jaydeep #Viral you have to come for party"

let hashString = convert(text.findMentionText(), string: text)

Output:

enter image description here

Upvotes: 6

Ajay saini
Ajay saini

Reputation: 2470

You can also use third party Activelabel . this is simple to use and also support Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns

https://github.com/optonaut/ActiveLabel.swift

Upvotes: 0

Moayad Al kouz
Moayad Al kouz

Reputation: 1392

 let str =  "This is #something with a lot of #random #hashtags #123yay."
 let words = str.components(separatedBy: " ")
 var hashTags = [String]()
 for word in words{
     if word.hasPrefix("#"){
         let hashtag = word.dropFirst()
         hashTags.append(String(hashtag))
     }
 }
 print("Hashtags :: ", hashTags)

Upvotes: 3

MarkWarriors
MarkWarriors

Reputation: 554

extension String
{
    func hashtags() -> [String]
    {
        if let regex = try? NSRegularExpression(pattern: "#[a-z0-9]+", options: .caseInsensitive)
        {
            let string = self as NSString

            return regex.matches(in: self, options: [], range: NSRange(location: 0, length: string.length)).map {
                string.substring(with: $0.range).replacingOccurrences(of: "#", with: "").lowercased()
            }
        }

        return []
    }
}

then, to get the hashtags array

yourstring.hashtags()

Here is the source

Upvotes: 4

user9373173
user9373173

Reputation:

First things first, this works best in a TextView. So set one up inside of your view however you want, but make sure that your ViewController has a UITextViewDelegate & the textView is delegated to that view controller. I’m also doing this with some prefilled information, but the same concept applies with pulling data from your database and what not.

This is how we set up our ViewController:

class ViewController: UIViewController, UITextViewDelegate {
    var string = "Hello, my name is @Jared & #Jared and I like to move it."
    @IBOutlet weak var textView: UITextView!
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.text = string
        textView.delegate = self
    }

The overall task we’re trying to accomplish in this part is just to split up all the words in our textView. It’s simpler than you might think: First, let’s create our extension:

Now add this to your ViewController:

extension UITextView {
    func resolveTags(){
        let nsText:NSString = self.text as NSString
        let words:[String] = nsText.components(separatedBy: " ")

        let attrs = [
            NSAttributedStringKey.font : UIFont.init(name: "HelveticaNeue", size: 13),
            NSAttributedStringKey.foregroundColor : UIColor.black

        ]

        let attrString = NSMutableAttributedString(string: nsText as String, attributes:attrs)

        for word in words {
            if word.hasPrefix("#") {
                let matchRange:NSRange = nsText.range(of: word as String)
                var stringifiedWord:String = word as String
                stringifiedWord = String(stringifiedWord.dropFirst())
                attrString.addAttribute(NSAttributedStringKey.link, value: "hash:\(stringifiedWord)", range: matchRange)
                attrString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue , range: matchRange)
          }
        }
        self.attributedText = attrString
    }
}

Let’s use this thing!

It all comes down to this. We have this function working, now how do we use it?

Easy.

Inside of your viewDidLoad function, or wherever you set your textView text, just call:

textView.resolveTags()

Result:

Result

Courtesy of: Jared Davidson On Twitter

Upvotes: 1

Related Questions