Обнаружение касания изображения, прикрепленного к NSAttributedString, в то время как UITextVIew редактирование верно

Я использую метод ниже для обнаружения касаний изображения в UITextView,

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

Этот метод вызывается только когда textView.isEditable = false,

Итак, я добавляю UITapGestureRecognizer на UITextView и он вызывает, когда пользователь нажимает на изображение внутри 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)

Может кто-нибудь, пожалуйста, скажите мне, как я могу обнаружить нажатие на изображение, когда редактирование верно. Или кто-нибудь знает, как я могу добавить действие (addTarget) на NSAttributedString образ. Я также проверил 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
    }
}

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)

затем в функции, вызываемой действием tap:

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

    }

}

Оттуда вы знаете, что касание было на изображении, и у вас есть координаты внутри рамки изображения, так что вы можете использовать эту комбинацию, чтобы выяснить, где на изображении было нажато.

Поскольку ответ Чарльза - отличный намек, я хотел бы осквернить свое приближение.

Мой способ не сильно отличается от него, вместо этого, чтобы добавить новый атрибут ключа, я использую оригинальное "вложение" в качестве ключа для получения изображения.

Итак, создайте массив изображений, обновляйте массив каждый раз, когда вы добавляете / удаляете изображения (изображения) (чтобы убедиться в правильном порядке изображений).

Создать просмотрщик изображений для просмотра изображений (вы можете искать из Интернета).

Используйте ответ Чарльза, чтобы обнаружить нажатие на изображение (я использую клавишу "вложение" вместо пользовательской клавиши).

После нажатия на изображение, открытия средства просмотра изображений и отображения текущего изображения вы должны проанализировать массив изображений для средства просмотра изображений, чтобы средство просмотра изображений могло отображать изображение в правильном порядке.

Вот фрагмент моего кода:

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

        }
    }
}

Из приведенного выше кода, вы можете найти, когда вы нажимаете на другое изображение, консоль покажет другой объект, оттуда вы можете использовать изображение делать все, что вам нужно.

Я надеюсь, что это может помочь людям, которые застряли на такого рода вопрос.

Кстати, я использую Swift 4.1 на Xcode 9,2

Другие вопросы по тегам