如何检测NSTextAttachment上的触摸

问题描述:

当iOS上的用户点击NSTextAttachment时,什么是最佳方式?如何检测NSTextAttachment上的触摸

我认为其中一种方法是检查carret的位置上的字符是否是NSAttachmentCharacter,但它看起来不正确。

我也试过UITextViewDelegate方法:-(BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange但当textView.editable=YES

+0

嗨米歇尔,我现在有完全相同的问题,并希望能够检测到'NSTextAttachment'即使'textView.editable = YES'上的点击和触摸 - 您是否找到了解决方案? – Jon

委托方法做工作,它不被调用,但只有当附着在图像属性,如果编辑图像= NO!所以如果你从别的地方将图像粘贴到了attributesString中,那么数据似乎最终被存储在fileWrapper中,并且下一次将attributesString放回到textView中时,image属性为零,而布局管理器或任何获取图像来自fileWrapper。

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

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

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

  1. 把你的字符串回的TextView你发现之前创建自己的自定义NSTextAttachment,包括用于归档图像和其他设置方法(请告诉我怎么过当你明白这出一个)
  2. 每次所有附件并重新创建图像属性,如下所示:

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

记住这样做的副作用是使fileWrapper无效。我想调整图像大小,但也要保留原始图像,以免丢失全部分辨率。我认为这样做的唯一方法可能是将NSTextAttachment转换为子类。

编辑:

我想出了如何创建自定义NSTextAttachments - 这里是一个链接为那些有兴趣http://ossh.com.au/design-and-technology/software-development/implementing-rich-text-with-images-on-os-x-and-ios/

编辑2:自定义菜单时,在编辑模式下看到下面的Apple文档中,问题是'touchEnded'似乎永远不会被调用,所以你可能不得不尝试使用touchesBegan。小心你不要干预默认的编辑行为。

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

注意,在下面的代码,你需要后// selection management评论添加代码,以确定哪些字符被感动了,检查它是否是特殊文本附件的性格和 然后修改编辑菜单或采取一些其他行动。

- (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方法来添加自定义菜单。

- (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; 
} 

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

- (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]; 
} 
+0

感谢您的回答,但我认为最初的问题是如何设置检测触摸,即使'textView.editable = YES'设置。 – Jon

+0

使用与用于显示文本编辑弹出式菜单相同的方法即可。你必须检查选定的字符是否是特殊的文本附加字符 - 我会看看我能否找到一个例子。 –

+0

@DuncanGroenewald - 这个答案的很大一部分似乎对这个问题完全没有影响。 – ArtOfWarfare

使用hitTest获取子类UITextView中的触摸。这样可以避免混淆标准编辑功能的问题。从该位置获取字符索引,然后检查附件的字符。

斯威夫特3回答:

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

确保TextView isEditable = falseisSelectable = trueisUserInteractionEnabled = true。邓肯的回答没有提到isUserInteractionEnabled,这必须是true,否则它不会工作。

你可以这样做编程(textView.isEditable = FALSE),或者通过属性检查器: enter image description here

enter image description here

苹果使这个真的很难。正如其他人指出的,代表方法被调用,但只有当isEditablefalse时,或者当用户点击并按住附件时。如果您想在编辑过程中了解简单的点按互动,请将其忽略。

我走下touchesBegan:hitTest:路径,两者都有问题。触摸方法在之后被称为UITextView已经处理了交互,并且hitTest:太粗糙了,因为它与第一响应者状态等混乱。

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

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

手势识别器类如下,以及将其添加到UITextView的扩展名。我将它添加到awakeFromNibUITextView子类中,就像这样。 (你不需要使用一个子类,如果你没有一个。)

override func awakeFromNib() { 
    super.awakeFromNib() 

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

,我通过调用现有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) 
} 

这里是主类。

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

} 

这种方法大概可以用于链接上的点击。