3

我正在使用以下方法来检测UITextView.

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

此方法仅在textView.isEditable = false.

所以然后我添加它,当用户点击里面的图像UITapGestureRecognizerUITextView它会调用UITextView。但那一点我不知道如果里面有多个图像,我如何检测用户点击哪个图像UITextView。我也得到UITextView了水龙头的 x 和 y 位置,但不知道如何获取文本或者它是否是来自这些点的图像

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

我还尝试在textView.addSubview. 但是我也不知道如果用户想在此子视图之前或之后键入文本,我如何更改其位置,就像它的行为与NSAttributedString Images相应地更改其位置文本一样。

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)

谁能告诉我如何在编辑是真的时检测到点击图像。或者任何人都知道我如何在NSAttributedString图像上添加操作(addTarget)。我还检查了 iOS 默认Notes应用程序,他们正在按照我的需要做同样的事情。此功能背后的主要原因是我想在其中添加附加视频缩略图选项UiTextView,当用户在输入时点击视频缩略图时,视频将自动在播放器中播放。我正在附上我从手机录制的视频,这是我的项目

我需要与视频下方完全相同的功能

在此处输入图像描述.

谢谢

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
    }
}
4

2 回答 2

2

首先,创建一个新的 NSAttributedStringKey 用于识别图像附件。然后使用图像创建一个 NSTextAttachment,将其包装在 NSMutableAttributedString 中,并为其添加自定义属性。最后将包装器添加到完整的 NSAttributedString 并附加一个 UITapGestureRecognizer。

然后,当在 UITapGestureRecognizer 上的选择器中时,只需查找该自定义标记。

大多数位的代码:

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

}

...然后在设置文本显示时

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)

然后在点击动作调用的函数中:

    @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)")
        }

    }

}

从那里您知道点击在图像中,并且您在图像框架内有坐标,因此您可以使用该组合来确定图像中的点击位置。

于 2018-05-17T16:39:51.083 回答
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

于 2018-06-25T12:03:58.300 回答