yeti logo icon
Close Icon
contact us
Yeti postage stamp
We'll reply within 24 hours.
Thank you! Your message has been received!
A yeti hand giving a thumb's up
Oops! Something went wrong while submitting the form.

Hashtags and Mentions in Swift

By
-
April 17, 2015

Tape is one of our most recent iOS apps at Yeti, and one we’re very proud of because it is our largest application written in Swift.

To preface, Tape is a social video editing app that allows users to record clips and “tape” them together into a longer video. Through the use of clip editing, filters, and music, our beta users have already made some very impressive Tapes.

One of the features that we had to implement were hashtags and mentions - if a user inputs a #hashtag or @mentions a user, that word would be highlighted, tappable, and take you to the appropriate page. This feature appears in multiple places throughout our app such as video description, comments, so it was important for us to make the code as reusable as possible.

Here's what this feature looks like in the comments section of Tape:

Inheriting from UITextView

In order create this functionality, we wrote a custom class which inherits from UITextView.

The logic within our custom class does two main things: it sets the text attributes on the incoming string and handles the tap gesture. Let's talk about setting text attributes first.  In our public setText function, we pass the string and the text attributes we want to set on it and add a tap gesture to the hashtag or mention. Here's our actual setText method in Tape:

public func setText(text: String, withHashtagColor hashtagColor: UIColor, andMentionColor mentionColor: UIColor, andCallBack callBack: (String, wordType) -> Void, font: UIFont) {   self.callBack = callBack   var attrString = NSMutableAttributedString(string: text)   self.attrString = attrString   var textString = NSString(string: text)   self.textString = textString   // Set initial font attributes for our string   attrString.addAttribute(NSFontAttributeName, value: font, range: NSRange(location: 0, length: textString.length))   // Call a custom set Hashtag and Mention Attributes Function   setAttrWithName("Hashtag", wordPrefix: "#", color: hashtagColor, text: text)   setAttrWithName("Mention", wordPrefix: "@", color: mentionColor, text: text)   // Add tap gesture that calls a function tapRecognized when tapped   let tapper = UITapGestureRecognizer(target: self, action: "tapRecognized:")   addGestureRecognizer(tapper)}

And in our custom setAttrWithName function:

 func setAttrWithName(attrName: String, wordPrefix: String, color: UIColor, text: String) {    let words = text.componentsSeparatedByString(" ")    for word in words.filter({$0.hasPrefix(wordPrefix)}) {      let range = textString!.rangeOfString(word)      attrString.addAttribute(NSForegroundColorAttributeName, value: color, range: range)      attrString.addAttribute(attrName, value: 1, range: range)      attrString.addAttribute("Clickable", value: 1, range: range)    }    self.attributedText = attrString  }

This is fairly straight forward code that converts the incoming string from a non-mutable string to mutable one and adds attributes on the string. Finally, it adds a gesture recognizer to the string.  Something you might've noticed is that we've added an attribute name for “Hashtag” or “Mention” in our setText function - in the next section we'll explain why this is necessary.

Handling our Tap Gesture

After setting text attributes and adding our tap gesture, we handle the tap via our tapRecognized function (shown below). Because checking for our text with a .Word granularity omits special characters such as @ and #, we had to set an attribute name to the word so we can easily identify whether the word is a hashtag or a mention. Once identified, we run our callback function which handles what data will get displayed.  If a user clicks on a hashtag, they will be presented with the search results for the hashtag, and if a user clicks on a mention, they will go to the mentioned user's profile page if it exists. The logic of which page to show the user is defined in our callback function, but since it is fairly straight forward, we have not shown that code here.

func tapRecognized(tapGesture: UITapGestureRecognizer) {  // Gets the range of word at current position  var point = tapGesture.locationInView(self)  var position = closestPositionToPoint(point)  let range = tokenizer.rangeEnclosingPosition(position, withGranularity: .Word, inDirection: 1)  if range != nil {    let location = offsetFromPosition(beginningOfDocument, toPosition: range!.start)    let length = offsetFromPosition(range!.start, toPosition: range!.end)    let attrRange = NSMakeRange(location, length)    let word = attributedText.attributedSubstringFromRange(attrRange)    // Checks the word's attribute, if any    let isHashtag: AnyObject? = word.attribute("Hashtag", atIndex: 0, longestEffectiveRange: nil, inRange: NSMakeRange(0, word.length))    let isAtMention: AnyObject? = word.attribute("Mention", atIndex: 0, longestEffectiveRange: nil, inRange: NSMakeRange(0, word.length))    // Runs callback function if word is a Hashtag or Mention    if isHashtag != nil && callBack != nil {      callBack!(word.string, wordType.Hashtag)    } else if isAtMention != nil && callBack != nil {      callBack!(word.string, wordType.Mention)    }  }}

Conclusion

The code for our feature worked great for our app, but it isn't without flaws.  For example, clicking on a #hashtag would detect and return the word ‘hashtag’, but clicking on the character ‘#’ itself would not register as a click while clicking on any part of the word itself would. This minor bug is unnoticable to users, but it's good to note.

Overall, working on a larger swift project has been as rewarding for me as it was challenging. Although there are many nuances to the language and relatively few code examples out there, one of the huge benefits of Swift was how readable it is. This was my first compiled language and one of the most complicated apps I've worked on to date, so I'm especially grateful to Swift for making large, complex codebase simpler. It's an exciting time to learn Swift and I can't wait to dive in deeper!

Thanks to our wonderful co-op John Kohler for providing the initial code.

You Might also like...

colorful swirlsAn Introduction to Neural Networks

Join James McNamara in this insightful talk as he navigates the intricate world of neural networks, deep learning, and artificial intelligence. From the evolution of architectures like CNNs and RNNs to groundbreaking techniques like word embeddings and transformers, discover the transformative impact of AI in image recognition, natural language processing, and even coding assistance.

A keyboardThe Symbolicon: My Journey to an Ineffective 10-key Keyboard

Join developer Jonny in exploring the Symbolicon, a unique 10-key custom keyboard inspired by the Braille alphabet. Delve into the conceptualization, ideas, and the hands-on process of building this unique keyboard!

Cross-Domain Product Analytics with PostHog

Insightful product analytics can provide a treasure trove of valuable user information. Unfortunately, there are also a large number of roadblocks to obtaining accurate user data. In this article we explore PostHog and how it can help you improve your cross-domain user analytics.

Browse all Blog Articles

Ready for your new product adventure?

Let's Get Started