【问题标题】:Keyboard handling just like in Messages app in iOS 7键盘处理就像在 iOS 7 中的消息应用程序中一样
【发布时间】:2014-08-29 08:44:22
【问题描述】:

我正在实现一个在某种程度上类似于 Messages 应用程序中发生的视图,因此有一个 UITextView 附加到屏幕底部的视图,还有一个 UITableView 显示主要内容。当它被点击时它会随着键盘向上滑动,当键盘被关闭时它会滑回屏幕底部。

我拥有的那部分工作正常 - 我刚刚订阅了键盘通知 - 将隐藏并显示。

问题是我已将 UITableView 上的键盘关闭模式设置为交互,并且在平移时我无法捕获对键盘的更改。

第二个问题是这个带有uitextview的栏覆盖了uitableview的某些部分。如何解决这个问题?我仍然希望 uitableview 在此栏“下方”,就像在消息应用中一样。

我在所有地方都使用 AutoLayout。

任何帮助将不胜感激!

============

编辑1: 这是一些代码:

View Hierarchy如下:

查看 - UITableView(这个将包含“消息”) - UIView(这个会滑动)

UITableView 对父视图的顶部、左侧、右侧和底部有限制,因此它会填满整个屏幕。 UIView 对父视图的左、右和底部有约束,因此它粘在底部 - 我通过调整约束上的常量来移动它。

在 ViewWillAppear 方法中:

NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.DidShowNotification, OnKeyboardDidShowNotification);
NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillChangeFrameNotification, OnKeyboardDidShowNotification);
NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillHideNotification, OnKeyboardWillHideNotification);

这里有一些方法:

void OnKeyboardDidShowNotification (NSNotification notification)
{
    AdjustViewToKeyboard (Ui.KeyboardHeightFromNotification (notification), notification);
}

void OnKeyboardWillHideNotification (NSNotification notification)
{   
    AdjustViewToKeyboard (0.0f, notification);
}

void AdjustViewToKeyboard (float offset, NSNotification notification = null)
{
    commentEditViewBottomConstraint.Constant = -offset;

    if (notification != null) {
        UIView.BeginAnimations (null, IntPtr.Zero);
        UIView.SetAnimationDuration (Ui.KeyboardAnimationDurationFromNotification (notification));
        UIView.SetAnimationCurve ((UIViewAnimationCurve)Ui.KeyboardAnimationCurveFromNotification (notification));
        UIView.SetAnimationBeginsFromCurrentState (true);
    }

    View.LayoutIfNeeded ();
    commentEditView.LayoutIfNeeded ();

    var insets = commentsListView.ContentInset;
    insets.Bottom = offset;
    commentsListView.ContentInset = insets;

    if (notification != null) {
        UIView.CommitAnimations ();
    }
}

【问题讨论】:

  • 键盘出现时你如何移动文本视图。你能显示一些代码。不是 100% 确定你在问什么。
  • 已添加代码,希望对您有所帮助。第一个问题是 UITableView 将 KeyboardDismissMode 设置为 Interactive,因此当用户向下滑动手指时,键盘被关闭 - 不是立即,而是“跟随”手指。我不知道如何更好地解释它 - 尝试在消息应用程序中进行。在这个实现中,键盘正确地“跟随”手指,但我的 UIView 没有。
  • 第二个问题是,因为我的 UIView 位于屏幕底部,它覆盖了 UITableView 的某些部分,并且它没有正确调整内容插入 - 我需要以某种方式调整它们。

标签: ios uitableview xamarin autolayout uikeyboard


【解决方案1】:

我正是为此目的制作了一个开源库。它适用于 iOS 7 和 8,也可以作为 cocoapod 工作。

https://github.com/oseparovic/MessageComposerView

这是它的外观示例:

您可以使用如下所示的一个非常基本的init 函数来创建具有屏幕宽度和默认高度的它,例如:

self.messageComposerView = [[MessageComposerView alloc] init];
self.messageComposerView.delegate = self;
[self.view addSubview:self.messageComposerView];

还有几个其他的初始化器也可用于自定义框架、键盘偏移和 textview 最大高度,以及一些委托来挂钩框架更改和按钮点击。请参阅自述文件了解更多信息!

【讨论】:

    【解决方案2】:

    此解决方案基于关于 SO 的许多不同答案。它有很多好处:

    • 键盘隐藏时撰写栏保持在底部
    • UITableView 上进行交互手势时,撰写 bas 跟随键盘
    • UITableViewCells 从下到上,就像在消息应用中一样
    • 键盘不妨碍查看全部UITableViewCells
    • 应该适用于 iOS6、iOS7 和 iOS8

    这段代码可以正常工作:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = // . . .
    
        // . . .
    
        cell.contentView.transform = CGAffineTransformMakeScale(1,-1);
        cell.accessoryView.transform = CGAffineTransformMakeScale(1,-1);
        return cell;
    }
    
    - (UIView *)inputAccessoryView {
        return self.composeBar;
    }
    
    - (BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.tableView.transform = CGAffineTransformMakeScale(1,-1);
    
        // This code prevent bottom inset animation while appearing view
        UIEdgeInsets newEdgeInsets = self.tableView.contentInset;
        newEdgeInsets.top = CGRectGetMaxY(self.navigationController.navigationBar.frame);
        newEdgeInsets.bottom = self.view.bounds.size.height - self.composeBar.frame.origin.y;
        self.tableView.contentInset = newEdgeInsets;
        self.tableView.scrollIndicatorInsets = newEdgeInsets;
        self.tableView.contentOffset = CGPointMake(0, -newEdgeInsets.bottom);
    
        // This code need to be done if you added compose bar via IB
        self.composeBar.delegate = self;
        [self.composeBar removeFromSuperview];
    
        [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillChangeFrameNotification object:nil queue:nil usingBlock:^(NSNotification *note)
        {
            NSNumber *duration = note.userInfo[UIKeyboardAnimationDurationUserInfoKey];
            NSNumber *options = note.userInfo[UIKeyboardAnimationCurveUserInfoKey];
            CGRect beginFrame = [note.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
            CGRect endFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    
            UIEdgeInsets newEdgeInsets = self.tableView.contentInset;
            newEdgeInsets.bottom = self.view.bounds.size.height - endFrame.origin.y;
            CGPoint newContentOffset = self.tableView.contentOffset;
            newContentOffset.y += endFrame.origin.y - beginFrame.origin.y;
    
            [UIView animateWithDuration:duration.doubleValue
                                  delay:0.0
                                options:options.integerValue << 16
                             animations:^{
                                 self.tableView.contentInset = newEdgeInsets;
                                 self.tableView.scrollIndicatorInsets = newEdgeInsets;
                                 self.tableView.contentOffset = newContentOffset;
                             } completion:^(BOOL finished) {
                                 ;
                             }];
        }];
    }
    

    例如使用pod 'PHFComposeBarView'撰写栏:

    @property (nonatomic, strong) IBOutlet PHFComposeBarView *composeBar;
    

    并将此类用于您的表格视图:

    @interface InverseTableView : UITableView
    @end
    @implementation InverseTableView
    void swapCGFLoat(CGFloat *a, CGFloat *b) {
        CGFloat tmp = *a;
        *a = *b;
        *b = tmp;
    }
    - (UIEdgeInsets)contentInset {
        UIEdgeInsets insets = [super contentInset];
        swapCGFLoat(&insets.top, &insets.bottom);
        return insets;
    }
    - (void)setContentInset:(UIEdgeInsets)contentInset {
        swapCGFLoat(&contentInset.top, &contentInset.bottom);
        [super setContentInset:contentInset];
    }
    @end
    

    如果您想通过点击消息使键盘消失:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        [self.composeBar.textView resignFirstResponder];
    }
    

    不要调用这个,这将完全隐藏composeBar

    [self resignFirstResponder];
    

    更新 2:

    新的键盘跟踪解决方案效果更好:

    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear:animated];
    
        // Compose view height growing tracking
        [self.composeBar addObserver:self forKeyPath:@"frame" options:0 context:nil];
        // iOS 7 keyboard tracking
        [self.composeBar.superview addObserver:self forKeyPath:@"center" options:0 context:nil];
        // iOS 8 keyboard tracking
        [self.composeBar.superview addObserver:self forKeyPath:@"frame" options:0 context:nil];
    }
    
    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
    
        [self.composeBar removeObserver:self forKeyPath:@"frame"];
        [self.composeBar.superview removeObserver:self forKeyPath:@"center"];
        [self.composeBar.superview removeObserver:self forKeyPath:@"frame"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        if (object == self.composeBar.superview || object == self.composeBar)
        {
            // Get all values
            CGPoint newContentOffset = self.tableView.contentOffset;
            UIEdgeInsets newEdgeInsets = self.tableView.contentInset;
            UIEdgeInsets newScrollIndicartorInsets = self.tableView.scrollIndicatorInsets;
    
            // Update values
            CGFloat bottomInset = self.view.bounds.size.height - [self.composeBar convertPoint:CGPointZero toView:self.view].y;
            CGFloat diff = newEdgeInsets.bottom - (bottomInset + 7);
            newContentOffset.y += diff;
            newEdgeInsets.bottom = bottomInset + 7;
            newScrollIndicartorInsets.bottom = bottomInset;
    
            // Set all values
            if (diff < 0 || diff > 40)
                self.tableView.contentOffset = CGPointMake(0, newContentOffset.y);
            self.tableView.contentInset = newEdgeInsets;
            self.tableView.scrollIndicatorInsets = newEdgeInsets;
        }
    }
    

    【讨论】:

      【解决方案3】:

      我建议您覆盖视图控制器的 -inputAccessoryView 属性,并将可编辑的 UITextView 作为其子视图。 另外,不要忘记覆盖 -canBecomeFirstResponder 方法以返回 YES。

      - (BOOL)canBecomeFirstResponder
      {
      if (!RUNNING_ON_IOS7 && !RUNNING_ON_IPAD)
      {
          //Workaround for iOS6-specific bug
          return !(self.viewDisappearing) && (!self.viewAppearing);
      }
      
      return !(self.viewDisappearing);
      }
      

      通过这种方法,系统可以管理一切。

      您还必须了解一些解决方法:针对 UISplitViewController (UISplitViewController detail-only inputAccessoryView)、针对释放错误 (UIViewController with inputAccessoryView is not deallocated) 等等。

      【讨论】:

      • 我什么都试过了。这是唯一适用于交互式关闭的解决方案。
      • 我在 WWDC 上与 Apple 的工程师进行了交谈,这就是消息应用程序中的工作方式
      • 感觉就像在消息中一样。完全没有故障...再次感谢!
      • 嗯,有很多小故障 - 我的 repo 有示例应用程序 - github.com/666tos/CustomAccessoryViewSample
      【解决方案4】:

      我会尝试使用一个空的、高度为零的inputAccessoryView。诀窍是在键盘出现时将文本字段的底部粘在上面,以便它们一起移动。当键盘消失时,您可以破坏该约束并再次粘在屏幕底部。

      【讨论】:

      • 这是一个有趣的想法,我想尝试一下。按照 Apple 的示例代码 keyboardAccessory,我在我的情节提要中添加了一个窄 UIView,位于 View Controller 的视图层次结构之外。在 viewDidLoad 中,我将此视图设置为 inputAccessoryView。然后在keyboardshow 事件中,我尝试设置附件视图和文本视图之间的约束。问题是这个约束似乎没有正确安装,而是执行崩溃,并显示消息:“视图层次结构没有为约束做好准备。”
      • 实际上不可能将您的文本字段“粘合”到输入附件视图,因为它们属于不同的窗口
      • 这应该被标记为正确答案!这是本机支持的。
      【解决方案5】:

      好的,交互式键盘关闭将发送一个名为 UIKeyboardDidChangeFrameNotification 的通知。

      这可用于在交互关闭键盘时移动文本视图。

      您已经在使用它,但您将其发送到 OnKeyboardDidShow 方法。

      您需要第三种方法,称为keyboardFramedDidChange。这适用于隐藏和显示。

      对于第二个问题,你应该有这样的垂直约束......

      |[theTableView][theTextView (==44)]|
      

      这会将表格视图的底部与文本视图的顶部联系起来。

      这不会改变任何动画的工作方式,它只会确保表格视图将显示其所有内容,无论键盘是否可见。

      不要更新表格视图的内容插图。使用约束确保帧不重叠。

      附:整理你的命名约定。方法名称以小写字母开头。

      P.P.S.使用基于块的动画。

      【讨论】:

      • @cichy202 设置tableView.clipsToBounds = NO :)
      • 这是 C# 代码,所以 CamelCase 方法遵循命名约定 ;)
      • 我不使用基于块的动画,因为我使用通知中的动画曲线。
      • 不幸的是,当用户拖动键盘时,keyboardFramedDidChange 通知没有被触发。
      • 用户拖动键盘时是否会触发通知?如果没有,有没有办法以干净的方式检测阻力?
      猜你喜欢
      • 1970-01-01
      • 2014-02-10
      • 2014-05-22
      • 1970-01-01
      • 2012-07-03
      • 1970-01-01
      • 2014-02-15
      • 1970-01-01
      • 2011-05-15
      相关资源
      最近更新 更多