title: Detect Tap On Image's Attached In NSAttributedString While UITextVIew Editing Is True
body: <p>I'm using Below method for detecting taps on image in <code>UITextView</code>.  </p>
  
  <pre><code>`func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -&gt; Bool`
  </code></pre>
  
  <p>This method only call when <code>textView.isEditable = false</code>.  </p>
  
  <p>So then i add <code>UITapGestureRecognizer</code> on <code>UITextView</code> and it calls when user tap on image inside <code>UITextView</code>. But that point i don't know how i can detect on which image user tap if there is more than one inside <code>UITextView</code>. I also get <code>UITextView</code> x and y location of tap but dont know how i can get text or if it is image from these point</p>
  
  <pre><code>let TapGesture = UITapGestureRecognizer(target: self, action: #selector(tapDetected(sender:)))
  TapGesture.delegate = self
  textView.addGestureRecognizer(TapGesture)`
  </code></pre>
  
  <p>I also tried to add a view in <code>textView.addSubview</code>. But i also don't know how i can change its position if user want to type a text before or after this subview, like it behave same as <code>NSAttributedString Images</code> change its position accordingly text.</p>
  
  <pre><code>let imgRect : UIBezierPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 30, height: 30))
  textView.textContainer.exclusionPaths = [imgRect]
  let spacerView : UIView = UIView.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
  spacerView.backgroundColor = .red
  textView.addSubview(spacerView)
  </code></pre>
  
  <p>Can anyone please tell me how i can detect tap on image while editing is true. Or anyone know how i can add action(addTarget) on <code>NSAttributedString</code> image. I'v also checked iOS default <code>Notes</code> App and they are doing same thing as i need. The main reason behind this feature i want to add attach videos Thumbnails option in <code>UiTextView</code>, when user tap on video thumbnail while typing, video will automatically play in player. I'm attaching video that i recorded from my phone, This is my <a href="https://www.dropbox.com/s/cpn3mlo2guwbnvy/TextView.zip?dl=0" rel="nofollow noreferrer">project</a>.</p>
  
  <p><strong>I need exact same functionality that is on Below Video</strong></p>
  
  <p><a href="https://i.stack.imgur.com/D5lmm.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/D5lmm.gif" alt="enter image description here"></a>. </p>
  
  <p>Thanks</p>
  
  <pre><code>import UIKit
  
  class ViewController: UIViewController,UITextViewDelegate,UIGestureRecognizerDelegate {
  
      @IBOutlet var textView: UITextView!
      @IBOutlet var imageView: UIImageView!
  
      override func touchesBegan(_ touches: Set&lt;UITouch&gt;, with event: UIEvent?) {
          textView.resignFirstResponder()
          print("touchesBegan")
      }
  
      override func viewDidLoad() {
          super.viewDidLoad()
  
          let TapGesture = UITapGestureRecognizer(target: self, action: #selector(tapDetected(sender:)))
          TapGesture.delegate = self
          textView.addGestureRecognizer(TapGesture)
  
          let imgRect : UIBezierPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 30, height: 30))
          textView.textContainer.exclusionPaths = [imgRect]
          let spacerView : UIView = UIView.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
          spacerView.backgroundColor = .red
          textView.addSubview(spacerView)
  
          textView.attributedText.addObserver(self, forKeyPath: "image", options: .new, context: nil)
          textView.attributedText.addObserver(self, forKeyPath: "image", options: .initial, context: nil)
          textView.attributedText.addObserver(self, forKeyPath: "image", options: .old, context: nil)
          textView.attributedText.addObserver(self, forKeyPath: "image", options: .prior, context: nil)
      }
  
      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -&gt; Bool {
          return true
      }
  
      @IBAction func addImage(_ sender: Any) {
  
          var attributedString :NSMutableAttributedString!
          attributedString = NSMutableAttributedString(attributedString:textView.attributedText)
          let textAttachment = NSTextAttachment()
          textAttachment.image = UIImage(named: "taylor")
          let oldWidth = textAttachment.image!.size.width;
  
          //I'm subtracting 10px to make the image display nicely, accounting
          //for the padding inside the textView
  
          let scaleFactor = (oldWidth / (textView.frame.size.width - 10))
          textAttachment.image = UIImage(cgImage: textAttachment.image!.cgImage!, scale: scaleFactor, orientation: .up)
          let attrStringWithImage = NSAttributedString(attachment: textAttachment)
          attributedString.append(attrStringWithImage)
          textView.attributedText = attributedString;
      }
  
      @objc func tapDetected(sender: UITapGestureRecognizer) {
  
          print("Tap On Image")
          print("Tap Location",sender.location(in: sender.view))
  
          guard case let senderView = sender.view, (senderView is UITextView) else {
              return
          }
  
          // calculate layout manager touch location
          let textView = senderView as! UITextView, // we sure this is an UITextView, so force casting it
          layoutManager = textView.layoutManager
  
          var location = sender.location(in: textView)
          location.x -= textView.textContainerInset.left
          location.y -= textView.textContainerInset.top
  
          print("location",location)
  
          let textContainer = textView.textContainer,
          characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil),
          textStorage = textView.textStorage
  
          guard characterIndex &lt; textStorage.length else {
              return
          }
      }
  
  
      func textViewDidChange(_ textView: UITextView) {
          print("textViewDidChange")
      }
  
      func textViewShouldBeginEditing(_ textView: UITextView) -&gt; Bool {
          print("textViewShouldBeginEditing")
          return true
      }
  
      func textViewDidBeginEditing(_ textView: UITextView) {
          print("textViewDidBeginEditing")
      }
  
      func textViewDidEndEditing(_ textView: UITextView) {
          print("textViewDidBeginEditing")
      }
  
      func textViewShouldEndEditing(_ textView: UITextView) -&gt; Bool {
          print("textViewShouldEndEditing")
          return true
      }
  
      func textViewDidChangeSelection(_ textView: UITextView) {
          print("textViewDidChangeSelection")
  
          print("selectedText", textView.selectedRange.location)
          print("textView.attributedText.containsAttachments(in: textView.selectedRange",textView.attributedText.containsAttachments(in: textView.selectedRange))
          print("textView.attributedText.attributedSubstring(from: textView.selectedRange)",textView.attributedText.attributedSubstring(from: textView.selectedRange))
  
          let img = textView.getParts()
          for i in img {
              if let image = i as? UIImage {
                  imageView.image = image
              }
          }
      }
  
      override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
          print("observeValueobserveValueobserveValueobserveValueobserveValue  keyPath \(String(describing: keyPath)) change \(String(describing: change)) context \(String(describing: context)) ")
      }
  
      func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -&gt; Bool {
          print("textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String)")
          return true
      }
  
  
      func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -&gt; Bool {
          print("textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -&gt; Bool ")
          return true
      }
  
      func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -&gt; Bool {
          print("textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -&gt; Bool")
          imageView.image = textAttachment.image
          return true
      }
  
      func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -&gt; Bool {
          print("textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -&gt; Bool")
          return true
      }
  }
  
  extension UITextView {
      func getParts() -&gt; [AnyObject] {
          var parts = [AnyObject]()
  
          let attributedString = self.attributedText
          let range = self.selectedRange//NSMakeRange(0, (attributedString?.length)!)
          attributedString?.enumerateAttributes(in: range, options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (object, range, stop) in
              if object.keys.contains(NSAttributedStringKey.attachment) {
                  if let attachment = object[NSAttributedStringKey.attachment] as? NSTextAttachment {
                      if let image = attachment.image {
                          parts.append(image)
                      } else if let image = attachment.image(forBounds: attachment.bounds, textContainer: nil, characterIndex: range.location) {
                          parts.append(image)
                      }
                  }
              } else {
                  let stringValue : String = attributedString!.attributedSubstring(from: range).string
                  if (!stringValue.trimmingCharacters(in: .whitespaces).isEmpty) {
                      parts.append(stringValue as AnyObject)
                  }
              }
          }
          return parts
      }
  }
  </code></pre>