从自定义单元格中正确委派按钮操作以删除 UITableView 中的行

Her*_*eis 2 uitableview ios swift swift-protocols swift3

仍然是一个 Swift 菜鸟,我一直在寻找一种正确的方法/最佳实践来管理我的UITableView(使用自定义UserCells)中的行删除,基于在using 委托中点击 a UIButtonUserCell这似乎是最干净的方法。

我跟着这个例子:UITableViewCell Buttons with action

我拥有的

UserCell 类

protocol UserCellDelegate {

    func didPressButton(_ tag: Int)
}

class UserCell: UITableViewCell {

    var delegate: UserCellDelegate?
    let addButton: UIButton = {

        let button = UIButton(type: .system)

        button.setTitle("Add +", for: .normal)
        button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

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

        addSubview(addButton)
        addButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -6).isActive = true
        addButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        addButton.heightAnchor.constraint(equalToConstant: self.frame.height / 2).isActive = true
        addButton.widthAnchor.constraint(equalToConstant: self.frame.width / 6).isActive = true
    }

    func buttonPressed(_ sender: UIButton) {

        delegate?.didPressButton(sender.tag)
    }
}
Run Code Online (Sandbox Code Playgroud)

TableViewController 类:

class AddFriendsScreenController: UITableViewController, UserCellDelegate {

    let cellId = "cellId"
    var users = [User]()

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell

        cell.delegate = self
        cell.tag = indexPath.row

        return cell
    }

    func didPressButton(_ tag: Int) {

        let indexPath = IndexPath(row: tag, section: 0)

        users.remove(at: tag)
        tableView.deleteRows(at: [indexPath], with: .fade)
    }
}
Run Code Online (Sandbox Code Playgroud)

其中Users 在users视图控制器中附加了对数据库的调用。

我的问题

  • 表格视图每一行的按钮都是可点击的,但不做任何事情
  • 该按钮似乎只有在“长按”时才可点击,即手指停留在它上面约 0.5 秒的时间
  • 这个方法能保证indexPath更新并且不会超出范围吗?即,如果在索引 0 处删除一行,删除索引 0 处的“新”行会正常工作还是会删除索引 1 处的行?

我想要的是

能够单击表格每一行中的按钮,这会将其从表格视图中删除。

我一定是得到了一些相当基本的错误,如果一个 Swift 骑士能启发我,我真的很感激。

提前谢谢了。

Oli*_*ier 5

您的代码中至少有 3 个问题:

  • UserCell你应该调用:
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
Run Code Online (Sandbox Code Playgroud)

一旦你的单元被实例化(比如,从你的实现中init(style:reuseIdentifier:)),那么它self指的是UserCell.

  • AddFriendsScreenController'stableView(_:cellForRowAt:)您正在设置单元格本身的标签 ( cell.tag = indexPath.row) 但在您的UserCell'sbuttonPressed(_:)您正在使用按钮的标签。您应该将该函数修改为:
func buttonPressed(_ sender: UIButton) {

    //delegate?.didPressButton(sender.tag)
    delegate?.didPressButton(self.tag)
}
Run Code Online (Sandbox Code Playgroud)
  • 正如您所猜测的那样,根据Prema Janoti 的回答,您应该在删除一行后重新加载表格视图,因为您的单元格标签将与它们的引用不同步indexPaths。理想情况下,您应该避免依赖索引路径来识别单元格,但这是另一个主题。

编辑:
避免标签与索引路径不同步的一个简单解决方案是将每个单元格与User它们应该表示的对象相关联:

  • 首先user向您的UserCell类添加一个属性:
class UserCell: UITableViewCell {

    var user = User()   // default with a dummy user

    /* (...) */
}
Run Code Online (Sandbox Code Playgroud)
  • User从内部将此属性设置为正确的对象tableView(_:cellForRowAt:)
//cell.tag = indexPath.row
cell.user = self.users[indexPath.row]
Run Code Online (Sandbox Code Playgroud)
  • 修改您的UserCellDelegate协议方法的签名以传递user存储在单元格中的属性而不是它的tag
protocol UserCellDelegate {

    //func didPressButton(_ tag: Int)
    func didPressButtonFor(_ user: User)

}
Run Code Online (Sandbox Code Playgroud)
  • 修改UserCellbuttonPressed(_:)相应动作:
func buttonPressed(_ sender: UIButton) {

    //delegate?.didPressButton(sender.tag)
    //delegate?.didPressButton(self.tag)
    delegate?.didPressButtonFor(self.user)
}
Run Code Online (Sandbox Code Playgroud)
  • 最后,在您的 中AddFriendsScreenController,根据User数据源中的位置确定要删除的正确行:
//func didPressButton(_ tag: Int) { /* (...) */ }   // Scrap this.

func didPressButtonFor(_ user: User) {

    if let index = users.index(where: { $0 === user }) {

        let indexPath = IndexPath(row: index, section: 0)

        users.remove(at: index)
        tableView.deleteRows(at: [indexPath], with: .fade)
    }
}
Run Code Online (Sandbox Code Playgroud)

注意if let index = ...构造(可选绑定)和三元组===身份运算符)。

这种方法的缺点是它会在您的类UserUserCell类之间创建紧密耦合。例如,最佳实践会规定使用更复杂的MVVM 模式,但这确实是另一个主题......