如何检测NSTextAttachment上的触摸

时间:2023-01-22 21:16:25

What is the best way to detect when user taps on NSTextAttachment on iOS?

检测用户在iOS上使用NSTextAttachment时的最佳方法是什么?

I think that one of the ways would be checking for the character on carret's position whether it is NSAttachmentCharacter, but it just doesn't seem right.

我认为其中一种方法是检查carret的位置是否是NSAttachmentCharacter,但它似乎不正确。

I've also tried UITextViewDelegate method: -(BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange but it's not invoked when textView.editable=YES

我也尝试过UITextViewDelegate方法: - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange但是当textView.editable = YES时不调用它

4 个解决方案

#1


3  

The delegate method does work but ONLY if the attachment has an image in the image attribute AND if editable = NO! So if you have an image pasted in to the attributedString from somewhere else it seems the data ends up being stored in the fileWrapper and next time you put the attributedString back into the textView the image attribute is nil and the layout manager or whatever gets the image from the fileWrapper.

委托方法确实有效,但只有附件在图像属性中有图像并且如果editable = NO!因此,如果您将图像粘贴到来自其他位置的attributionString,则数据最终会存储在fileWrapper中,并且下次将referencedString放回textView时,image属性为nil,布局管理器或其他任何内容都会生成图像来自fileWrapper。

Somewhere in the documents it does mention that there are no methods in NSTextAttachment for persistence of the image attribute.

在文档的某处,它确实提到NSTextAttachment中没有方法来保持图像属性的持久性。

To test this try copy a photo from the Photo app and paste it into your textView, now if you hold down your finger on it you should see the default menu pop up. Now if you save this rich text, say into a Core Data entity and then retrieve it the image attribute will be nil but the image data will be in attachment.fileWrapper.regularFileContents

要测试此操作,请尝试从Photo应用程序复制照片并将其粘贴到textView中,现在如果您按住手指,则应该会弹出默认菜单。现在,如果你保存这个富文本,说进入一个核心数据实体,然后检索它,图像属性将是零,但图像数据将在attachment.fileWrapper.regularFileContents

Its a pain, and I would love to know the engineers intention. So you have two options it seems.

这很痛苦,我很想知道工程师的意图。所以你有两个选择。

  1. Create your own custom NSTextAttachment and include methods for archiving the image and other settings (PLEASE SHOW ME HOW TOO WHEN YOU FIGURE THIS ONE OUT)
  2. 创建您自己的自定义NSTextAttachment并包含用于存档图像和其他设置的方法(请告诉我,当你想到这一点时怎么样)
  3. Every time prior to putting your string back into textView you find all the attachments and recreated the image attribute like so:

    每次将字符串放回textView之前,您都会找到所有附件并重新创建图像属性,如下所示:

    attachment.image = [UIImage imageWithData:attachment.fileWrapper.regularFileContents];

    attachment.image = [UIImage imageWithData:attachment.fileWrapper.regularFileContents];

Bear in mind the side effect of doing this is invalidating the fileWrapper. I want to resize the image but also keep the original so I don't loose the full resolution. I think the only way of doing this might be to subclass NSTextAttachment.

请记住,这样做的副作用是使fileWrapper无效。我想调整图像的大小,但也保留原始图像,所以我没有松开全分辨率。我认为这样做的唯一方法可能是继承NSTextAttachment。

EDIT:

编辑:

I figured out how to create the custom NSTextAttachments - here is a link for those interested http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os-x-and-ios/

我想出了如何创建自定义NSTextAttachments - 这是一个链接,感兴趣的人http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os -X-和-IOS /

EDIT 2: To customise the menu when in Edit Mode see the following Apple documents, the issue is 'touchEnded' never seems to get called so you might have to try using touchesBegan. Careful you don't interfere with the default editing behaviour though.

编辑2:要在编辑模式下自定义菜单,请参阅以下Apple文档,问题是“touchEnded”似乎永远不会被调用,因此您可能必须尝试使用​​touchesBegan。但请注意,不要干扰默认的编辑行为。

https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html

https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html

Note that in the code below you would need to add code after // selection management comment to determine which character was touched, check if it is the special text attachment character and then modify the edit menu or take some other action.

请注意,在下面的代码中,您需要在//选择管理注释后添加代码以确定触摸了哪个字符,检查它是否是特殊文本附件字符,然后修改编辑菜单或采取其他操作。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];

    if ([theTouch tapCount] == 2  && [self becomeFirstResponder]) {

        // selection management code goes here...

        // bring up edit menu.
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        CGRect selectionRect = CGRectMake (currentSelection.x, currentSelection.y, SIDE, SIDE);
        [theMenu setTargetRect:selectionRect inView:self];
        [theMenu setMenuVisible:YES animated:YES];

    }
}

Alternately you could add a custom menu by adding the menu item and then modifying the canPerformAction method.

或者,您可以通过添加菜单项然后修改canPerformAction方法来添加自定义菜单。

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    LOG(@"canPerformAction: called");

    if (action == @selector(viewImage)) {
       // Check the selected character is the special text attachment character

       return YES;
    }
   return NO;
}

Here is some addition code but its a bit fussy. Second method just disables the default edit menu if an attachment is detected.

这是一些额外的代码,但它有点挑剔。如果检测到附件,则第二种方法仅禁用默认编辑菜单。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    FLOG(@"touchesBegan:withEvent: called");

    if (self.selectedRange.location != NSNotFound) {
        FLOG(@" selected location is %d", self.selectedRange.location);

        int ch;

        if (self.selectedRange.location >= self.textStorage.length) {
            // Get the character at the location
            ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
        } else {
            // Get the character at the location
            ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
        }

        if (ch == NSAttachmentCharacter) {
            FLOG(@" selected character is %d, a TextAttachment", ch);
        } else {
            FLOG(@" selected character is %d", ch);
        }
    }

}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    FLOG(@"canPerformAction: called");

        FLOG(@" selected location is %d", self.selectedRange.location);
        FLOG(@" TextAttachment character is %d", NSAttachmentCharacter);

        if (self.selectedRange.location != NSNotFound) {

            int ch;

            if (self.selectedRange.location >= self.textStorage.length) {
                // Get the character at the location
                ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
            } else {
                // Get the character at the location
                ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
            }

            if (ch == NSAttachmentCharacter) {
                FLOG(@" selected character is %d, a TextAttachment", ch);
                return NO;
            } else {
                FLOG(@" selected character is %d", ch);
            }

            // Check for an attachment
            NSTextAttachment *attachment = [[self textStorage] attribute:NSAttachmentAttributeName atIndex:self.selectedRange.location effectiveRange:NULL];
            if (attachment) {
                FLOG(@" attachment attribute retrieved at location %d", self.selectedRange.location);
                return NO;
            }
            else
                FLOG(@" no attachment at location %d", self.selectedRange.location);
        }
    return [super canPerformAction:action withSender:sender];
}

#2


2  

Swift 3 answer:

斯威夫特3回答:

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

Make sure your textView isEditable = false, isSelectable = true, and isUserInteractionEnabled = true. Duncan's answer did not mention isUserInteractionEnabled, this has to be true, otherwise it wont work.

确保textView isEditable = false,isSelectable = true,isUserInteractionEnabled = true。 Duncan的回答没有提到isUserInteractionEnabled,这必须是真的,否则它不会工作。

You can do this programmatically (textView.isEditable = false), or via attributes inspector: 如何检测NSTextAttachment上的触摸

如何检测NSTextAttachment上的触摸

您可以通过编程方式(textView.isEditable = false)或通过属性检查器执行此操作:

#3


0  

Use hitTest to get the touch in a subclassed UITextView. This avoids the issue of messing up the standard editing functions. From the position get the character index and then check the character for the attachment.

使用hitTest在子类UITextView中进行触摸。这避免了弄乱标准编辑功能的问题。从位置获取字符索引,然后检查附件的字符。

#4


0  

Apple make this really difficult. As others point out, the delegate method is called, but only when isEditable is false, or when the user does a tap and hold on the attachment. If you want to be informed about a simple tap interaction during editing, forget it.

Apple让这真的很难。正如其他人指出的那样,委托方法被调用,但仅当isEditable为false时,或者当用户点击并按住附件时。如果您想在编辑过程中了解简单的点按互动,请忘掉它。

I went down the touchesBegan: and hitTest: paths, both with problems. The touches methods are called after the UITextView has already handled the interaction, and the hitTest: is too crude, because it messes with the first responder status and so forth.

我走下了touchesBegan:和hitTest:路径,都有问题。在UITextView已经处理了交互之后调用touches方法,而hitTest:太粗糙了,因为它与第一个响应者状态混淆等等。

My solution in the end was gesture recognizers. Apple are using those internally, which explains why touchesBegan: is not really viable in the first place: the gesture recognizers have already handled the event.

我的解决方案最终是手势识别器。 Apple正在内部使用它们,这解释了为什么touchesBegan:首先不是真正可行的:手势识别器已经处理了这个事件。

I created a new gesture recognizer class for use with a UITextView. It simply checks for the location of the tap, and if it is an attachment, it handles it. I make all the other gesture recognizers subordinate to my one, so we get first look at the events, and the others only come into play if our one fails.

我创建了一个新的手势识别器类,用于UITextView。它只是检查水龙头的位置,如果是附件,它会处理它。我将所有其他手势识别器从属于我的手势识别器,因此我们先看看事件,其他人只有在我们的事件失败时才会发挥作用。

The gesture recognizer class is below, along with an extension for adding it to UITextView. I add it in my UITextView subclass in awakeFromNib, like this. (You needn't use a subclass if you don't have one.)

下面是手势识别器类,以及用于将其添加到UITextView的扩展。我将它添加到awakeFromNib中的UITextView子类中,就像这样。 (如果没有子类,则无需使用子类。)

override func awakeFromNib() {
    super.awakeFromNib()

    let recognizer = AttachmentTapGestureRecognizer(target: self, action: #selector(handleAttachmentTap(_:)))
    add(recognizer)

and I handle the action by calling the existing UITextViewDelegate method textView(_:,shouldInteractWith:,in:,interaction:). You could just as easily put the handling code directly in the action, rather than using the delegate.

我通过调用现有的UITextViewDelegate方法textView(_:,shouldInteractWith:,in:,interaction :)来处理动作。您可以轻松地将处理代码直接放在操作中,而不是使用委托。

@IBAction func handleAttachmentTap(_ sender: AttachmentTapGestureRecognizer) {
    let _ = delegate?.textView?(self, shouldInteractWith: sender.attachment!, in: NSRange(location: sender.attachmentCharacterIndex!, length: 1), interaction: .invokeDefaultAction)
}

Here is the main class.

这是主要的课程。

import UIKit
import UIKit.UIGestureRecognizerSubclass

/// Recognizes a tap on an attachment, on a UITextView.
/// The UITextView normally only informs its delegate of a tap on an attachment if the text view is not editable, or a long tap is used.
/// If you want an editable text view, where you can short cap an attachment, you have a problem.
/// This gesture recognizer can be added to the text view, and will add requirments in order to recognize before any built-in recognizers.
class AttachmentTapGestureRecognizer: UIGestureRecognizer {

    /// Character index of the attachment just tapped
    private(set) var attachmentCharacterIndex: Int?

    /// The attachment just tapped
    private(set) var attachment: NSTextAttachment?

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        attachmentCharacterIndex = nil
        attachment = nil

        let textView = view as! UITextView
        if touches.count == 1, let touch = touches.first, touch.tapCount == 1 {
            let point = touch.location(in: textView)
            let glyphIndex: Int? = textView.layoutManager.glyphIndex(for: point, in: textView.textContainer, fractionOfDistanceThroughGlyph: nil)
            let index: Int? = textView.layoutManager.characterIndexForGlyph(at: glyphIndex ?? 0)
            if let characterIndex = index, characterIndex < textView.textStorage.length {
                if NSAttachmentCharacter == (textView.textStorage.string as NSString).character(at: characterIndex) {
                    attachmentCharacterIndex = characterIndex
                    attachment = textView.textStorage.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment
                    state = .recognized
                } else {
                    state = .failed
                }
            }
        } else {
            state = .failed
        }
    }
}

extension UITextView {

    /// Add an attachment recognizer to a UITTextView
    func add(_ attachmentRecognizer: AttachmentTapGestureRecognizer) {
        for other in gestureRecognizers ?? [] {
            other.require(toFail: attachmentRecognizer)
        }
        addGestureRecognizer(attachmentRecognizer)
    }

}

This same approach could presumably be used for taps on links.

也许这种方法可以用于链接上的点击。

#1


3  

The delegate method does work but ONLY if the attachment has an image in the image attribute AND if editable = NO! So if you have an image pasted in to the attributedString from somewhere else it seems the data ends up being stored in the fileWrapper and next time you put the attributedString back into the textView the image attribute is nil and the layout manager or whatever gets the image from the fileWrapper.

委托方法确实有效,但只有附件在图像属性中有图像并且如果editable = NO!因此,如果您将图像粘贴到来自其他位置的attributionString,则数据最终会存储在fileWrapper中,并且下次将referencedString放回textView时,image属性为nil,布局管理器或其他任何内容都会生成图像来自fileWrapper。

Somewhere in the documents it does mention that there are no methods in NSTextAttachment for persistence of the image attribute.

在文档的某处,它确实提到NSTextAttachment中没有方法来保持图像属性的持久性。

To test this try copy a photo from the Photo app and paste it into your textView, now if you hold down your finger on it you should see the default menu pop up. Now if you save this rich text, say into a Core Data entity and then retrieve it the image attribute will be nil but the image data will be in attachment.fileWrapper.regularFileContents

要测试此操作,请尝试从Photo应用程序复制照片并将其粘贴到textView中,现在如果您按住手指,则应该会弹出默认菜单。现在,如果你保存这个富文本,说进入一个核心数据实体,然后检索它,图像属性将是零,但图像数据将在attachment.fileWrapper.regularFileContents

Its a pain, and I would love to know the engineers intention. So you have two options it seems.

这很痛苦,我很想知道工程师的意图。所以你有两个选择。

  1. Create your own custom NSTextAttachment and include methods for archiving the image and other settings (PLEASE SHOW ME HOW TOO WHEN YOU FIGURE THIS ONE OUT)
  2. 创建您自己的自定义NSTextAttachment并包含用于存档图像和其他设置的方法(请告诉我,当你想到这一点时怎么样)
  3. Every time prior to putting your string back into textView you find all the attachments and recreated the image attribute like so:

    每次将字符串放回textView之前,您都会找到所有附件并重新创建图像属性,如下所示:

    attachment.image = [UIImage imageWithData:attachment.fileWrapper.regularFileContents];

    attachment.image = [UIImage imageWithData:attachment.fileWrapper.regularFileContents];

Bear in mind the side effect of doing this is invalidating the fileWrapper. I want to resize the image but also keep the original so I don't loose the full resolution. I think the only way of doing this might be to subclass NSTextAttachment.

请记住,这样做的副作用是使fileWrapper无效。我想调整图像的大小,但也保留原始图像,所以我没有松开全分辨率。我认为这样做的唯一方法可能是继承NSTextAttachment。

EDIT:

编辑:

I figured out how to create the custom NSTextAttachments - here is a link for those interested http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os-x-and-ios/

我想出了如何创建自定义NSTextAttachments - 这是一个链接,感兴趣的人http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os -X-和-IOS /

EDIT 2: To customise the menu when in Edit Mode see the following Apple documents, the issue is 'touchEnded' never seems to get called so you might have to try using touchesBegan. Careful you don't interfere with the default editing behaviour though.

编辑2:要在编辑模式下自定义菜单,请参阅以下Apple文档,问题是“touchEnded”似乎永远不会被调用,因此您可能必须尝试使用​​touchesBegan。但请注意,不要干扰默认的编辑行为。

https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html

https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html

Note that in the code below you would need to add code after // selection management comment to determine which character was touched, check if it is the special text attachment character and then modify the edit menu or take some other action.

请注意,在下面的代码中,您需要在//选择管理注释后添加代码以确定触摸了哪个字符,检查它是否是特殊文本附件字符,然后修改编辑菜单或采取其他操作。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];

    if ([theTouch tapCount] == 2  && [self becomeFirstResponder]) {

        // selection management code goes here...

        // bring up edit menu.
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        CGRect selectionRect = CGRectMake (currentSelection.x, currentSelection.y, SIDE, SIDE);
        [theMenu setTargetRect:selectionRect inView:self];
        [theMenu setMenuVisible:YES animated:YES];

    }
}

Alternately you could add a custom menu by adding the menu item and then modifying the canPerformAction method.

或者,您可以通过添加菜单项然后修改canPerformAction方法来添加自定义菜单。

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    LOG(@"canPerformAction: called");

    if (action == @selector(viewImage)) {
       // Check the selected character is the special text attachment character

       return YES;
    }
   return NO;
}

Here is some addition code but its a bit fussy. Second method just disables the default edit menu if an attachment is detected.

这是一些额外的代码,但它有点挑剔。如果检测到附件,则第二种方法仅禁用默认编辑菜单。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    FLOG(@"touchesBegan:withEvent: called");

    if (self.selectedRange.location != NSNotFound) {
        FLOG(@" selected location is %d", self.selectedRange.location);

        int ch;

        if (self.selectedRange.location >= self.textStorage.length) {
            // Get the character at the location
            ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
        } else {
            // Get the character at the location
            ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
        }

        if (ch == NSAttachmentCharacter) {
            FLOG(@" selected character is %d, a TextAttachment", ch);
        } else {
            FLOG(@" selected character is %d", ch);
        }
    }

}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    FLOG(@"canPerformAction: called");

        FLOG(@" selected location is %d", self.selectedRange.location);
        FLOG(@" TextAttachment character is %d", NSAttachmentCharacter);

        if (self.selectedRange.location != NSNotFound) {

            int ch;

            if (self.selectedRange.location >= self.textStorage.length) {
                // Get the character at the location
                ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
            } else {
                // Get the character at the location
                ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
            }

            if (ch == NSAttachmentCharacter) {
                FLOG(@" selected character is %d, a TextAttachment", ch);
                return NO;
            } else {
                FLOG(@" selected character is %d", ch);
            }

            // Check for an attachment
            NSTextAttachment *attachment = [[self textStorage] attribute:NSAttachmentAttributeName atIndex:self.selectedRange.location effectiveRange:NULL];
            if (attachment) {
                FLOG(@" attachment attribute retrieved at location %d", self.selectedRange.location);
                return NO;
            }
            else
                FLOG(@" no attachment at location %d", self.selectedRange.location);
        }
    return [super canPerformAction:action withSender:sender];
}

#2


2  

Swift 3 answer:

斯威夫特3回答:

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

Make sure your textView isEditable = false, isSelectable = true, and isUserInteractionEnabled = true. Duncan's answer did not mention isUserInteractionEnabled, this has to be true, otherwise it wont work.

确保textView isEditable = false,isSelectable = true,isUserInteractionEnabled = true。 Duncan的回答没有提到isUserInteractionEnabled,这必须是真的,否则它不会工作。

You can do this programmatically (textView.isEditable = false), or via attributes inspector: 如何检测NSTextAttachment上的触摸

如何检测NSTextAttachment上的触摸

您可以通过编程方式(textView.isEditable = false)或通过属性检查器执行此操作:

#3


0  

Use hitTest to get the touch in a subclassed UITextView. This avoids the issue of messing up the standard editing functions. From the position get the character index and then check the character for the attachment.

使用hitTest在子类UITextView中进行触摸。这避免了弄乱标准编辑功能的问题。从位置获取字符索引,然后检查附件的字符。

#4


0  

Apple make this really difficult. As others point out, the delegate method is called, but only when isEditable is false, or when the user does a tap and hold on the attachment. If you want to be informed about a simple tap interaction during editing, forget it.

Apple让这真的很难。正如其他人指出的那样,委托方法被调用,但仅当isEditable为false时,或者当用户点击并按住附件时。如果您想在编辑过程中了解简单的点按互动,请忘掉它。

I went down the touchesBegan: and hitTest: paths, both with problems. The touches methods are called after the UITextView has already handled the interaction, and the hitTest: is too crude, because it messes with the first responder status and so forth.

我走下了touchesBegan:和hitTest:路径,都有问题。在UITextView已经处理了交互之后调用touches方法,而hitTest:太粗糙了,因为它与第一个响应者状态混淆等等。

My solution in the end was gesture recognizers. Apple are using those internally, which explains why touchesBegan: is not really viable in the first place: the gesture recognizers have already handled the event.

我的解决方案最终是手势识别器。 Apple正在内部使用它们,这解释了为什么touchesBegan:首先不是真正可行的:手势识别器已经处理了这个事件。

I created a new gesture recognizer class for use with a UITextView. It simply checks for the location of the tap, and if it is an attachment, it handles it. I make all the other gesture recognizers subordinate to my one, so we get first look at the events, and the others only come into play if our one fails.

我创建了一个新的手势识别器类,用于UITextView。它只是检查水龙头的位置,如果是附件,它会处理它。我将所有其他手势识别器从属于我的手势识别器,因此我们先看看事件,其他人只有在我们的事件失败时才会发挥作用。

The gesture recognizer class is below, along with an extension for adding it to UITextView. I add it in my UITextView subclass in awakeFromNib, like this. (You needn't use a subclass if you don't have one.)

下面是手势识别器类,以及用于将其添加到UITextView的扩展。我将它添加到awakeFromNib中的UITextView子类中,就像这样。 (如果没有子类,则无需使用子类。)

override func awakeFromNib() {
    super.awakeFromNib()

    let recognizer = AttachmentTapGestureRecognizer(target: self, action: #selector(handleAttachmentTap(_:)))
    add(recognizer)

and I handle the action by calling the existing UITextViewDelegate method textView(_:,shouldInteractWith:,in:,interaction:). You could just as easily put the handling code directly in the action, rather than using the delegate.

我通过调用现有的UITextViewDelegate方法textView(_:,shouldInteractWith:,in:,interaction :)来处理动作。您可以轻松地将处理代码直接放在操作中,而不是使用委托。

@IBAction func handleAttachmentTap(_ sender: AttachmentTapGestureRecognizer) {
    let _ = delegate?.textView?(self, shouldInteractWith: sender.attachment!, in: NSRange(location: sender.attachmentCharacterIndex!, length: 1), interaction: .invokeDefaultAction)
}

Here is the main class.

这是主要的课程。

import UIKit
import UIKit.UIGestureRecognizerSubclass

/// Recognizes a tap on an attachment, on a UITextView.
/// The UITextView normally only informs its delegate of a tap on an attachment if the text view is not editable, or a long tap is used.
/// If you want an editable text view, where you can short cap an attachment, you have a problem.
/// This gesture recognizer can be added to the text view, and will add requirments in order to recognize before any built-in recognizers.
class AttachmentTapGestureRecognizer: UIGestureRecognizer {

    /// Character index of the attachment just tapped
    private(set) var attachmentCharacterIndex: Int?

    /// The attachment just tapped
    private(set) var attachment: NSTextAttachment?

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        attachmentCharacterIndex = nil
        attachment = nil

        let textView = view as! UITextView
        if touches.count == 1, let touch = touches.first, touch.tapCount == 1 {
            let point = touch.location(in: textView)
            let glyphIndex: Int? = textView.layoutManager.glyphIndex(for: point, in: textView.textContainer, fractionOfDistanceThroughGlyph: nil)
            let index: Int? = textView.layoutManager.characterIndexForGlyph(at: glyphIndex ?? 0)
            if let characterIndex = index, characterIndex < textView.textStorage.length {
                if NSAttachmentCharacter == (textView.textStorage.string as NSString).character(at: characterIndex) {
                    attachmentCharacterIndex = characterIndex
                    attachment = textView.textStorage.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment
                    state = .recognized
                } else {
                    state = .failed
                }
            }
        } else {
            state = .failed
        }
    }
}

extension UITextView {

    /// Add an attachment recognizer to a UITTextView
    func add(_ attachmentRecognizer: AttachmentTapGestureRecognizer) {
        for other in gestureRecognizers ?? [] {
            other.require(toFail: attachmentRecognizer)
        }
        addGestureRecognizer(attachmentRecognizer)
    }

}

This same approach could presumably be used for taps on links.

也许这种方法可以用于链接上的点击。