利用 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