8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

使用 UITableViewDataSource 和自定义 UITableViewCell Delegate 的正确方法

LHY 2月前

26 0

我正在努力将 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
        }
帖子版权声明 1、本帖标题:使用 UITableViewDataSource 和自定义 UITableViewCell Delegate 的正确方法
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由LHY在本站《uitableview》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 一种选择是让您 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 的引用并不是什么大问题。

    但是,如果你需要做更多的事情怎么办?例如:

    enter image description here

    您不希望 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

返回
作者最近主题: