Get tapped word from UITextView in Swift

I know that this problem has been solved in Objective-C, but I haven’t seen any solution to it in Swift. I have tried to convert the solution code from this post, but I’m getting errors:

func textTapped(recognizer: UITapGestureRecognizer){

    var textView: UITextView = recognizer.view as UITextView
    var layoutManager: NSLayoutManager = textView.layoutManager
    var location: CGPoint = recognizer.locationInView(textView)
    location.x -= textView.textContainerInset.left
    location.y -= textView.textContainerInset.top

    var charIndex: Int
    charIndex = layoutManager.characterIndexForPoint(location, inTextContainer: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

    if charIndex < textView.textStorage.length {
        // do the stuff
        println(charIndex)
    }
}

I think the problem is in this line (see error here):

  • Is it possible to draw a line with SpriteKit animated?
  • Is it possible to add custom text in NSDateFormatter's format string?
  • Xcode unsupported architecture armv7k
  • UIView used as separator messed up my constraints
  • Get currency symbols from currency code with swift
  • Is it possible to add multiple colors in a UIProgressView?
  •  var textView: UITextView = recognizer.view as UITextView
    

    … which I have converted from Objective-C based on this line:

     UITextView *textView = (UITextView *)recognizer.view;
    

    Finally, I am also in doubt about how this function should be called. As I understand it, the function should be passed to a Selector in viewDidLoad(), like this:

     let aSelector: Selector = "textTapped:"   
    
     let tapGesture = UITapGestureRecognizer(target: self, action: aSelector)
     tapGesture.numberOfTapsRequired = 1
     view.addGestureRecognizer(tapGesture)
    

    Because I’m getting the before mentioned error, I’m not sure if it would work. But I’m thinking that I would need to pass the parameter in the textTapped function (recognizer) into the Selector as well. However, I’ve read that you can only pass the function and not any parameters.

    2 Solutions Collect From Internet About “Get tapped word from UITextView in Swift”

    You need to add the UITapGestureRecognizer to the UITextView that you want to be able to tap. You are presently adding the UITapGestureRecognizer to your ViewController‘s view. That is why the cast is getting you into trouble. You are trying to cast a UIView to a UITextView.

    let tapGesture = UITapGestureRecognizer(target: self, action: "textTapped:")
    tapGesture.numberOfTapsRequired = 1
    myTextView.addGestureRecognizer(tapGesture)
    

    Technically recognizer.view is an optional type (UIView!) and could be nil, but it seems unlikely that your textTapped() would be called it that wasn’t set. Likewise, the layoutManager is of type NSLayoutManager!. To be on the safe side though, the Swift way to do this is:

    if let textView = recognizer.view as? UITextView {
        if let layoutManager = textView.layoutManager {
            // code using textView and layoutManager goes here
        }
    }
    

    In fact, if you had written it this way, you wouldn’t have crashed because the conditional cast of the UIView to UITextView would not have succeeded.

    To make this all work then, add attributes to your attributed string that you will extract in your textTapped routine:

    var beginning = NSMutableAttributedString(string: "To the north you see a ")
    var attrs = [NSFontAttributeName: UIFont.systemFontOfSize(19.0), "idnum": "1", "desc": "old building"]
    var condemned = NSMutableAttributedString(string: "condemned building", attributes: attrs)
    beginning.appendAttributedString(condemned)
    attrs = [NSFontAttributeName: UIFont.systemFontOfSize(19.0), "idnum": "2", "desc": "lake"]
    var lake = NSMutableAttributedString(string: " on a small lake", attributes: attrs)
    beginning.appendAttributedString(lake)
    myTextView.attributedText = beginning
    

    Here’s the full textTapped:

    func textTapped(recognizer: UITapGestureRecognizer){
        if let textView = recognizer.view as? UITextView {
            if let layoutManager = textView.layoutManager {
                var location: CGPoint = recognizer.locationInView(textView)
                location.x -= textView.textContainerInset.left
                location.y -= textView.textContainerInset.top
    
                Here is what the Documentation looks like :
                // Returns the index of the character falling under the given point,    expressed in the given container's coordinate system.  If no character is under the point, the nearest character is returned, where nearest is defined according to the requirements of selection by touch or mouse.  This is not simply equivalent to taking the result of the corresponding glyph index method and converting it to a character index, because in some cases a single glyph represents more than one selectable character, for example an fi ligature glyph.  In that case, there will be an insertion point within the glyph, and this method will return one character or the other, depending on whether the specified point lies to the left or the right of that insertion point.  In general, this method will return only character indexes for which there is an insertion point (see next method).  The partial fraction is a fraction of the distance from the insertion point logically before the given character to the next one, which may be either to the right or to the left depending on directionality.
                var charIndex = layoutManager.characterIndexForPoint(location, inTextContainer: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    
                if charIndex < textView.textStorage.length {
                    var range = NSRange(location: 0, length: 0)
                    if let idval = textView.attributedText?.attribute("idnum", atIndex: charIndex, effectiveRange: &range) as? NSString {
                        println("id value: \(idval)")
                        println("charIndex: \(charIndex)")
                        println("range.location = \(range.location)")
                        println("range.length = \(range.length)")
                        let tappedPhrase = (textView.attributedText.string as NSString).substringWithRange(range)
                        println("tapped phrase: \(tappedPhrase)")
                        var mutableText = textView.attributedText.mutableCopy() as NSMutableAttributedString
                        mutableText.addAttributes([NSForegroundColorAttributeName: UIColor.redColor()], range: range)
                        textView.attributedText = mutableText
                    }
                    if let desc = textView.attributedText?.attribute("desc", atIndex: charIndex, effectiveRange: &range) as? NSString {
                        println("desc: \(desc)")
                    }
                }
            }
        }
    }
    

    For Swift 3.0 OR Above

    add Tap gesture to UITextView

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapOnTextView(_:)))
    textView.addGestureRecognizer(tapGesture)
    

    add tap handler method

    @objc private final func tapOnTextView(_ tapGesture: UITapGestureRecognizer){
    
      let point = tapGesture.location(in: textView)
      if let detectedWord = getWordAtPosition(point)
      {
    
      }
    }
    

    get word from point

    private final func getWordAtPosition(_ point: CGPoint) -> String?{
    if let textPosition = textView.closestPosition(to: point)
    {
      if let range = textView.tokenizer.rangeEnclosingPosition(textPosition, with: .word, inDirection: 1)
      {
        return textView.text(in: range)
      }
    }
    return nil}