利用 contentInset 讓鍵盤出現時 scroll view 自動上移

在 iOS App 畫面上我們時常看到利用鍵盤輸入內容的頁面,這樣的畫面有很多做法,可以用 view,scroll view,table view 或 collection view 實現,不過它們都會遇到一個麻煩的問題,鍵盤出現時有可能檔到 text field & text view。

比方以下例子,我們想購買地表最強的電腦 Mac M1,但是要輸入 address 時 text field 卻被鍵盤遮住了。我們真心想買 M1,但是無法輸入地址,M1 沒辦法寄到家裡。

想要鍵盤出現時不要檔到 text field 或 text view,解法是讓畫面在鍵盤出現時往上移動。若是採用 scroll view 製作畫面,我們可參考 Apple 在 Develop in Swift Data Collections lesson 1.4 Scroll Views 的方法,利用 contentInset 讓畫面上移。

接下來就讓我利用 content inset 解決討厭的鍵盤問題吧。

storyboard 的設計如下:

假設我們已經事先拉好 outlet scrollView。

class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!

scroll view 的 content inset

在 scroll view 內容的上下左右可加入稱為 contentInset 的空白區塊。

比方設定 contentInset top 是 300 時,scroll view 的上方將多出一塊高度 300 的空間。

override func viewDidLoad() {
uper.viewDidLoad()

scrollView.contentInset = UIEdgeInsets(top: 300, left: 0, bottom: 0, right: 0)
}

設定 contentInset bottom 是 400 時,scroll view 內容的下方將多出一塊高度 400 的空間。

scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 400, right: 0)

如下圖所示,address textfield 下方將長出高度 400 的空間。

利用 contentInset bottom 讓 scroll view 捲動

鍵盤出現時,scroll view 會判斷鍵盤是否檔到正在輸入的 text field,若有檔到,而且 contentInset bottom 等於鍵盤的高度,scroll view 將聰明地捲到剛好的位置,讓 text field 剛好在鍵盤的上方。

我們希望的效果如下,輸入 name 時不會檔到,所以畫面沒捲動。

輸入 address 時,因為會檔到 address,所以畫面上移,text field 剛好在鍵盤上方。

因此剩下最後一個問題,如何知道鍵盤的高度呢 ? 答案就在 keyborad 的 notification。

利用 keyborad notification 知道鍵盤高度和調整 contentInset

以下範例參考 Apple 在 Develop in Swift Data Collections lesson 1.4 Scroll Views 的寫法,唯一差別在讀取 notification 資料時,讀取的內容是 UIResponder.keyboardFrameEndUserInfoKey。範例的寫法採用 UIResponder.keyboardFrameBeginUserInfoKey,不過測試時發現會有些問題,因此改成 UIResponder.keyboardFrameEndUserInfoKey。

class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!

override func viewDidLoad() {
super.viewDidLoad()
registerForKeyboardNotifications()
}

func registerForKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}

@objc func keyboardWasShown(_ notification: NSNotification) {
guard let info = notification.userInfo,
let keyboardFrameValue = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardFrame = keyboardFrameValue.cgRectValue
let keyboardSize = keyboardFrame.size
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
}

@objc func keyboardWillBeHidden(_ notification: NSNotification) {
let contentInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
}
}

說明:

利用 NotificationCenter 加入鍵盤出現跟消失的通知,在鍵盤出現時讀取鍵盤的高度,將鍵盤高度設為 scroll view 的 contentInset bottom。在鍵盤消失時再將 contentInset 設回 0。

UITextView 必須另外搭配 scrollRectToVisible

設定 scroll view 的 contentInset 後,當我們在 text field 打字時,scroll view 將聰明地捲到剛好的位置,讓 text field 剛好在鍵盤的上方。不過若是在 text view 打字,scroll view 不會聰明地自動捲動,我們必須自己呼叫 scrollRectToVisible,例如以下例子。

  • scroll view 裡有 text filed & text view。
  • 當游標在 text view 時,呼叫 scrollRectToVisible 捲動 scroll view。
@objc func keyboardWasShown(_ notification: NSNotification) {
guard let info = notification.userInfo,
let keyboardFrameValue = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardFrame = keyboardFrameValue.cgRectValue
let keyboardSize = keyboardFrame.size
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
if textView.isFirstResponder {
scrollView.scrollRectToVisible(textView.frame, animated: true)
}
}

範例連結

參考連結

Apple 在 Develop in Swift Data Collections lesson 1.4 Scroll Views。

專案 ScrollingForm

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com