当键盘出现时调整屏幕大小

25

我正在开发一款聊天应用。当键盘出现时,我需要移动一个文本框。我使用以下代码完成此操作:

func keyboardWillShow(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        if let keyboardSize =  (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            kbHeight = keyboardSize.height
            self.animateTextField(true)
        }
    }
}
func keyboardWillHide(notification: NSNotification) {
    self.animateTextField(false)
}

func animateTextField(up: Bool) {
    var movement = (up ? -kbHeight : kbHeight)

    UIView.animateWithDuration(0.3, animations: {
        self.view.frame = CGRectOffset(self.view.frame, 0, movement)
    })
}

然而,当我使用这段代码时,第一条消息没有显示出来。我猜测我需要调整tableview的大小。

这里是键盘出现之前之后的屏幕截图:

我正在使用自动布局。

我该如何解决这个问题?


你在故事板中使用自动布局了吗? - Eendje
是的,我正在使用自动布局。 - Okan
哦!我忘了那个……抱歉。 - nacho4d
不要更新“frame”,请更新“contentInset”。 - holex
https://stackoverflow.com/questions/48922266/uitableview-custom-cell-auto-scroll-when-text-field-is-tapped-swift-3/48923632#48923632 - Venkatesh G
2019年,这个问题终于被KUIViewController解决了。 - Fattie
5个回答

18

2023 更新

在 iOS 中,正确使用约束是处理这个混乱的唯一方法。

  1. KUIViewController 粘贴到您的项目中,

  2. 创建一个非常简单的约束,将其与您的内容的底部对齐。

  3. 将该约束拖动到 bottomConstraintForKeyboard

KUIViewController 将始终自动且正确地调整您的内容视图的大小。

一切都是完全自动的。

所有 Apple 的行为都以标准的方式正确处理,例如通过点击来解除等等。

您已经完成了 100%。

那么“您应该调整哪个视图的大小?”

不能使用 .view ...

因为...您不能在 iOS 中调整 .view 的大小!!!!噢!

只需创建一个名为“holder”的 UIView。它位于 .view 内部。

把你的所有东西放进“holder”里。
当然,“holder”会有四个简单的约束条件:上/下/左/右到.view。
那个底部约束到“holder”的确是bottomConstraintForKeyboard。
你完成了。
给客户发一张账单,然后去喝酒。
没有其他事情要做了。
class KUIViewController: UIViewController {

    // KBaseVC is the KEYBOARD variant BaseVC. more on this later

    @IBOutlet var bottomConstraintForKeyboard: NSLayoutConstraint!

    @objc func keyboardWillShow(sender: NSNotification) {
        let i = sender.userInfo!
        let s: TimeInterval = (i[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let k = (i[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
        bottomConstraintForKeyboard.constant = k
        // Note. that is the correct, actual value. Some prefer to use:
        // bottomConstraintForKeyboard.constant = k - bottomLayoutGuide.length
        UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
    }

    @objc func keyboardWillHide(sender: NSNotification) {
        let info = sender.userInfo!
        let s: TimeInterval = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        bottomConstraintForKeyboard.constant = 0
        UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
    }

    @objc func clearKeyboard() {
        view.endEditing(true)
        // (subtle iOS bug/problem in obscure cases: see note below
        // you may prefer to add a short delay here)
    }

    func keyboardNotifications() {
        NotificationCenter.default.addObserver(self,
            selector: #selector(keyboardWillShow),
            name: UIResponder.keyboardWillShowNotification,
            object: nil)
        NotificationCenter.default.addObserver(self,
            selector: #selector(keyboardWillHide),
            name: UIResponder.keyboardWillHideNotification,
            object: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        keyboardNotifications()
        let t = UITapGestureRecognizer(target: self, action: #selector(clearKeyboard))
        view.addGestureRecognizer(t)
        t.cancelsTouchesInView = false
    }
}

只需在可能出现键盘的任何地方使用KUIViewController。
class AddCustomer: KUIViewController, SomeProtocol {

class EnterPost: KUIViewController {

class EditPurchase: KUIViewController {

在那些屏幕上,现在关于键盘的一切都是完全自动化的。
你完成了。
呼。
*小注脚 - 背景点击可以正确地关闭键盘。这包括点击落在您的内容上的点击。这是正确的苹果行为。任何与此有很大不同的异常变化都需要大量的反苹果定制编程。
*非常小的注脚 - 因此,屏幕上的所有按钮每次都会100%正确地工作。然而,在极其罕见的情况下,嵌套的(!)容器视图内嵌套的(!)滚动视图内嵌套的(!)页面视图容器(!!!!),您可能会发现一个按钮似乎无法工作。这似乎基本上是当前iOS中的一个(隐晦的!)问题。如果您遇到这个极其隐晦的问题,幸运的是解决方案很简单。只需在函数clearKeyboard()中添加一个短延迟,问题就解决了。
@objc func clearKeyboard() {
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
        self.view.endEditing(true)
    }
}

(来自用户@wildcat12的一个很棒的提示https://dev59.com/i6jja4cB1Zd3GeqP6SWw#57698468

4
你知道,这本不应该那么难! - ICL1901
1
这是我找到的最佳答案,非常好。虽然对我来说似乎向上移动了太多,看起来与选项卡栏的大小相同。 - nicwhitts
我不确定问题的根本原因,但是内容“向上移动”太多了(似乎多了49个点)。但是当键盘隐藏时,它会返回到正确的位置。我猜测这与选项卡栏有关(它有49个点),我已经考虑到了这一点,解决了这个问题。很可能是我忽略了某些问题,但这个hack对于这个项目有效。如果问题仍然存在,我会发布一个问题,谢谢。 - nicwhitts
@Fattie 非常感谢您!!只是出于好奇,我之前使用的是:“self.view.frame.origin.y -= getKeyBoardHeight(notification)”。它一直运行良好,直到突然间不再调整视图大小,而是将整个视图向上移动。现在我知道“视图”无法调整大小,这似乎是合理的。仍然不知道为什么之前可以正常工作。这已经发生了两次,这次是在从XCode 7升级到8后,上一次是在向程序添加更多视图控制器后。也许我在Storyboard中改变了什么?谢谢。 - Irene
我不确定关于“标准方式,例如通过点击来解除”的说法是从哪里来的...UIKit提供的唯一键盘解除操作是在UIScrollView拖动时,即使这样,默认情况下也会关闭。无论如何,似乎我可以注释掉触摸解除逻辑。 - androidguy

17
您可以创建一个表视图底部自动布局约束的出口。 然后只需使用此代码:
func keyboardWillShow(sender: NSNotification) {
    let info = sender.userInfo!
    var keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
    bottomConstraint.constant = keyboardSize - bottomLayoutGuide.length

    let duration: TimeInterval = (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue

    UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() }
}

func keyboardWillHide(sender: NSNotification) {
    let info = sender.userInfo!
    let duration: TimeInterval = (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    bottomConstraint.constant = 0

    UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() }
}

如果你在创建底部约束时遇到问题:

在故事板中:

  • 选择你的搜索栏。
  • 在右下角,你会看到三个图标。点击中间的那个看起来像 |-[]-| 的图标。
  • 在弹出窗口的顶部,有4个框。在底部的那个框中输入0。
  • 约束已创建!

现在你可以将其拖动到你的视图控制器中,并将其添加为出口。

另一种解决方案是设置tableView.contentInset.bottom。但我以前没有这样做过。如果你更喜欢这种方式,我可以尝试解释一下。

使用inset:

func keyboardWillShow(sender: NSNotification) {
    let info = sender.userInfo!
    let keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height

    tableView.contentInset.bottom = keyboardSize
}

func keyboardWillHide(sender: NSNotification) {
    tableView.contentInset.bottom = 0
}

您可以尝试使用此代码来设置插入。我自己还没有尝试过,但它应该是类似这样的东西。

编辑:根据nacho4d的建议更改了持续时间


第二个解决方案不起作用。它什么也没做。我认为我必须使用第一个解决方案,但我需要更多有关创建底部约束的信息。 - Okan
文本框出现在键盘下面,你知道怎么移动它吗? - Okan
看起来...是时候使用约束解决方案了 ;) - Eendje
1
不要硬编码动画持续时间0.5,而应该使用提供的长度info[UIKeyboardAnimationDurationUserInfoKey]。有时候更改会在没有动画的情况下发生。 - nacho4d
1
更新了Swift 3的代码。 @JoeBlow:我认为一个(旧)答案不应该因为它没有被更新到最新版本的语言而被投票否决。您可以留下评论或编辑答案,纠正轻微的更改,然后就完成了(而不是替换整个帖子)。我认为,如果因为答案没有更新到最新版本而将其投票否决,这是对那些通过帮助他人和社区来付出大量努力和时间的人的侮辱,并且这个答案本身仍然是有效的。 - Eendje
显示剩余6条评论

2

来自 @Fattie 的消息:

一个细节 - (不幸的是)点击您的内容也会关闭键盘。 (它们都获得事件。)但是,这几乎总是正确的行为;试试看。没有合理的方法可以避免这种情况,所以忘记它,按照苹果的方式去做。

可以通过实现以下 UIGestureRecognizerDelegate 的方法来解决此问题:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        return !(touch.view?.isKind(of: UIControl.self) ?? true)
    }

这样,如果用户触摸到任何一个UIControl(如UIButton,UITextField等),手势识别器就不会调用clearKeyboard()方法。
为了使它正常工作,请记得在类定义或扩展中子类化UIGestureRecognizerDelegate。然后,在viewDidLoad()中,您应将手势识别器委托分配为self。
准备好复制和粘贴代码:
// 1. Subclass UIGestureRecognizerDelegate
class KUIViewController: UIViewController, UIGestureRecognizerDelegate {

@IBOutlet var bottomConstraintForKeyboard: NSLayoutConstraint!

func keyboardWillShow(sender: NSNotification) {
    let i = sender.userInfo!
    let k = (i[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
    bottomConstraintForKeyboard.constant = k - bottomLayoutGuide.length
    let s: TimeInterval = (i[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}

func keyboardWillHide(sender: NSNotification) {
    let info = sender.userInfo!
    let s: TimeInterval = (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    bottomConstraintForKeyboard.constant = 0
    UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}

func keyboardNotifications() {
    NotificationCenter.default.addObserver(self,
        selector: #selector(keyboardWillShow),
        name: Notification.Name.UIKeyboardWillShow,
        object: nil)
    NotificationCenter.default.addObserver(self,
        selector: #selector(keyboardWillHide),
        name: Notification.Name.UIKeyboardWillHide,
        object: nil)
}

func clearKeyboard() {
    view.endEditing(true)
}

override func viewDidLoad() {
    super.viewDidLoad()
    keyboardNotifications()
    let t = UITapGestureRecognizer(target: self, action: #selector(clearKeyboard))
    view.addGestureRecognizer(t)
    t.cancelsTouchesInView = false

    // 2. Set the gesture recognizer's delegate as self
    t.delegate = self
}

// 3. Implement this method from UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return !(touch.view?.isKind(of: UIControl.self) ?? true)
}
}

2
也许这会对某些人有所帮助。您可以完全不使用界面构建器来实现所需的行为。
首先,您需要创建一个约束并计算安全区域插图,以便正确支持无按钮设备。
var container: UIView!
var bottomConstraint: NSLayoutConstraint!
let safeInsets = UIApplication.shared.windows[0].safeAreaInsets

然后在您的代码中的某个地方进行初始化

container = UIView()
bottomConstraint = container.bottomAnchor.constraint(equalTo: view.bottomAnchor)

将其附加到视图并激活

view.addSubview(container)

NSLayoutConstraint.activate([
       ...

       container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
       container.trailingAnchor.constraint(equalTo: view.trailingAnchor),
       container.topAnchor.constraint(equalTo: view.topAnchor),
       bottomConstraint,

       ...
 ])

最后。
@objc func keyboardWillShow(notification: NSNotification) {
       if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {

       if bottomConstraint.constant == 0 {
          bottomConstraint.constant = -keyboardSize.height + safeInsets.bottom     
          view.layoutIfNeeded()
       }
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
       bottomConstraint.constant = 0
       view.layoutIfNeeded()
}

如果你的视图可以滚动,并且你想用键盘将其向上移动,并在键盘隐藏时返回初始位置,则可以更改视图的contentOffset。

view.contentOffset = CGPoint(x: view.contentOffset.x, y: view.contentOffset.y + keyboardSize.height - safeInsets.bottom)

向上滚动,请使用以下方法:
view.contentOffset = CGPoint(x: view.contentOffset.x, y: view.contentOffset.y - keyboardSize.height + safeInsets.bottom)

将其向下移动


1
如果您不想自己解决这个问题,您可能会发现TPKeyboardAvoiding框架很有用。
只需按照“安装说明”操作,即将适当的.h/.m文件拖放到您的项目中,然后使您的ScrollView / TableView成为以下子类:

Custom Class


但这是针对Objective-C的。 - Okan
我已经在Swift应用程序中使用它了... ObjC与Swift的桥接非常好。 - Chris
如果您阅读页面上的文档,它将指导您如何设置它。但基本上,您需要将您的TableView设置为TPKeyboardAvoidingTableView的子类。(我的答案已更新并附有图片。) - Chris
那么文本框、发送按钮和眼睛图标会发生什么? - Okan
一切都应该按照相应的方式进行。试一试,看看会发生什么。 - Chris
TPKeyboardAvoiding框架曾经非常棒,但现在唯一的选择就是使用约束,如上所述。再也没有比这更简单的了。 - Fattie

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