2013-10-11 112 views
13

當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

回答

3

委託方法做工作,它不被調用,但只有當附着在圖像屬性,如果編輯圖像= 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

0

使用hitTest獲取子類UITextView中的觸摸。這樣可以避免混淆標準編輯功能的問題。從該位置獲取字符索引,然後檢查附件的字符。

2

斯威夫特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

0

蘋果使這個真的很難。正如其他人指出的,代表方法被調用,但只有當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) 
    } 

} 

這種方法大概可以用於鏈接上的點擊。