使用 UITableViewDataSource 和自定义 UITableViewCell Delegate 的正确方法
我正在努力将 UITableViewDataSource 移出 UITableViewController。但是我有一些自定义单元格有自己的委托,然后它们会调用 tableView 进行重新加载。我...
我正在努力移动 UITableViewDataSource
的外部 UITableViewController
。但是我有一些自定义单元格,它们有自己的委托,然后调用 来 tableView
重新加载。
我不确定正确的处理方法是什么。以下是我所拥有的:
final class MyTableViewController: UITableViewController {
lazy var myTableViewDataSource: MyTableViewDataSource = { MyTableViewDataSource(myProperty: MyProperty) }()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = myTableViewDataSource
}
}
细胞
protocol MyTableViewCellDelegate: AnyObject {
func doSomething(_ cell: MyTableViewCellDelegate, indexPath: IndexPath, text: String)
}
final class MyTableViewCell: UITableViewCell, UITextFieldDelegate {
@IBOutlet weak var packageSizeTextField: UITextField!
weak var delegate: MyTableViewCellDelegate?
var indexPath = IndexPath()
override func awakeFromNib() {
super.awakeFromNib()
}
func configureCell() {
// configureCell...
}
func textFieldDidChangeSelection(_ textField: UITextField) {
print(#function)
delegate?.doSomething(self, indexPath: indexPath, text: textField.text ?? "")
}
}
数据源
final class MyTableViewDataSource: NSObject, UITableViewDataSource {
var myProperty: MyProperty!
init(myProperty: MyProperty) {
self.myProperty = myProperty
}
// ...
func doSomething(_ cell: MyTableViewCell, indexPath: IndexPath, text: String) {
// ...
tableView.performBatchUpdates({
tableView.reloadRows(at: [indexPath], with: .automatic)
})
// ERROR - tableView doesn't exist
}
}
我的问题是,我如何访问 tableView
此类提供源代码的?是否像添加对 tableView 的引用一样简单?
var tableView: UITableView
var myProperty: MyProperty!
init(myProperty: MyProperty, tableView: UITableView) {
self.myProperty = myProperty
self.tableView = tableView
}
下载声明: 本站所有软件和资料均为软件作者提供或网友推荐发布而来,仅供学习和研究使用,不得用于任何商业用途。如本站不慎侵犯你的版权请联系我,我将及时处理,并撤下相关内容!
-
引用 2楼
一种选择是让您
MyTableViewController
符合您的要求MyTableViewCellDelegate
在您的 dataSource 类中将cellForRowAt
控制器设置为委托然而,使用闭包可能会更好。
删除
delegate
和indexPath
属性,并添加闭包属性:final class MyTableViewCell: UITableViewCell, UITextFieldDelegate { @IBOutlet weak var packageSizeTextField: UITextField! override func awakeFromNib() { super.awakeFromNib() configureCell() } func configureCell() { // configureCell... packageSizeTextField.delegate = self } var changeClosure: ((String, UITableViewCell)->())? func textFieldDidChangeSelection(_ textField: UITextField) { print(#function) changeClosure?(textField.text ?? "", self) // delegate?.doSomething(self, indexPath: indexPath, text: textField.text ?? "") } }
现在,在您的 dataSource 类中,设置闭包:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let c = tableView.dequeueReusableCell(withIdentifier: "mtvc", for: indexPath) as! MyTableViewCell c.packageSizeTextField.text = myData[indexPath.row] c.changeClosure = { [weak self, weak tableView] str, c in guard let self = self, let tableView = tableView, let pth = tableView.indexPath(for: c) else { return } // update our data self.myData[pth.row] = str // do something with the tableView //tableView.reloadData() } return c }
请注意,当您编写代码时,textField 中的第一次点击似乎不会执行任何操作,因为
textFieldDidChangeSelection
会立即被调用。
编辑
这是一个无需任何 Storyboard 连接即可运行的完整示例。
该单元格创建一个标签和一个文本字段,并将它们排列在垂直堆栈视图中。
零行将隐藏文本字段,并将其标签文本设置为来自的连接字符串
myData
.其余行的标签将被隐藏。
闭包将在 上被调用
.editingChanged
(而不是textFieldDidChangeSelection
),因此在编辑开始时它不会被调用。还为了演示目的实现了行删除。
当任意行的文本字段中的文本发生改变或删除某一行时,将重新加载第一行。
单元格类别
final class MyTableViewCell: UITableViewCell, UITextFieldDelegate { var testLabel = UILabel() var packageSizeTextField = UITextField() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) configureCell() } required init?(coder: NSCoder) { super.init(coder: coder) configureCell() } func configureCell() { // configureCell... let stack = UIStackView() stack.axis = .vertical stack.translatesAutoresizingMaskIntoConstraints = false testLabel.numberOfLines = 0 testLabel.backgroundColor = .yellow packageSizeTextField.borderStyle = .roundedRect stack.addArrangedSubview(testLabel) stack.addArrangedSubview(packageSizeTextField) contentView.addSubview(stack) let g = contentView.layoutMarginsGuide NSLayoutConstraint.activate([ stack.topAnchor.constraint(equalTo: g.topAnchor), stack.leadingAnchor.constraint(equalTo: g.leadingAnchor), stack.trailingAnchor.constraint(equalTo: g.trailingAnchor), stack.bottomAnchor.constraint(equalTo: g.bottomAnchor), ]) packageSizeTextField.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged) } var changeClosure: ((String, UITableViewCell)->())? @objc func textChanged(_ v: UITextField) -> Void { print(#function) changeClosure?(v.text ?? "", self) } }
TableView 控制器类
class MyTableViewController: UITableViewController { lazy var myTableViewDataSource: MyTableViewDataSource = { MyTableViewDataSource() }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "mtvc") tableView.dataSource = myTableViewDataSource } }
TableView 数据源类
final class MyTableViewDataSource: NSObject, UITableViewDataSource { var myData: [String] = [ " ", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", ] func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { myData.remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .fade) tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) } else if editingStyle == .insert { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. } } func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return indexPath.row != 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return myData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let c = tableView.dequeueReusableCell(withIdentifier: "mtvc", for: indexPath) as! MyTableViewCell c.testLabel.isHidden = indexPath.row != 0 c.packageSizeTextField.isHidden = indexPath.row == 0 if indexPath.row == 0 { myData[0] = myData.dropFirst().joined(separator: " : ") c.testLabel.text = myData[indexPath.row] } else { c.packageSizeTextField.text = myData[indexPath.row] } c.changeClosure = { [weak self, weak tableView] str, c in guard let self = self, let tableView = tableView, let pth = tableView.indexPath(for: c) else { return } // update our data self.myData[pth.row] = str // do something with the tableView // such as reload the first row (row Zero) tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) } return c } }
编辑2
有很多内容需要讨论,超出了你的问题的范围,但简而言之......
首先, 一般来说, 类别应该尽可能独立。
- 你的 Cell 应该只处理它的元素
- 您的数据源应该只管理数据(当然,还有必要的资金,如返回单元格、处理编辑提交等)
- 正如预期的那样,你的 TableViewController 应该 控制 tableView
如果您只是操作数据并希望重新加载特定的行,那么您的 DataSource 类获取对 tableView 的引用并不是什么大问题。
但是,如果你需要做更多的事情怎么办?例如:
您不希望 Cell 或 DataSource 类对按钮点击做出反应并执行诸如将新控制器推送到导航堆栈上之类的操作。
要使用协议/委托模式,您可以通过类“传递委托引用”。
这是一个例子(仅包含最少的代码)...
两个协议 - 一个用于文本更改,一个用于按钮点击:
protocol MyTextChangeDelegate: AnyObject { func cellTextChanged(_ cell: UITableViewCell) } protocol MyButtonTapDelegate: AnyObject { func cellButtonTapped(_ cell: UITableViewCell) }
控制器类,符合
MyButtonTapDelegate
:class TheTableViewController: UITableViewController, MyButtonTapDelegate { lazy var myTableViewDataSource: TheTableViewDataSource = { TheTableViewDataSource() }() override func viewDidLoad() { super.viewDidLoad() // assign custom delegate to dataSource instance myTableViewDataSource.theButtonTapDelegate = self tableView.dataSource = myTableViewDataSource } // delegate func func cellButtonTapped(_ cell: UITableViewCell) { // do something } }
符合
MyTextChangeDelegate
并引用MyButtonTapDelegate
“传递给单元格”的数据源类:final class TheTableViewDataSource: NSObject, UITableViewDataSource, MyTextChangeDelegate { weak var theButtonTapDelegate: MyButtonTapDelegate? func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! theCell // assign custom delegate to cell instance c.theTextChangeDelegate = self c.theButtonTapDelegate = self.theButtonTapDelegate return c } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 } func cellTextChanged(_ cell: UITableViewCell) { // update the data } }
以及 Cell 类,它将
MyTextChangeDelegate
在文本改变时调用(数据源类),并MyButtonTapDelegate
在点击按钮时调用(控制器类):final class theCell: UITableViewCell, UITextFieldDelegate { weak var theTextChangeDelegate: MyTextChangeDelegate? weak var theButtonTapDelegate: MyButtonTapDelegate? func textFieldDidChangeSelection(_ textField: UITextField) { theTextChangeDelegate?.cellTextChanged(self) } func buttonTapped() { theButtonTapDelegate?.cellButtonTapped(self) } }
那么,说了这么多...
抽象地讲可能很难。对于你的 具体 实现,你可能会陷入困境。
您提到 “如何使用 containerView / 分段控件在控制器之间切换” ... 创建“数据管理器”类而不是“数据源”类 可能
此外,只要稍微搜索一下,
Swift Closure vs Delegate
你就会发现很多讨论表明闭包是当今首选的方法。我在 GitHub 上发布了一个项目,展示了这两种方法。功能相同 --- 一种方法使用闭包,另一种方法使用协议/委托模式。您可以查看并深入研究代码(尽量保持简单明了),看看哪种方法更适合您。
https://github.com/DonMag/DelegatesAndClosures