Detect Tap On Image's Attached In NSAttributedString While UITextVIew Editing Is True

你。 提交于 2019-12-22 10:59:37

问题


I'm using Below method for detecting taps on image in UITextView.

`func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool`

This method only call when textView.isEditable = false.

So then i add UITapGestureRecognizer on UITextView and it calls when user tap on image inside UITextView. But that point i don't know how i can detect on which image user tap if there is more than one inside UITextView. I also get UITextView x and y location of tap but dont know how i can get text or if it is image from these point

let TapGesture = UITapGestureRecognizer(target: self, action: #selector(tapDetected(sender:)))
TapGesture.delegate = self
textView.addGestureRecognizer(TapGesture)`

I also tried to add a view in textView.addSubview. 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 NSAttributedString Images change its position accordingly text.

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)

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 NSAttributedString image. I'v also checked iOS default Notes 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 UiTextView, 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 project.

I need exact same functionality that is on Below Video

.

Thanks

import UIKit

class ViewController: UIViewController,UITextViewDelegate,UIGestureRecognizerDelegate {

    @IBOutlet var textView: UITextView!
    @IBOutlet var imageView: UIImageView!

    override func touchesBegan(_ touches: Set<UITouch>, 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) -> 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 < textStorage.length else {
            return
        }
    }


    func textViewDidChange(_ textView: UITextView) {
        print("textViewDidChange")
    }

    func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
        print("textViewShouldBeginEditing")
        return true
    }

    func textViewDidBeginEditing(_ textView: UITextView) {
        print("textViewDidBeginEditing")
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        print("textViewDidBeginEditing")
    }

    func textViewShouldEndEditing(_ textView: UITextView) -> 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) -> 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) -> Bool {
        print("textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool ")
        return true
    }

    func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        print("textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool")
        imageView.image = textAttachment.image
        return true
    }

    func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
        print("textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool")
        return true
    }
}

extension UITextView {
    func getParts() -> [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
    }
}

回答1:


Firstly, create a new NSAttributedStringKey that you'll use to identify the image attachment. Then create an NSTextAttachment with the image, wrap it in a NSMutableAttributedString and add the custom attribute to it. Finally add the wrapper to the full NSAttributedString and attach a UITapGestureRecognizer.

Then when in the selector on the UITapGestureRecognizer simply look for that custom tag.

Code for most bits:

extension NSAttributedStringKey {
static let imagePath = NSAttributedStringKey(rawValue: "imagePath")

}

... then when setting up the text display

let fullString = NSMutableAttributedString()    
let imageAttachment = NSTextAttachment()
imageAttachment.image = image

let imageAttributedString: NSMutableAttributedString = NSAttributedString(attachment: imageAttachment).mutableCopy() as! NSMutableAttributedString

let customAttribute = [ NSAttributedStringKey.imagePath: imagePath ]
imageAttributedString.addAttributes(customAttribute, range: NSRange(location: 0, length: imageAttributedString.length))

fullString.append(imageAttributedString)

then in the function called by the tap action:

    @objc func onImageTap(_ sender: UITapGestureRecognizer) {
      let textView = sender.view as! UITextView
      let layoutManager = textView.layoutManager

      // location of tap in textView coordinates
      var location = sender.location(in: textView)
      location.x -= textView.textContainerInset.left;
      location.y -= textView.textContainerInset.top;

      // character index at tap location
      let characterIndex = layoutManager.characterIndex(for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

      // if index is valid 
      if characterIndex < textView.textStorage.length {

        // check if the tap location has the custom attribute
        let attributeValue = textView.attributedText.attribute(NSAttributedStringKey.imagePath, at: characterIndex, effectiveRange: nil) as? String
        if let value = attributeValue {
            print("You tapped on \(NSAttributedStringKey.imagePath) and the value is: \(value)")
        }

    }

}

From there you know the tap was in the image and you have the coordinates inside the image frame, so you can use that combination to figure out where in the image was tapped.




回答2:


As Charles's answer is great hint, I would like to shard my approaching.

My way is not much different from him, instead to add a new attribute key, I am using the original "attachment" as my key to get the image.

So, create an image array, update the array every time you add / remove image(s) (to make sure the right order of images).

Create an image viewer for view images (you can search from internet).

Use Charles answer, to detect the tap on image (I use "attachment" key instead of custom key).

Once the tap on an image, open image viewer and show the current image, you should parse the image array to image viewer, so the image viewer can display image in the right order.

Here is a snip of my code:

@objc func tapOnImage(_ sender: UITapGestureRecognizer) {
    let textView = sender.view as! UITextView
    let layoutManager = textView.layoutManager

    var location = sender.location(in: textView)
    location.x -= textView.textContainerInset.left
    location.y -= memtextViewoView.textContainerInset.top

    let characterIndex = layoutManager.characterIndex(for: location,
                                                      in: textView.textContainer,
                                                      fractionOfDistanceBetweenInsertionPoints: nil)

    if characterIndex < textView.textStorage.length {    
        let attachment = textView.attributedText.attribute(NSAttributedStringKey.attachment,
                                                         at: characterIndex,
                                                         effectiveRange: nil) as? NSTextAttachment
        if let attachImage = attachment {
            print("tap on image: ", attachImage.image)

        }
    }
}

From above code, you can found when you click on different image, the console will show different object, from there you can use the image do whatever you need.

I hope this can help people who stuck on this kind of question.

By the way, I'm using swift 4.1 on xcode 9.2



来源:https://stackoverflow.com/questions/48498366/detect-tap-on-images-attached-in-nsattributedstring-while-uitextview-editing-is

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!