当用户在NSTextAttachment上点击iOS时,最好的检测方法是什么?
我认为其中一种方法是在卡雷特的位置上检查角色是否是NSAttachmentCharacter,但这似乎不对。
我也尝试过UITextViewDelegate方法:-(BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange,但是当textView.editable=YES时没有调用它
发布于 2013-10-25 01:27:08
委托方法确实有效,但只有当附件中的图像属性中有图像和可编辑= NO时才能工作!因此,如果将图像从其他地方粘贴到attributedString,则数据似乎最终存储在fileWrapper中,下次将attributedString放回textView时,图像属性为零,布局管理器或其他任何东西都会从fileWrapper获取图像。
在文档中的某个地方,它确实提到在NSTextAttachment中没有用于持久化图像属性的方法。
要测试这一点,请尝试从照片应用程序复制一张照片并将其粘贴到您的textView中,现在如果您按住手指,您应该会看到默认菜单弹出。现在,如果您保存这个丰富的文本,比方说在一个核心数据实体中,然后检索它,图像属性将为零,但是图像数据将在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:要在编辑模式下定制菜单,请查看以下苹果文档,问题是“touchEnded”似乎从未被调用过,因此您可能不得不尝试使用touchesBegan。但是要小心,不要干扰默认的编辑行为。
注意,在下面的代码中,您需要在// 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];
}发布于 2019-04-25 17:00:41
乔什的answer几乎是完美的。但是,如果您在输入结束后点击UITextView的空格,那么UITextView将返回字符串中的最后一个字形。如果这是你的附件,它将不正确地评估为真。
苹果公司的文档说:如果没有字形在点下,则返回最近的字形,其中最近的符号是根据鼠标选择的要求定义的。希望确定该点是否位于返回的字形范围内的客户端应该在此之后调用boundingRect(forGlyphRange: in :),并测试该点是否位于该方法返回的矩形中。
因此,这里是一个经过调整的版本(Swift 5,XCode 10.2),它对检测到的字形的边界执行额外的检查。我相信一些characterIndex测试现在是多余的,但它们不会伤害任何东西。
一个警告:字形似乎延伸到包含它们的线条的高度。如果你有一个高的肖像图像附件旁边的景观图像附件,点击上方的空白景观图像仍将评估为真。
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)
}
}发布于 2018-03-07 13:34:15
苹果让这件事变得很困难。正如其他人所指出的,委托方法被调用,但只有当isEditable是false时,或者当用户执行点击并保持附件时,才调用委托方法。如果你想在编辑过程中了解一个简单的点击交互,那就算了吧。
我沿着touchesBegan:和hitTest:的道路前进,都遇到了问题。在UITextView已经处理了交互之后调用touches方法,而hitTest:太粗糙了,因为它扰乱了第一个响应程序的状态等等。
最后我的解决方案是手势识别器。苹果正在内部使用这些,这就解释了为什么touchesBegan:一开始并不可行:手势识别器已经处理了这个事件。
我创建了一个用于UITextView的新手势识别器类。它只是检查龙头的位置,如果它是附件,它就会处理它。我让所有其他手势识别器都从属于我的手势识别器,所以我们首先要查看事件,而其他手势识别器只有在我们的手势识别失败时才能发挥作用。
手势识别器类如下所示,以及将其添加到UITextView的扩展。我将其添加到awakeFromNib中的awakeFromNib子类中,如下所示。(如果没有子类,则不需要使用子类。)
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)
}
}同样的方法大概也可以用于链接上的点击。
https://stackoverflow.com/questions/19318092
复制相似问题