当前位置: > > > Swift - 解决表格中TextField,TextView编辑时,输入框被键盘遮挡的问题

Swift - 解决表格中TextField,TextView编辑时,输入框被键盘遮挡的问题

通常我们可以通过自定义单元格,并在单元格内部添加 UITextField UITextView 来实现可编辑列表。但有时会发现,当我们编辑 tableView 中的输入框内容时,出现的软键盘会将输入框遮挡住。
比如下面样例:
(1)表格中一共有 20 条记录。自定义单元格中使用的是 UITextFieldUITextView 同理)
(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()
    }
}

  • 方法2:监听键盘通知,在键盘出现或消失的时候修改tableView contentInset scrollIndicatorInsets
  • 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
        }
    }
    
    评论0