textFiled 遮住鍵盤問題解決方案
===
# 問題
`mainView` 是包含一個 `tableView` 與 `textField`
點擊 `textField` 時鍵盤不要擋住,希望能把 mainview 往上移動到鍵盤的上方
# Method 1
```swift
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var textfield: UITextField!
@IBOutlet weak var titleView: UIView!
@IBOutlet weak var mainView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
textfield.delegate = self
// 增加點擊背景收起鍵盤
addHideKeyboardWhenTappedAround()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
override func viewWillDisappear(_ animated: Bool) { {
NotificationCenter.default.removeObserver(self)
}
private func addHideKeyboardWhenTappedAround() {
let tapAround: UITapGestureRecognizer =
UITapGestureRecognizer(target: self,
action: #selector(dismissKeyboard))
// tapAround.cancelsTouchesInView = false
view.addGestureRecognizer(tapAround)
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
@objc func keyboardShown(notification: Notification) {
guard let info = notification.userInfo,
let keyboardFrame = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
// 鍵盤高度
let keyboardHeight = keyboardFrame.height
// textfield 在 self.view 座標系的位置
let editingTextFieldFrame = textfield.convert(textfield.bounds, to: self.view)
// textfield 的底部 Y 座標
let editingTextFieldMaxY = editingTextFieldFrame.maxY
// 畫面高度 - 鍵盤高度 = 鍵盤上緣
let keyboardMinY = self.view.frame.height - keyboardHeight
// textfield 與鍵盤的間距,如果小於 0 代表被擋住
let overlap = editingTextFieldMaxY - keyboardMinY + 20 // +20 buffer
if overlap > 0 {
UIView.animate(withDuration: 0.25) {
self.mainView.transform = CGAffineTransform(translationX: 0, y: -overlap)
}
}
}
@objc func keyboardHidden(notification: Notification) {
UIView.animate(withDuration: 0.25) {
self.mainView.transform = .identity
}
}
}
```
使用 `transform` 和 `CGRect` 在 iOS UI 裡的差異,核心在於影響的層次不同
| 特性 | `CGRect` (`frame`) | `transform` |
| ---------- | ------------------------------ | ------------------------- |
| 作用 | 直接改變 View 的位置與大小 | 在顯示層做仿射變換(Affine Transform),不動原本的 frame 定義 |
| AutoLayout | 容易衝突(因為 frame 被 constraint 控制) | 不影響 constraint,比較安全 |
| 動畫用途 | 一般較少用,需重新 layout | 非常常用(移動 translation、縮放 scale、旋轉 rotation) |
| 適合場景 | 需要永久改變 View 的位置/大小 | 暫時調整或動畫效果 |
```swift=
view.frame = CGRect(x: 0, y: 100, width: 200, height: 50) // 把 view 移到 y = 100,寬高變 200x50
view.transform = CGAffineTransform(translationX: 0, y: -100) // 向上移動 100
view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) // 放大 1.5 倍
view.transform = CGAffineTransform(rotationAngle: .pi/4) // 旋轉 45 度
```
# Method 2
iOS15+ 新推出的 **UIKeyboardLayout**,可以自動解決鍵盤擋住畫面的問題。
需滿足:
- 確認要移動的 `UIView`,這邊假設是上述的 `mainView`
- 要移動的 `UIView` 的底部必須對齊
1. `UIView` 的 **Layout Guides** 啟用 **Keyboard** 選項

2. 在 xib 左側就會出現 **Keyboard Layout guide**,直接將 `mainView` 拖曳到 **Keyboard Layout Guide** 設置對齊它的 top。

注意:若是要被往上移的 UIView 是子 UIView,則每一層的 super view 都要設置底部對齊 **Keyboard Layout Guide**
```swift=
override func viewDidLoad() {
super.viewDidLoad()
// mainView 底部 = 鍵盤上緣 - 0(或留個間距 8)
mainView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: 0).isActive = true
}
```
這樣做效果就是:
- 鍵盤收起時 → keyboardLayoutGuide.topAnchor 會等於 Safe Area Bottom,所以 `mainView` 會貼到底部。
- 鍵盤彈出時 → keyboardLayoutGuide.topAnchor 自動移到鍵盤上緣,`mainView` 會跟著被頂上去。
- 不需要寫 `NotificationCenter` 的監聽,也不用自己算 offset。
###### tags: `textField`