Swift 跨UITableView自定义单元格处理键盘的“上一步”、“下一步”和“完成”按钮

Swift 跨UITableView自定义单元格处理键盘的“上一步”、“下一步”和“完成”按钮,swift,uitableview,Swift,Uitableview,首先,我是Swift的新手,我一直在寻找一个很好的解决方案来处理UITableView中不同自定义单元格中键盘上的“上一步”、“下一步”和“完成”按钮。我已经看过了关于堆栈溢出的各种解决方案,但没有一个能100%满足我的需要 我的tableview每行有一个字段UITextField、UITextView等,需要一种从一个单元格移动到下一个单元格的通用方法。有些解决方案没有考虑到下一个单元格可能在屏幕外的情况 我已经想出了一个解决办法,我将把它作为答案发布。如果您有任何建议,请随时评论改进方法

首先,我是Swift的新手,我一直在寻找一个很好的解决方案来处理UITableView中不同自定义单元格中键盘上的“上一步”、“下一步”和“完成”按钮。我已经看过了关于堆栈溢出的各种解决方案,但没有一个能100%满足我的需要

我的tableview每行有一个字段UITextField、UITextView等,需要一种从一个单元格移动到下一个单元格的通用方法。有些解决方案没有考虑到下一个单元格可能在屏幕外的情况


我已经想出了一个解决办法,我将把它作为答案发布。如果您有任何建议,请随时评论改进方法

请检查此库。简单有效。您只需要通过cocoa pods和appDelegate中的单行代码进行安装

pod 'IQKeyboardManagerSwift'
应用程序内代理

IQKeyboardManager.sharedManager().enable = true

<>对于我的自定义单元格,我有一个基类作为基础,因为我以编程方式创建所有的东西。看起来是这样的:

class BaseTableViewCell: UITableViewCell {
    weak var delegate: BaseTableViewCellDelegate? = nil
    var indexPath: IndexPath? = nil

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setupViews() {
        fatalError("BaseTableViewCell setupViews not overridden")
    }

    func handleBecomeFirstResponser() {
        // handle in derived class
    }

    func handleResignFirstResponder() {
        // handle in derived class
    }
}
protocol BaseTableViewCellDelegate: class {
    func cellEdited(indexPath: IndexPath)
    func cellPreviousPressed(indexPath: IndexPath)
    func cellNextPressed(indexPath: IndexPath)
    func cellNeedsResize(indexPath: IndexPath)
}

// Using extension to provide default implementation for previous/next actions
//   This makes then optional for a cell that doesn't need them
extension BaseTableViewCellDelegate {
    func cellPreviousPressed(indexPath: IndexPath) {}
    func cellNextPressed(indexPath: IndexPath) {}
    func cellNeedsResize(indexPath: IndexPath) {}
}
另外,我还有一个基类的委托,如下所示:

class BaseTableViewCell: UITableViewCell {
    weak var delegate: BaseTableViewCellDelegate? = nil
    var indexPath: IndexPath? = nil

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setupViews() {
        fatalError("BaseTableViewCell setupViews not overridden")
    }

    func handleBecomeFirstResponser() {
        // handle in derived class
    }

    func handleResignFirstResponder() {
        // handle in derived class
    }
}
protocol BaseTableViewCellDelegate: class {
    func cellEdited(indexPath: IndexPath)
    func cellPreviousPressed(indexPath: IndexPath)
    func cellNextPressed(indexPath: IndexPath)
    func cellNeedsResize(indexPath: IndexPath)
}

// Using extension to provide default implementation for previous/next actions
//   This makes then optional for a cell that doesn't need them
extension BaseTableViewCellDelegate {
    func cellPreviousPressed(indexPath: IndexPath) {}
    func cellNextPressed(indexPath: IndexPath) {}
    func cellNeedsResize(indexPath: IndexPath) {}
}
我正在使用一个纯swift机制的扩展,使上一个和下一个实现成为可选的,以防出现不需要这些按钮的情况

然后,在我的BaseTableViewCell类中,我有一个函数来设置键盘工具栏,如下所示。我还有另外一个支持UITextView的函数,可能有更好的方法;不确定

func setupKeyboardToolbar(targetTextField: UITextField, dismissable: Bool, previousAction: Bool, nextAction: Bool) {
    let toolbar: UIToolbar = UIToolbar()
    toolbar.sizeToFit()

    var items = [UIBarButtonItem]()
        let previousButton = UIBarButtonItem(image: UIImage(imageLiteralResourceName: "previousArrowIcon"), style: .plain, target: nil, action: nil)
        previousButton.width = 30
        if !previousAction {
            previousButton.isEnabled = false
        } else {
            previousButton.target = self
            previousButton.action = #selector(toolbarPreviousPressed)
        }

        let nextButton = UIBarButtonItem(image: UIImage(imageLiteralResourceName: "nextArrowIcon"), style: .plain, target: nil, action: nil)
        nextButton.width = 30
        if !nextAction {
            nextButton.isEnabled = false
        } else {
            nextButton.target = self
            nextButton.action = #selector(toolbarNextPressed)
        }

        items.append(contentsOf: [previousButton, nextButton])

    let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
    items.append(contentsOf: [spacer, doneButton])

    toolbar.setItems(items, animated: false)
    targetTextField.inputAccessoryView = toolbar
}
以下是相关的操作例程:

func toolbarPreviousPressed() {
    if delegate != nil && indexPath != nil {
        delegate?.cellPreviousPressed(indexPath: indexPath!)
    }
}

func toolbarNextPressed() {
    if delegate != nil && indexPath != nil {
        delegate?.cellNextPressed(indexPath: indexPath!)
    }
}
在我拥有tableview的视图控制器中,我的cellForRowAt函数具有以下代码:

    let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell") as! TextFieldCell
    let addressItem = (item as! XXXXXXAddressViewModelItem)
    cell.textField.placeholder = addressItem.placeHolderText
    cell.textField.text = addressItem.getValue(row: 0)
    cell.indexPath = indexPath
    cell.delegate = self
    cell.setupKeyboardToolbar(targetTextField: cell.textField, dismissable: true, previousAction: false, nextAction: true)
    return cell
以下是我如何为按下的上一个和下一个按钮处理委托方法:

func cellPreviousPressed(indexPath: IndexPath) {
    // Resign the old cell
    let oldCell = tableView.cellForRow(at: indexPath) as! BaseTableViewCell
    oldCell.handleResignFirstResponder()

    // Scroll to previous cell
    let tempIndex = IndexPath(row: indexPath.row, section: indexPath.section - 1)
    tableView.scrollToRow(at: tempIndex, at: .middle, animated: true)

    // Become first responder
    let cell = tableView.cellForRow(at: tempIndex) as! BaseTableViewCell
    cell.handleBecomeFirstResponser()
}

func cellNextPressed(indexPath: IndexPath) {
    // Resign the old cell
    let oldCell = tableView.cellForRow(at: indexPath) as! BaseTableViewCell
    oldCell.handleResignFirstResponder()

    // Scroll to next cell
    let tempIndex = IndexPath(row: indexPath.row, section: indexPath.section + 1)
    self.tableView.scrollToRow(at: tempIndex, at: .middle, animated: true)

    // Become first responder for new cell
    let cell = self.tableView.cellForRow(at: tempIndex) as! BaseTableViewCell
    cell.handleBecomeFirstResponser()
}
最后,在从BaseTableViewCell派生的cell类中,我重写了handleBecomeFirstResponder和handleResignFirstResponder,如下所示:

override func handleBecomeFirstResponder() {
    textField.becomeFirstResponder()
}

override func handleResignFirstResponder() {
    textField.resignFirstResponder()
}
另一方面,我通过使用tableview上的插图来处理键盘显示和隐藏通知:

self.tableView.contentInset = UIEdgeInsetsMake(0, 0, (keyboardFrame?.height)!, 0)
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, (keyboardFrame?.height)!, 0)
以及:

这花费了我大量的尝试和错误,使之正确,而不是认真地将视图控制器与应该在cell类中的代码纠缠在一起

我一直在寻找更好的方法。让我知道你的想法