显示键盘时在 iPad 上的表单中调整表格视图的大小

Posted

技术标签:

【中文标题】显示键盘时在 iPad 上的表单中调整表格视图的大小【英文标题】:Resize tableview in form sheet on iPad when keyboard is shown 【发布时间】:2015-02-18 10:17:07 【问题描述】:

我在调整表格视图大小和滚动到活动文本字段时遇到困难,当键盘出现时,当视图以模态方式呈现在 iPad 上的表单时。它在 iPhone 上运行良好,因为我不必考虑表单视图的偏移量 - 我可以将 tableview 的底部 contentInset 更改为与键盘高度相同。但是,这在 iPad 上不起作用,因为表单以及它的 tableview 不会占据整个屏幕。

计算tableview的新底部contentInset应该是多少的最佳方法是什么?

【问题讨论】:

嗨!你找到解决问题的方法了吗?我也面临同样的情况。想知道其他人是否已经解决了这个问题。 【参考方案1】:

好的,我自己偶然发现了这个问题。我创建了一个适用于各种情况的解决方案(因此不仅适用于以表单形式呈现的视图控制器)。解决方案在 Swift 3 中,因此您仍然需要将其转换为 Objective-C,但如果您仔细阅读 cmets,这应该不是问题。

解决方案的关键是在键盘动画完成时更新tableView(或scrollview)插图,并且表单在它的新位置上。

在你的 UIViewController 子类中添加:

override func viewDidLoad() 
     super.viewDidLoad()
     NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: .UIKeyboardWillChangeFrame, object: nil)
     NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: .UIKeyboardWillHide, object: nil)

这将添加一个观察者,以防键盘显示/隐藏。我们还需要取消订阅这些通知,否则应用会崩溃:

deinit 
    NotificationCenter.default.removeObserver(self)

最后,最重要的代码:

func getTableViewInsets(keyboardHeight: CGFloat) -> UIEdgeInsets 
    // Calculate the offset of our tableView in the 
    // coordinate space of of our window
    let window = (UIApplication.shared.delegate as! AppDelegate).window!
    let tableViewFrame = tableView.superview!.convert(tableView.frame, to: window)

    // BottomInset = part of keyboard that is covering the tableView
    let bottomInset = keyboardHeight 
        - ( window.frame.height - tableViewFrame.height - tableViewFrame.origin.y )

    // Return the new insets + update this if you have custom insets
    return UIEdgeInsetsMake(
         tableView.contentInset.top, 
         tableView.contentInset.left, 
         bottomInset, 
         tableView.contentInset.right
    )


func keyboardWillChangeFrame(_ notification: Notification)
    guard let info = (notification as NSNotification).userInfo else 
        return
    

    guard let animationDuration = info[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval else 
        return
    

    // Default: keyboard will hide:
    var keyboardHeight: CGFloat = 0

    if notification.name == .UIKeyboardWillChangeFrame 
        // The keyboard will show
        guard let keyboardFrame = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else 
            return
        

        keyboardHeight = keyboardFrame.cgRectValue.height
    

    let contentInsets = getTableViewInsets(keyboardHeight: keyboardHeight)

    UIView.animate(withDuration: animationDuration, animations: 
        self.tableView.contentInset = contentInsets
        self.tableView.scrollIndicatorInsets = contentInsets
    , completion: (completed: Bool) -> Void in
        // Chances are the position of our view has changed, (form sheet)
        // so we need to double check our insets
        let contentInsets = self.getTableViewInsets(keyboardHeight: keyboardHeight)
        self.tableView.contentInset = contentInsets
        self.tableView.scrollIndicatorInsets = contentInsets
    )

【讨论】:

谢谢,我使用了let bottomInset = keyboardHeight - (window.frame.maxY - tableViewFrame.maxY)的bottomInset计算 我做了一些与此非常相似的事情,但更改了包含 tableview 和下方按钮的视图的底部约束。我想动画约束的变化,但为此我需要表单的最终位置。有没有办法得到它? 你为什么用UIKeyboardWillChangeFrame而不是UIKeyboardWillShow【参考方案2】:

首先,感谢 Simon Backx,我在实际项目中使用了他的方法。它有效,但有一些场景没有被充分考虑。我优化了他的回答。我原本想在他的答案下方添加评论,但我们没有足够的声誉来添加 cmets。我只能创建一个新的答案,希望这个答案可以帮助别人。

注册观察者
override func viewDidLoad() 
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)

注销观察者 (如果您的应用面向 ios 9.0 及更高版本或 macOS 10.11 及更高版本,则无需取消注册观察者)
deinit 
    NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)

根据键盘的变化修改tableview的contentInsets
private func calculateTableViewContentInsets(keyboardHeight: CGFloat) -> UIEdgeInsets 
    let window = (UIApplication.shared.delegate as! AppDelegate).window!
    let tableViewFrame = tableView.superview!.convert(tableView.frame, to: window)

    let bottomInset = keyboardHeight
        - (window.frame.height - tableViewFrame.height - tableViewFrame.origin.y)
        - tableView.safeAreaInsets.bottom

    var newInsets = tableView.contentInset
    newInsets.bottom = bottomInset

    return newInsets



@objc private func keyboardWillChangeFrame(_ sender: Notification) 
    guard let userInfo = sender.userInfo else  return 
    guard let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else  return 
    guard let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else  return 
    let keyboardHeight = keyboardFrame.height

    let contentInsets = calculateTableViewContentInsets(keyboardHeight: keyboardHeight)
    UIView.animate(withDuration: duration, animations: 
        self.tableView.contentInset = contentInsets
        self.tableView.scrollIndicatorInsets = contentInsets
    , completion:  _ in
        let contentInsets = self.calculateTableViewContentInsets(keyboardHeight: keyboardHeight)
        self.tableView.contentInset = contentInsets
        self.tableView.scrollIndicatorInsets = contentInsets
    )


@objc private func keyboardWillHide(_ sender: Notification) 
    guard let userInfo = sender.userInfo else  return 
    guard let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else  return 

    UIView.animate(withDuration: duration) 
        self.tableView.contentInset.bottom = 0
        self.tableView.scrollIndicatorInsets.bottom = 0
    

【讨论】:

【参考方案3】:

为什么不调整整个 UITableView 的大小而不是设置 contentInset ?

我会在显示键盘时修改 tableview 的约束。

在您的 .h 中创建一个实际约束的引用,当键盘隐藏或不隐藏时该约束将发生变化。 (例如,tableview 底部和 superview 底部之间的边距。)

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomMargin;

UIKeyboardWillShowNotification添加观察者

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

和实际的方法

-(void)keyboardWillShow:(NSNotification*)notification 
    [self.view layoutIfNeeded]; 

    NSDictionary* userInfo = [notification userInfo]; 
    CGRect keyboardFrameEnd;
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameEnd]; //getting the frame of the keyboard
    //you can get keyboard animation's duration and curve with UIKeyboardAnimationCurveUserInfoKey and UIKeyboardAnimationDurationUserInfoKey if you want to animate it

    //calculate the new values of the constraints of your UITableView

    //for the example, the new value will be the keyboard height
    self.bottomMargin.constant = keyboardFrameEnd.size.height;
    [self.view layoutIfNeeded];

【讨论】:

我认为这不能解决我的问题,即如何计算我应该改变多少 tableview 高度或内容插图。在 iPhone 应用程序中,这很容易,因为键盘与 tableview 一起出现在 viewcontroller 的正上方 - 它们具有相同的底部坐标。但在我的 iPad 应用程序中,tableview 及其视图控制器位于模态表单中,这意味着我还必须考虑从窗口底部到表单底部的像素数。我可以计算出来,我只是想知道最好的方法是什么。【参考方案4】:

我在这个帖子中发布了一个演示如何做到这一点的链接:

iOS 7 iPad Orientation issue, when view moves upside after keyboard appears on UITextField

它还包括一个keyboardWillShow 函数,但它对 iOS 8 友好:

-(void)onKeyboardWillShow:(NSNotification*)notification

    //  The user has turned on the onscreen keyboard.
    //
    NSDictionary *info = [notification userInfo];
    NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
    CGRect keyboardFrame = [kbFrame CGRectValue];
    keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil];

    CGFloat keyboardHeight = keyboardFrame.size.height;

我发现在 iOS 8.x 中,如果我没有使用convertRect 函数,iPad 有时会在横向模式下报告 1024 像素的键盘高度。

【讨论】:

如果表格视图显示在模态表单中,这是否也能按预期工作?

以上是关于显示键盘时在 iPad 上的表单中调整表格视图的大小的主要内容,如果未能解决你的问题,请参考以下文章

iPad 上的 UIModalPresentationFormSheet。键盘出现时如何调整UITextView的高度

使用自动布局进行编辑时在表格视图中调整子视图的大小

iPad 上的表格视图组

显示键盘时推送另一个视图时的tableView调整大小问题

iPad:弹出视图中的表格不滚动以显示选定的行

如何在使用 tableViews 自调整单元格时在运行时更改表格视图单元格高度?