一种选择是让您的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 连接即可运行的完整示例。
单元格创建一个标签和一个文本字段,将它们排列在垂直堆栈视图中。
第 0 行将隐藏文本字段,其标签文本将设置为来自 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