Search code examples
iosswiftnsattributedstringuitapgesturerecognizer

iOS/Swift: Add UITapGestureRecognizer to NSMutableAttributedString


I am wondering if it is possible to add a UITapGestureRecognizer to an NSMutableAttributedString.

What I am looking to do is have a "more..." object after the last word of a character-limited UITextView. In order for the object to appear immediatley after the last word, it seems best to make the UITextView accept attributed text (rather than plain text) and then append a blue "more..." of type NSMutableAttributedString.

I have seen examples of people accomplishing this by adding the tap gesture to the enclosing UITextView, but that is not sufficient as I wish for an action to occur only if they tap the "more..." at the end of the attributed string within the text view. I would like to see if there is a way to add a tap gesture directly to the attributed string.


Solution

  • First of all, there is no way to add a gesture recogniser to a NSMutableAttributedString because an attributed string doesn't know anything about either its position in the screen or user touches.

    The process to get an attributed string drawn in a UITextView is controlled mainly by three classes: NSTextStorage, NSTextContainer and NSLayoutManager. If you want to know a little bit more about how they work, check out this awesome tutorial.

    After reading that tutorial you should know that you don't need to add a tap gesture recogniser to an attributed string to get what you want. If you don't need the UITextView to be editable, a solution would be to treat the blue More... as a link and attach a custom action to respond when the user touches it. Here is a Swift translation of the objective-c code in this link to illustrate how to achieve that:

    class ViewController: UIViewController, UITextViewDelegate {
        @IBOutlet weak var textView: UITextView!
    
       override func viewDidLoad() {
          super.viewDidLoad()
          let str = NSMutableAttributedString(
              string: "This is a sample text that is very long but you can see it completely because More..."
          )
          str.addAttribute(
             NSLinkAttributeName,
             value: "more://",
             range: (str.string as NSString).range(of: "More...")
          )
      
          textView.attributedText = str
          textView.delegate = self
          textView.isSelectable = true
          textView.isEditable = false
       }
    
       func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
          if URL.scheme == "more" {
             print("TODO: Handle more action here")
             return false
          }
          else {
             return true
          }
       }
    }