iOS应用中的iMessage风格逐渐消失的键盘

66

我一直在想,是否有可能在不使用任何私有API调用的情况下,在消息应用程序中复制苹果iOS5键盘的行为。当您在消息应用程序中向下滚动键盘时,键盘会折叠,从而留下更多空间来查看消息 - 试试看。

我找不到任何指向这样做的线索,除非开始跳跃以获取键盘视图的实例。而且我相信苹果不会对此感到满意。

除了下面给出的答案,您还可以在此处查看我的实现的完整xcode项目:https://github.com/orta/iMessage-Style-Receding-Keyboard


1
我相当确定的是,所有的工作都是将键盘上下文渲染成位图,其余的就是魔法(例如跟踪滚动位置,来回移动渲染的图像等)。 - Nick
Instagram有这种行为。@jasongregori - nickw444
@nickw444 哇,你说得完全正确。我看到下面的David的解决方案没有使用私有API调用,但它确实访问了私有视图,这仍然让我感到担忧。 - jasongregori
4
这显然是一个有偏见的观点,但如果你正在寻找一种可以轻松实现键盘功能并适用于各种视图类型的方法,我建议使用DAKeyboardControl:https://github.com/danielamitay/DAKeyboardControl - Daniel Amitay
@DanielAmitay 在 swizzled_addSubView 中出现了一些崩溃。它将子视图添加到已释放的实例中。 - Itesh
显示剩余6条评论
3个回答

50

iOS 7中UIScrollView有一个名为keyboardDismissMode的属性。 只需将其设置为“UIScrollViewKeyboardDismissModeInteractive”,您就可以获得此行为。适用于UIScrollView的子类,例如UITableView。

self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;

Swift 3:

tableView.keyboardDismissMode = .interactive

如果使用storyboard,则可以在属性检查器中更改UIScrollView子类的属性。


1
你如何追踪当前键盘位置? - João Nunes
当您将键盘向下发送时,并且您想根据键盘位置调整UI。您如何在那一点上知道键盘框架?或者您如何获取有关键盘移动的通知? - João Nunes
@JoãoNunes 通常你会监听 UIKeyboardNotifications。但据我所知,它们只会告诉你一些像“将要显示键盘”、“将要隐藏键盘”的信息。如果你需要根据确切的键盘框架来改变你的 UI,那就是一个不同的问题了,超出了这个范围。 - Vladimir Shutyuk
1
当你处于交互模式时,你需要追踪键盘的位置或者至少需要追踪某些能让你知道交互百分比的东西。在 iMessage 应用中,当键盘弹出和收起时,输入框也会上下移动! - João Nunes
@JoãoNunes 是的,一切都有起有落,但在滚动视图子类(如表视图)中,你可以轻松实现这一点。这就是为什么这种新的键盘模式很酷的原因。 - Vladimir Shutyuk
显示剩余2条评论

22

这是一个不完整的解决方案,但它应该为您提供一个很好的起点。

将以下ivars添加到您的UIViewController中:

CGRect        keyboardSuperFrame; // frame of keyboard when initially displayed
UIView      * keyboardSuperView;  // reference to keyboard view

将inputAccessoryView添加到您的文本控制器中。我创建了一个小视图作为accessoryView插入:

accView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
accView.backgroundColor = [UIColor clearColor];
textField.inputAccessoryView = accView;

我将上述代码加入到-(void)loadView

当视图加载时,注册以接收UIKeyboardDidShowNotification和UIKeyboardDidHideNotification:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(keyboardWillShow:)
        name:UIKeyboardWillShowNotification
        object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(keyboardDidShow:)
        name:UIKeyboardDidShowNotification
        object:nil];
    return;
}

为指定的选择器添加作为通知的方法:

// method is called whenever the keyboard is about to be displayed
- (void)keyboardWillShow:(NSNotification *)notification
{
    // makes keyboard view visible incase it was hidden
    keyboardSuperView.hidden = NO;
    return;
}
// method is called whenever the keyboard is displayed
- (void) keyboardDidShow:(NSNotification *)note
{
    // save reference to keyboard so we can easily determine
    // if it is currently displayed
    keyboardSuperView  = textField.inputAccessoryView.superview;

    // save current frame of keyboard so we can reference the original position later
    keyboardSuperFrame = textField.inputAccessoryView.superview.frame;
    return;
}

添加跟踪被触摸的方法并更新键盘视图:

// stops tracking touches to divider
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGRect newFrame;
    CGRect bounds = [[UIScreen mainScreen] bounds];

    newFrame = keyboardSuperFrame;
    newFrame.origin.y = bounds.size.height;  

    if ((keyboardSuperView.superview))
        if (keyboardSuperFrame.origin.y != keyboardSuperView.frame.origin.y)
            [UIView animateWithDuration:0.2
                    animations:^{keyboardSuperView.frame = newFrame;}
                    completion:^(BOOL finished){
                                keyboardSuperView.hidden = YES;
                                keyboardSuperView.frame = keyboardSuperFrame;
                                [textField resignFirstResponder]; }];
    return;
}


// updates divider view position based upon movement of touches
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
   UITouch  * touch;
   CGPoint    point;
   CGFloat    updateY;

   if ((touch = [touches anyObject]))
   {
      point   = [touch locationInView:self.view];
      if ((keyboardSuperView.superview))
      {
         updateY = keyboardSuperView.frame.origin.y;
         if (point.y < keyboardSuperFrame.origin.y)
            return;
         if ((point.y > updateY) || (point.y < updateY))
            updateY = point.y;
         if (keyboardSuperView.frame.origin.y != updateY)
            keyboardSuperView.frame = CGRectMake(keyboardSuperFrame.origin.x,
                                                 point.y,
                                                 keyboardSuperFrame.size.width,
                                                 keyboardSuperFrame.size.height);
      };
   };
   return;
}

免责声明:

  • 当第一次响应时,键盘会移回到原始位置,然后滑出屏幕。为了使键盘消失更加流畅,您需要首先创建一个动画将键盘移出屏幕,然后隐藏视图。我将把这部分留给读者作为练习。
  • 我只在iOS 5模拟器和iPhone上测试过这个功能,并且是在iOS 5下进行的。我没有在早期版本的iOS上测试过。

测试这个概念我创建了一个SlidingKeyboard项目,位于BindleKit的examples目录中:

https://github.com/bindle/BindleKit

编辑:更新示例,以解决第一个免责声明。


我认为这是一个很好的实现,而且没有我必须做的很多麻烦才能到达键盘。 - orta
在iOS 4中,使用inputAccessoryView技巧来获取键盘句柄的方法是可行的。 - Micah Hainline
小错误修复:当向上拖动键盘时,如果拖动得太快,键盘将无法“粘”在屏幕上,而是会被取消。这可以通过将 if (point.y < keyboardSuperFrame.origin.y) return; 替换为 if (point.y < keyboardSuperFrame.origin.y) { if (keyboardSuperView.frame.origin.y != keyboardSuperFrame.origin.y) { keyboardSuperView.frame = keyboardSuperFrame; } return; } 来修复。 - Ginny
另一个问题是:如果你的视图没有占据整个窗口,触摸点可能会有些偏差(在我的情况下,偏移了64个点:44个用于导航栏,20个用于状态栏)。我通过将self.view替换为[self.view window]在该行中进行修复:point = [touch locationInView:self.view]; - Ginny
看起来这显示了 iOS 8 及以下版本的正确动画。在 iOS 9 上,动画消失了。键盘只会无动画地消失! - kevinl
在 https://dev59.com/jo_ea4cB1Zd3GeqPLDbu 找到了解决方案。 - kevinl

3

Vladimir的简单解决方案可以在用户向下滚动时隐藏键盘。然而,为了完成关于iMessage的问题,为了使TextField始终可见并锚定在键盘顶部,您需要实现以下方法:

- (UIView *) inputAccessoryView {
     // Return your textfield, buttons, etc
}

- (BOOL) canBecomeFirstResponder {
    return YES;
}

这是一篇很好的教程,更加详细地介绍了它。


这实际上是实现iMessage风格的消息输入工具栏的正确方式...哇..我简直不敢相信苹果把它变得如此复杂,而与此同时,它一直在我的眼前。无论如何,谢谢! - nekonari

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