Swift - 解决表格中TextField,TextView编辑时,输入框被键盘遮挡的问题
通常我们可以通过自定义单元格,并在单元格内部添加 UITextField 或 UITextView 来实现可编辑列表。但有时会发现,当我们编辑 tableView 中的输入框内容时,出现的软键盘会将输入框遮挡住。
比如下面样例:
(1)表格中一共有 20 条记录。自定义单元格中使用的是 UITextField(UITextView 同理)
(2)当我们想要编辑“条目8”这个单元格内容时,点击该单元格内容,出现的虚拟键盘会把输入框给挡住。“条目8”中的输入框并没有自动移动到可视区域。
(3)而且由于虚拟键盘的存在,我们如果将滚动条滚到底会发现底部的单元格也被键盘给挡住了。
要解决输入框被遮挡的问题有很多办法,下面分别介绍。由于有的方法即适用于 UITextField 也适用于 UITextView,而有的方法对于这两种控件稍微有些区别,所以下面我对单元格使用 UITextField 或 UITextView 这两种情况分别进行说明。
一、单元格中使用UITextField
假设我们有如下自定义单元格,其内部使用的是 UITextField。
import UIKit //单元格类 class MyTextFieldCell: UITableViewCell, UITextFieldDelegate { //单元格内部标签(可输入) var label:UITextField! //初始化 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) //初始化文本标签 label = UITextField(frame: CGRect.null) label.textColor = UIColor.black label.font = UIFont.systemFont(ofSize: 16) //设置文本标签代理 label.delegate = self label.contentVerticalAlignment = .center //添加文本标签 addSubview(label) } //布局 override func layoutSubviews() { super.layoutSubviews() label.frame = CGRect(x: 15, y: 0, width: bounds.size.width - 15, height: bounds.size.height) } //键盘回车 func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return false } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
1,视图控制器为UITableViewController情况
如果 tableView 所在的视图控制器是 UITableViewController 的话,那我们就不需要特别处理即可放心使用,系统会自动处理键盘遮挡的问题。
import UIKit class TableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() //创建一个重用的单元格 self.tableView!.register(MyTextFieldCell.self, forCellReuseIdentifier: "tableCell") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //创建一个重用的单元格 let cell = tableView .dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! MyTextFieldCell cell.label.text = "条目\(indexPath.row)" return cell } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
2,视图控制器为UIViewController情况
如果 tableView 所在的视图控制器是 UIViewController 的话,这里有两种办法防止键盘遮挡。
- 方法1:在原来的 UIViewController 内部再添加一层 UITableViewController
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView:UITableView? override func loadView() { super.loadView() } override func viewDidLoad() { super.viewDidLoad() //创建表视图 self.tableView = UITableView(frame: self.view.frame, style:.plain) self.tableView!.delegate = self self.tableView!.dataSource = self //创建一个重用的单元格 self.tableView!.register(MyTextFieldCell.self, forCellReuseIdentifier: "tableCell") self.view.addSubview(self.tableView!) //添加一个UITableViewController let tableVC = UITableViewController.init(style: .plain) tableVC.tableView = self.tableView self.addChildViewController(tableVC) } //返回表格行数(也就是返回控件数) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 } //创建各单元显示内容(创建参数indexPath指定的单元) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //创建一个重用的单元格 let cell = tableView .dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! MyTextFieldCell cell.label.text = "条目\(indexPath.row)" return cell } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView:UITableView? override func loadView() { super.loadView() } override func viewDidLoad() { super.viewDidLoad() //创建表视图 self.tableView = UITableView(frame: self.view.frame, style:.plain) self.tableView!.delegate = self self.tableView!.dataSource = self //创建一个重用的单元格 self.tableView!.register(MyTextFieldCell.self, forCellReuseIdentifier: "tableCell") self.view.addSubview(self.tableView!) //监听键盘弹出通知 NotificationCenter.default .addObserver(self,selector: #selector(keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) //监听键盘隐藏通知 NotificationCenter.default .addObserver(self,selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) } //返回表格行数(也就是返回控件数) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 } //创建各单元显示内容(创建参数indexPath指定的单元) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //创建一个重用的单元格 let cell = tableView .dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! MyTextFieldCell cell.label.text = "条目\(indexPath.row)" return cell } // 键盘显示 func keyboardWillShow(_ notification: Notification) { let userInfo = (notification as NSNotification).userInfo! //键盘尺寸 let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue var contentInsets:UIEdgeInsets //判断是横屏还是竖屏 let statusBarOrientation = UIApplication.shared.statusBarOrientation if UIInterfaceOrientationIsPortrait(statusBarOrientation) { contentInsets = UIEdgeInsetsMake(64.0, 0.0, (keyboardSize.height), 0.0); } else { contentInsets = UIEdgeInsetsMake(64.0, 0.0, (keyboardSize.width), 0.0); } //tableview的contentview的底部大小 self.tableView!.contentInset = contentInsets; self.tableView!.scrollIndicatorInsets = contentInsets; } // 键盘隐藏 func keyboardWillHide(_ notification: Notification) { //还原tableview的contentview大小 let contentInsets:UIEdgeInsets = UIEdgeInsetsMake(64.0, 0.0, 0, 0.0); self.tableView!.contentInset = contentInsets self.tableView!.scrollIndicatorInsets = contentInsets } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
二、单元格中使用UITextView
假设我们有一个自定义单元格 MyTextViewCell,其内部使用的是 UITextView。(同上面的自定义单元格 MyTextFieldCell 相比,就是把里面的 UITextField 替换成 UITextView。这里就不再写详细代码了)
1,视图控制器为UITableViewController情况
如果 tableView 所在的视图控制器是 UITableViewController 的话,系统会自动处理键盘遮挡的问题。这个同上面使用 UITextField 的情况一样,不再说明。
2,视图控制器为UIViewController情况
如果 tableView 所在的视图控制器是 UIViewController 的话,同样有两种办法防止键盘遮挡。
- 方法1:在原来的 UIViewController 内部再添加一层 UITableViewController。这个同上面使用 UITextField 的情况一样,不再说明。
- 方法2:监听键盘通知,除了在键盘出现或消失的时候修改 tableView 的 contentInset 和 scrollIndicatorInsets以外,我们还需要添加一个 textView 编辑响应事件,即 textView 开始编辑时 tableView 要将当前单元格滚动到可视区域。
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView:UITableView? override func loadView() { super.loadView() } override func viewDidLoad() { super.viewDidLoad() //创建表视图 self.tableView = UITableView(frame: self.view.frame, style:.plain) self.tableView!.delegate = self self.tableView!.dataSource = self //创建一个重用的单元格 self.tableView!.register(MyTextViewCell.self, forCellReuseIdentifier: "tableCell") self.view.addSubview(self.tableView!) //添加一个UITableViewController let tableVC = UITableViewController.init(style: .plain) tableVC.tableView = self.tableView self.addChildViewController(tableVC) } //返回表格行数(也就是返回控件数) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 } //创建各单元显示内容(创建参数indexPath指定的单元) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //创建一个重用的单元格 let cell = tableView .dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! MyTextViewCell cell.label.text = "条目\(indexPath.row)" return cell } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } //单元格类 class MyTextViewCell: UITableViewCell, UITextViewDelegate { //单元格内部标签(可输入) var label:UITextView! //初始化 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) //初始化文本标签 label = UITextView(frame: CGRect.null) label.textColor = UIColor.black label.font = UIFont.systemFont(ofSize: 16) //设置文本标签代理 label.delegate = self //添加文本标签 addSubview(label) } //布局 override func layoutSubviews() { super.layoutSubviews() label.frame = CGRect(x: 15, y: 0, width: bounds.size.width - 15, height: bounds.size.height) } //开始编辑 func textViewDidBeginEditing(_ textView: UITextView) { //获取tablveView let tableView = superTableView() //获取当前cell所在的indexPath let indexPath = tableView?.indexPath(for: self) //将当前cell滚动到可视区域 tableView?.scrollToRow(at: indexPath!, at: .none, animated: true) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension UITableViewCell { //返回cell所在的UITableView func superTableView() -> UITableView? { for view in sequence(first: self.superview, next: { $0?.superview }) { if let tableView = view as? UITableView { return tableView } } return nil } }