模仿 UITextView 的默认双击行为

Posted

技术标签:

【中文标题】模仿 UITextView 的默认双击行为【英文标题】:Mimic UITextView's default double tap behavior 【发布时间】:2014-12-28 01:43:45 【问题描述】:

有谁知道当用户用两根手指在UITextView 中点击时会调用什么方法?

当用户用两根手指点击时,段落中的整个文本都会被选中。我想以编程方式实现相同的选择,以使此段落选择在我的自定义单击手势方法中可用。

【问题讨论】:

您是否已经设置了自定义单击手势和相关方法? 是的,- (IBAction)tapTextViewGesture:(id)sender NSLog(@"TAP"); 【参考方案1】:

UITextView双击手势识别器的默认行为来看,我认为selectAll:是被调用来处理文本选择的方法。同样,您可以通过在现有的tapTextViewGesture: 方法中使用selectAll: 来强制您的文本视图在识别您的单击手势识别器时选择文本(如您的评论中所述)。

如果您希望文本选项自动显示以响应默认的双击手势识别器(即剪切、复制、粘贴等),请将selectAll: 设置为self

- (IBAction)tapTextViewGesture:(id)sender 
    [self.textView selectAll:self]; 

否则,要简单地选择文本而不显示菜单,请将其设置为nil

- (IBAction)tapTextViewGesture:(id)sender 
    [self.textView selectAll:nil]; 

更新

正如 cmets 中的 OP 所指出的,UITextView 双击手势识别器最初只会导致选择单个段落。

首先,从当前光标位置显示编辑菜单:

// Access the application's shared menu
UIMenuController *menu = [UIMenuController sharedMenuController];

// Calculate the cursor's position within the superview
// and convert it to a CGRect
CGPoint cursorPosition = [self.textView caretRectForPosition:self.textView.selectedTextRange.start].origin;
CGPoint cursorPositionInView = [self.textView convertPoint:cursorPosition toView:self.view];
CGRect menuRect = CGRectMake(cursorPositionInView.x, cursorPositionInView.y, 0, 0);

// Show the menu from the cursor's position
[menu setTargetRect:menuRect inView:self.view];
[menu setMenuVisible:YES animated:YES];

然后选择当前段落,这是我的建议:

// Break the text into components separated by the newline character
NSArray *paragraphs = [self.textView.text componentsSeparatedByString:@"\n"];

// Keep a tally of the paragraph character count
int characterCount = 0;

// Go through each paragraph
for (NSString *paragraph in paragraphs) 

    // If the total number of characters up to the end
    // of the current paragraph is greater than or
    // equal to the start of the textView's selected
    // range, select the most recent paragraph and break
    // from the loop
    if (characterCount + paragraph.length >= self.textView.selectedRange.location) 
        [self.textView setSelectedRange:NSMakeRange(characterCount, paragraph.length)];
        break;
    

    // Increment the character count by adding the current
    // paragraph length + 1 to account for the newline character
    characterCount += paragraph.length + 1;

【讨论】:

非常感谢,这种方法有效,但不能用于选择单独的段落,它会选择 UITextView 中的所有文本。我的 textView 中的文本由几个段落组成。这些段落除以 \n\n 空格。在我的应用程序中,我只需要选择用户点击的段落,而不是 TextView 中的整个文本。你不知道如何实现段落选择吗? 需要与 Notes 应用程序相同的功能,但只需点击一下,这里是屏幕截图 link 。您可以在此屏幕截图中看到,当用户点击此段落时,它只选择段落,而不是整个文本。 谢谢林赛。您的代码有效,但在我的情况下(共享菜单)并不总是出现在段落的顶部。所以我把你的代码 link 和 @WorldOfWarcraft 的代码 link 混合出来了。 @Adelmaer 哦,我想我明白你的意思了......因为段落的顶部可能不在视图中,因此菜单将显示在文本视图上方......我猜是可能最好(1)在选择文本之前显示菜单以便它显示用户单击的位置或(2)滚动到段落的开头......现在,我会编辑我对选项 1 的回答。【参考方案2】:

参见UITextInputTokenizer 协议参考:

采用 UITextInputTokenizer 协议的类的实例是分词器;分词器允许文本输入系统评估不同粒度的文本单元。文本单元的粒度总是根据存储或参考方向进行评估。

根据协议,使用 - (UITextRange *)rangeEnclosingPosition:(UITextPosition *)position withGranularity:(UITextGranularity)granularity inDirection:(UITextDirection)direction 并将 UITextGranularity 设置为 UITextGranularityParagraph 以检测您设置的粒度的 textRange。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event


  if (touches.count == 2) 
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self.textView];

    //calculate the distance from touch
    UITextPosition *position = [self.textView closestPositionToPoint:touchLocation];
    NSUInteger distanceFromTouch = [self.textView offsetFromPosition:self.textView.beginningOfDocument
                                                          toPosition:position];
    //calculate the position by offset 
    UITextPosition *positionOffset = [self.textView positionFromPosition:self.textView.beginningOfDocument
                                                  offset:distanceFromTouch];

    //set up the granularity
    UITextGranularity granularity = UITextGranularityParagraph;

    //implement the protocol
    id<UITextInputTokenizer> tokenizer = self.textView.tokenizer;
    UITextRange *textRange = [tokenizer rangeEnclosingPosition:positionOffset
                                               withGranularity:granularity
                                                   inDirection:UITextWritingDirectionLeftToRight];

    //select the textRange
    [self.textView setSelectedTextRange:textRange];

    self.textView.keyboardType = UIKeyboardTypeNamePhonePad;

  

不要忘记分配协议。 @interface ViewController ()&lt;UITextInputTokenizer&gt;

【讨论】:

谢谢,这也适用于 UITextView 中的整个文本,但不适用于段落,我还添加了 [self.textView becomeFirstResponder];让它在我的 - (IBAction)tapTextViewGesture:(id)sender 方法中工作。但在我的情况下,我只需要选择用户触摸(点击)的段落,而不是我的 textView 中的整个文本。如果您在段落的笔记应用程序中双击,则仅选择该段落,而不是整个文本。我需要相同的功能。这是示例:link 谢谢,这个 UITextInputTokenizer 真的很棒。但在我的应用程序中没有显示副本。这就是为什么我最终将您的代码与 和粒度以及来自 Lyndsey 的显示复制菜单的答案的代码混合在一起。 @Adelmaer 你的意思是剪切/复制/粘贴菜单,为什么我的设备会显示? 我认为这是我的错,因为我已将您的代码放入 - (IBAction)tapTextViewGesture:(id)sender 方法,而不是 - (void)touchesEnded:(NSSet *)touches withEvent: (UIEvent *) 事件方法。最后决定制作一个自定义编辑菜单,一切正常。再次感谢,您的回答很有帮助,也解决了问题。 @Adelmaer :) 没问题。

以上是关于模仿 UITextView 的默认双击行为的主要内容,如果未能解决你的问题,请参考以下文章

如何将双击手势添加到 UITextView

向 UITextView 添加手势会取消默认行为

带有 UITextView 的 UIScrollView 的 Apple TV 默认滚动行为?

iOS 7 自定义分隔符不模仿默认分隔符行为。

可变高度 UITextView

双击 uitextview