首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何检测NSTextAttachment上的触摸

如何检测NSTextAttachment上的触摸
EN

Stack Overflow用户
提问于 2013-10-11 12:23:39
回答 7查看 11.4K关注 0票数 17

当用户在NSTextAttachment上点击iOS时,最好的检测方法是什么?

我认为其中一种方法是在卡雷特的位置上检查角色是否是NSAttachmentCharacter,但这似乎不对。

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

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2013-10-25 01:27:08

委托方法确实有效,但只有当附件中的图像属性中有图像和可编辑= NO时才能工作!因此,如果将图像从其他地方粘贴到attributedString,则数据似乎最终存储在fileWrapper中,下次将attributedString放回textView时,图像属性为零,布局管理器或其他任何东西都会从fileWrapper获取图像。

在文档中的某个地方,它确实提到在NSTextAttachment中没有用于持久化图像属性的方法。

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

这很痛苦,我很想知道工程师的意图。看来你有两种选择。

  1. 创建您自己的自定义NSTextAttachment,并包括用于归档图像和其他设置的方法(当您计算出此设置时,也请向我展示)
  2. 每次在将字符串放回textView之前,都会找到所有附件并重新创建图像属性,如下所示: imageWithData:attachment.fileWrapper.regularFileContents;= attachment.image = UIImage

请记住,这样做的副作用是使fileWrapper无效。我想调整图像的大小,但也保留原来的,这样我就不会松散的全部分辨率。我认为这样做的唯一方法可能是子类NSTextAttachment。

编辑:

我想出了如何创建自定义NSTextAttachments --以下是对http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os-x-and-ios/感兴趣的人的链接

编辑2:要在编辑模式下定制菜单,请查看以下苹果文档,问题是“touchEnded”似乎从未被调用过,因此您可能不得不尝试使用touchesBegan。但是要小心,不要干扰默认的编辑行为。

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

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

代码语言:javascript
复制
- (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];

    }
}

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

代码语言:javascript
复制
- (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;
}

这里有一些附加代码,但有点繁琐。第二个方法只是在检测到附件时禁用默认编辑菜单。

代码语言:javascript
复制
- (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];
}
票数 3
EN

Stack Overflow用户

发布于 2019-04-25 17:00:41

乔什的answer几乎是完美的。但是,如果您在输入结束后点击UITextView的空格,那么UITextView将返回字符串中的最后一个字形。如果这是你的附件,它将不正确地评估为真。

苹果公司的文档说:如果没有字形在点下,则返回最近的字形,其中最近的符号是根据鼠标选择的要求定义的。希望确定该点是否位于返回的字形范围内的客户端应该在此之后调用boundingRect(forGlyphRange: in :),并测试该点是否位于该方法返回的矩形中。

因此,这里是一个经过调整的版本(Swift 5,XCode 10.2),它对检测到的字形的边界执行额外的检查。我相信一些characterIndex测试现在是多余的,但它们不会伤害任何东西。

一个警告:字形似乎延伸到包含它们的线条的高度。如果你有一个高的肖像图像附件旁边的景观图像附件,点击上方的空白景观图像仍将评估为真。

代码语言:javascript
复制
import UIKit
import UIKit.UIGestureRecognizerSubclass

// Thanks to https://stackoverflow.com/a/52883387/658604
// and https://stackoverflow.com/a/49153247/658604

/// 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: UITapGestureRecognizer {

    typealias TappedAttachment = (attachment: NSTextAttachment, characterIndex: Int)

    private(set) var tappedState: TappedAttachment?

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

        guard let textView = view as? UITextView else {
            state = .failed
            return
        }

        if let touch = touches.first {
            tappedState = evaluateTouch(touch, on: textView)
        }

        if tappedState != nil {
            // UITapGestureRecognizer can accurately differentiate discrete taps from scrolling
            // Therefore, let the super view evaluate the correct state.
            super.touchesBegan(touches, with: event)

        } else {
            // User didn't initiate a touch (tap or otherwise) on an attachment.
            // Force the gesture to fail.
            state = .failed
        }
    }

    /// Tests to see if the user has tapped on a text attachment in the target text view.
    private func evaluateTouch(_ touch: UITouch, on textView: UITextView) -> TappedAttachment? {
        let point = touch.location(in: textView)
        let glyphIndex: Int = textView.layoutManager.glyphIndex(for: point, in: textView.textContainer, fractionOfDistanceThroughGlyph: nil)
        let glyphRect = textView.layoutManager.boundingRect(forGlyphRange: NSRange(location: glyphIndex, length: 1), in: textView.textContainer)
        guard glyphRect.contains(point) else {
            return nil
        }
        let characterIndex: Int = textView.layoutManager.characterIndexForGlyph(at: glyphIndex)
        guard characterIndex < textView.textStorage.length else {
            return nil
        }
        guard NSTextAttachment.character == (textView.textStorage.string as NSString).character(at: characterIndex) else {
            return nil
        }
        guard let attachment = textView.textStorage.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment else {
            return nil
        }
        return (attachment, characterIndex)
    }
}
票数 7
EN

Stack Overflow用户

发布于 2018-03-07 13:34:15

苹果让这件事变得很困难。正如其他人所指出的,委托方法被调用,但只有当isEditablefalse时,或者当用户执行点击并保持附件时,才调用委托方法。如果你想在编辑过程中了解一个简单的点击交互,那就算了吧。

我沿着touchesBegan:hitTest:的道路前进,都遇到了问题。在UITextView已经处理了交互之后调用touches方法,而hitTest:太粗糙了,因为它扰乱了第一个响应程序的状态等等。

最后我的解决方案是手势识别器。苹果正在内部使用这些,这就解释了为什么touchesBegan:一开始并不可行:手势识别器已经处理了这个事件。

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

手势识别器类如下所示,以及将其添加到UITextView的扩展。我将其添加到awakeFromNib中的awakeFromNib子类中,如下所示。(如果没有子类,则不需要使用子类。)

代码语言:javascript
复制
override func awakeFromNib() {
    super.awakeFromNib()

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

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

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

这是主修课。

代码语言:javascript
复制
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)
    }

}

同样的方法大概也可以用于链接上的点击。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19318092

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档