前言

iOS開發會遇到的問題中,鍵盤擋到輸入框這件事一定包括在內

網路上也提供了很多解決的方案

我自己評估了方便性、實作難易度、呈現效果後

提供給大家一個我自己比較喜歡的一種方式

範例(in Swift 5)

需要將想監聽的輸入框元件的 delegate 設定到本 ViewController


import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
    
    /* 暫存輸入框元件 */
    var currentTextField: UITextField?
    /* 暫存 View 的範圍 */
    var rect: CGRect?
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        /* 開始輸入時,將輸入框實體儲存 */
        currentTextField = textField
    }
     
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /* 監聽 鍵盤顯示/隱藏 事件 */
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow),
            name: UIResponder.keyboardWillShowNotification,
            object: nil)
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillHide),
            name: UIResponder.keyboardWillHideNotification,
            object: nil)
        
        /* 將 View 原始範圍儲存 */
        rect = view.bounds
    }
    
    /* 這個地方寫法有問題,請看文章下方補充
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        /* 移除監聽 */
        NotificationCenter.default.removeObserver(
            self,
            name: UIResponder.keyboardWillShowNotification,
            object: nil
        )
        
        NotificationCenter.default.removeObserver(
            self,
            name: UIResponder.keyboardWillHideNotification,
            object: nil
        )
    }
    */
     
    @objc func keyboardWillShow(note: NSNotification) {
        if currentTextField == nil {
            return
        }
        
        let userInfo = note.userInfo!
        /* 取得鍵盤尺寸 */
        let keyboard = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.size
        let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
        /* 取得焦點輸入框的位置 */
        let origin = (currentTextField?.frame.origin)!
        /* 取得焦點輸入框的高度 */
        let height = (currentTextField?.frame.size.height)!
        /* 計算輸入框最底部Y座標,原Y座標為上方位置,需要加上高度 */
        let targetY = origin.y + height
        /* 計算扣除鍵盤高度後的可視高度 */
        let visibleRectWithoutKeyboard = self.view.bounds.size.height - keyboard.height
        
        /* 如果輸入框Y座標在可視高度外,表示鍵盤已擋住輸入框 */
        if targetY >= visibleRectWithoutKeyboard {
            var rect = self.rect!
            /* 計算上移距離,若想要鍵盤貼齊輸入框底部,可將 + 5 部分移除 */
            rect.origin.y -= (targetY - visibleRectWithoutKeyboard) + 5
            
            UIView.animate(
                withDuration: duration,
                animations: { () -> Void in
                    self.view.frame = rect
                }
            )
        }
    }
     
    @objc func keyboardWillHide(note: NSNotification) {
        /* 鍵盤隱藏時將畫面下移回原樣 */
        let keyboardAnimationDetail = note.userInfo as! [String: AnyObject]
        let duration = TimeInterval(truncating: keyboardAnimationDetail[UIResponder.keyboardAnimationDurationUserInfoKey]! as! NSNumber)
        
        UIView.animate(
            withDuration: duration,
            animations: { () -> Void in
                self.view.frame = self.view.frame.offsetBy(dx: 0, dy: -self.view.frame.origin.y)
            }
        )
    }
}

補充

社團 iOS @ Taipei 內的開發者們提供一些建議與資訊

建議1

在 iOS 9 之後可以選擇不手動移除觀察者,原因在於會對觀察者採用 weak reference

與 unsafe unretained 的差異為若對象消滅了,unsafe unretained 並不會將內容改為nil

故有機會造成系統崩潰,詳細內容請參考官方文件

如此一來上面範例中的 viewDidDisappear 函式將可移除不使用

建議2

如需要手動移除,建議將註冊觀察者放至於 viewWillAppear 函式

移除則放至於 viewWillDisappear 函式內

原因在於 viewDidLoad 只在第一次加載 View 後調用

而 viewDidDisappear 則是 View 發生覆蓋、隱藏、消滅時調用,包括跳轉到其他頁面

這樣會造成跳轉時發生移除觀察者的行為,返回原畫面並不會重新註冊觀察者(不會再次執行 viewDidLoad)

效果

切換輸入框會重新計算一次高度

若這邊教學有幫助到你的話~請多多分享轉發出去給更多的人知道

謝謝大家的觀看