如何将UITextView的自动更正建议移动到文本上方

10

我正在使用UITextView大致复制键盘上方的短信文本框。 我使用UITextView而不是UITextField,以便它可以随着多行文本而扩展。

问题是,在我的UITextView中,更正建议弹出在文本下方,导致它们被键盘部分遮挡。

在短信应用中,建议会在文本上方弹出。该位置似乎不是UITextView或UITextInputTraits属性的一部分。

有任何想法如何复制此行为吗?谢谢!


只有两个选择:1)使用私有方法或2)将您的UITextView移动到足够高的位置,以便不会遮挡您的更正气泡。 - bentford
7个回答

8
问题在于键盘是作为一个独立的UIWindow实现的,而不是作为主UIWindow中的一个视图,因此使用它进行布局比较棘手。以下是一些指向正确方向的提示:
  • 通过查找应用程序的“-windows”属性来查找私有的UITextEffectsWindow窗口,并确定其框架。这就是键盘。
  • 通过TextView的子视图查找私有的UIAutocorrectInlinePrompt视图。这是自动更正气泡。
  • 将该子视图移动到单独的包装器视图(添加到TextView中),然后将该包装器视图移动到上述键盘窗口之上。
你会注意到以上提到了“private”两次。这带有所有相关警告。我不知道为什么即使是他们的应用程序也必须解决这个问题,苹果还允许这个问题存在。

1
我在想,如果遍历子视图而不明确引用私有类是否被认为是违规的?我的意思是,我假设它扩展了UIView对吧?如果我只是碰巧搜索UIView树并更改其中一个的位置呢?这对我来说似乎不是“私有”的,但也许苹果公司持不同意见... - DougW
遍历子视图完全基于公共API。但无论如何,您最终都会依赖于未记录的行为。这里的主要危险是更新可能会使您崩溃,因此在生产代码中,我希望能够优雅地失败。我不认为这两个私有子类是非常稳定的,因为它们都有点奇怪,而奇怪的东西最有可能被苹果重新设计。信不信由你,苹果并不是出于恶意而将事物设为私有 :)很多时候,它们是苹果计划重构的东西。接受它所值得的。 - Rob Napier
你有没有示例代码?我很好奇你是使用了方法交换还是其他什么方式来实现这个... - marcc
很遗憾,我不能发布这段代码,但我们不需要方法交换。你只需要子类化UITextView并重载-layoutSubviews方法,在自动更正气泡被添加时调用该方法即可。 - Rob Napier

3

通过在重写或替换layoutSubViews中搜索UIAutocorrectInlinePrompt,可以更改校正的布局,使其出现在上方。您可以通过查找特定类的子视图并按预期位置放置它们来完成此操作,而无需调用任何私有API。此示例确定每个视图是哪个,检查校正是否已经在文本上方,并将校正移到上方,并在窗口上绘制它,使其不受UITextView本身的限制。显然,如果苹果更改底层实现,则此操作将无法移动校正。将此添加到您的重写或替换layoutSubViews实现中。

- (void) moveSpellingCorrection {
 for (UIView *view in self.subviews)
 {
  if ([[[view class] description] isEqualToString:@"UIAutocorrectInlinePrompt"])
  {
   UIView *correctionShadowView = nil; // [view correctionShadowView];

   for (UIView *subview in view.subviews)
   {
    if ([[[subview class] description] isEqualToString:@"UIAutocorrectShadowView"])
    {
     correctionShadowView = subview;
     break;
    }
   }

   if (correctionShadowView)
   {
    UIView *typedTextView = nil; //[view typedTextView];
    UIView *correctionView = nil; //[view correctionView];

    for (UIView *subview in view.subviews)
    {
     if ([[[subview class] description] isEqualToString:@"UIAutocorrectTextView"])
     {
      if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
      {
       correctionView = subview;
      }
      else
      { 
       typedTextView = subview;
      }
     }

    }
    if (correctionView && typedTextView)
    {

     CGRect textRect = [typedTextView frame];
     CGRect correctionRect = [correctionView frame];
     if (textRect.origin.y < correctionRect.origin.y)
     {
      CGAffineTransform moveUp = CGAffineTransformMakeTranslation(0,-50.0);
      [correctionView setTransform: moveUp];
      [correctionShadowView setTransform: moveUp];

      CGRect windowPos = [self convertRect: view.frame toView: nil ];
      [view removeFromSuperview];
      [self.window addSubview: view];
      view.frame = windowPos;
     }

    }
   }

  }

 }
}

谢谢你的代码。它确实很有趣,但我同意其他评论中的一些观点,认为它对于一般用途来说太不可预测了。 - DougW
1
这需要修复 - 它没有考虑接口方向。 - Eiko
这似乎运行良好,但重新父级导致自动更正视图在第二个键被敲击后移动到非常低的y位置(即当其内容被扩展时)。有任何想法为什么? - Tore Olsen

1
实际上,键盘只是使用-[UITextInput textInputView]的结果来确定纠正视图应该放置在哪里(以及询问您的视图是否支持纠正)。所以你只需要这样做:
- (UIView *)textInputView {
  for (UIWindow *window in [UIApplication sharedApplication].windows) {
    if ([window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")] && 
        window != self.window) {
      return window;
    }
  }

  // Fallback just in case the UITextEffectsWindow has not yet been created.
  return self;
}

请注意,您很可能还需要更新-[UITextInput firstRectForRange:]以使用窗口/设备的坐标系统,因此可以执行以下操作:
- (CGRect)firstRectForRange:(CoreTextTokenTextRange *)range {
  CGRect firstRect = [self firstRectForRangeInternal:range];

  return [self convertRect:firstRect toView:[self textInputView]];
}

(在上下文中,self是一个实现UITextInput的类。)

1
实际上正在做
textview.scrollEnabled = NO;

将气泡设置在文本上方...但需要注意的是,您会失去滚动功能,在我的情况下,由于只有一个用于输入且有字符限制的文本字段,这并不是问题。


通过将+1与contentSize相结合,您可以在需要时禁用/启用滚动。 - EsbenB
这对我来说不起作用。在iPhone 6s模拟器上运行的iOS9.3.1,我仍然会得到下面的自动更正文本。 - Chris Prince
是的,这是在 iOS7 的惊叹声中完成的,我需要检查一下这个解决方案是否仍然适用于新设备。 - Heavy_Bullets

0
将下面的方法 adjustAutocorrectPromptView 放在 layoutSubviews 中对我来说在纵向和横向上都有效。我有一个类别,提供了视图底部和顶部的方法,但你可以理解这个思路。
NSArray * subviewsWithDescription(UIView *view, NSString *description)
{
    return [view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:@"class.description == '%@'", description]]];
}

- (void) adjustAutocorrectPromptView;
{
    UIView *autocorrectPromptView = [subviewsWithDescription(self, @"UIAutocorrectInlinePrompt") lastObject];

    if (! autocorrectPromptView)
    {
        return;
    }

    UIView *correctionShadowView = [subviewsWithDescription(autocorrectPromptView, @"UIAutocorrectShadowView") lastObject];

    if (! correctionShadowView)
    {
        return;
    }

    UIView *typedTextView = nil; //[view typedTextView];
    UIView *correctionView = nil; //[view correctionView];

    for (UIView *subview in subviewsWithDescription(autocorrectPromptView, @"UIAutocorrectTextView"))
    {
        if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
        {
            correctionView = subview;
        }
        else
        {
            typedTextView = subview;
        }
    }

    if (correctionView && typedTextView)
    {
        if (typedTextView.top < correctionView.top)
        {
            correctionView.bottom = typedTextView.top;
            correctionShadowView.center = correctionView.center;
        }
    }
}

0

如果您的UITextView底部清除了键盘,您应该能够调整UITextView的大小,使其足够高以查看更正。这些更正本身不会显示在UITextView的框架之外。

如果您想模仿短信应用程序中的内容(上面的更正),您可能需要自己编写代码。


-2
确保您的视图控制器委托在键盘弹出时监听通知,以便调整UITextView的大小,使键盘不会遮挡UITextView。然后您的更正就不会被键盘遮挡了。请参见:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/12641-uitextview-scroll-while-editing.html

以下是该页面代码的副本,以防原始链接失效:

// the amount of vertical shift upwards keep the Notes text view visible as the keyboard appears
#define kOFFSET_FOR_KEYBOARD                    140.0

// the duration of the animation for the view shift
#define kVerticalOffsetAnimationDuration        0.50

- (IBAction)textFieldDoneEditing:(id)sender
{
    [sender resignFirstResponder];
}

- (IBAction)backgroundClick:(id)sender
{
    [latitudeField resignFirstResponder];
    [longitudeField resignFirstResponder];
    [notesField resignFirstResponder];

    if (viewShifted)
    {
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:kVerticalOffsetAnimationDuration];

        CGRect rect = self.view.frame;
        rect.origin.y += kOFFSET_FOR_KEYBOARD;
        rect.size.height -= kOFFSET_FOR_KEYBOARD;
        self.view.frame = rect;

        [UIView commitAnimations];

        viewShifted = FALSE;
    }       
}

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
    if (!viewShifted) {     // don't shift if it's already shifted

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:kVerticalOffsetAnimationDuration];

        CGRect rect = self.view.frame;      
        rect.origin.y -= kOFFSET_FOR_KEYBOARD;
        rect.size.height += kOFFSET_FOR_KEYBOARD;
        self.view.frame = rect;

        [UIView commitAnimations];

        viewShifted = TRUE;
    }
    return YES;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接