为啥使用委托时我的 collectionView 为零?

Posted

技术标签:

【中文标题】为啥使用委托时我的 collectionView 为零?【英文标题】:Why is my collectionView nil when using a delegate?为什么使用委托时我的 collectionView 为零? 【发布时间】:2021-01-07 15:12:07 【问题描述】:

我有一个包含 2 个 CollectionViews 的 TableView,每个 CollectionViews 在一个 TableViewCells 中。当我在第一个 CollectionView 中选择一个项目时,我想更新第二个 CollectionView。我正在使用委托模式,但它不起作用,因为当我想使用 .reloadData 方法时,我的第二个 CollectionView 似乎为零,但是为什么以及如何在第一个 CollectionView 中选择一个项目时更新第二个 CollectionView?

protocol TableViewCellDelegate 
func update()

class TableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource 
var delegate: TableViewCellDelegate?

//...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        self.delegate = ExistingInterestsCell() as? TableViewCellDelegate
        delegate?.update()


class ExistingInterestsCell: UITableViewCell, TableViewCellDelegate 
func update() 
    collectionView.reloadData()

错误信息是:

致命错误:在隐式展开可选值时意外发现 nil:文件 Suggest_Accounts_and_Docs/ExistingInterestsCell.swift,第 13 行

【问题讨论】:

尝试将代码转换为可读格式,这里很难理解是什么问题。 我很抱歉,但这是我能想到的最好的方法,堆栈溢出似乎看不到我的代码块,所以我必须通过单击代码图标将它们添加 5 次,一旦我使用我返回代码块结束,很奇怪也许我做错了 【参考方案1】:

这是使用协议/委托模式的错误方式。您正在创建彼此过于依赖的类。

更好的方法是让您的第一行告诉控制器它的一个集合视图单元格被选中,然后允许控制器更新用于第二行,然后重新加载该行。

因此,您的“第一行”单元格将包含以下内容:

class FirstRowTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource 
    
    var didSelectClosure: ((Int) -> ())?
    
    var collectionView: UICollectionView!

    // cell setup, collection view setup, etc...
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        // tell the controller that a collection view cell was selected
        didSelectClosure?(indexPath.item)
    


您的“第二行”单元格将包含以下内容:

class SecondRowTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource 
    
    var collectionView: UICollectionView!
    
    var activeData: [String] = []
    
    // cell setup, collection view setup, etc...
    

在您的表格视图控制器中,您将拥有一个 var,例如:

var dataType: Int = 0

你的cellForRowAt 看起来像这样:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    if indexPath.row == 0 
        let c = tableView.dequeueReusableCell(withIdentifier: "firstRowCell", for: indexPath) as! FirstRowTableViewCell

        // set the closure so the first row can tell us one of its
        //  collection view cells was selected
        c.didSelectClosure =  [weak self] i in
            guard let self = self else  return 
            // only reload if different cell was selected
            if i != self.dataType 
                self.dataType = i
                self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
            
        
        return c
    

    let c = tableView.dequeueReusableCell(withIdentifier: "secondRowCell", for: indexPath) as! SecondRowTableViewCell
    switch dataType 
    case 1:
        c.activeData = numberData
    case 2:
        c.activeData = wordData
    default:
        c.activeData = letterData
    
    c.collectionView.reloadData()
    return c

这是一个完整的例子......

第一行表格单元格

class FirstRowTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource 
    
    var didSelectClosure: ((Int) -> ())?
    
    var collectionView: UICollectionView!
    
    let myData: [String] = [
        "Letters", "Numbers", "Words",
    ]
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    
    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    
    func commonInit() -> Void 
        
        let fl = UICollectionViewFlowLayout()
        fl.scrollDirection = .horizontal
        fl.minimumInteritemSpacing = 8
        fl.minimumLineSpacing = 8
        fl.estimatedItemSize = CGSize(width: 80.0, height: 60)
        
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(collectionView)
        
        let g = contentView.layoutMarginsGuide
        
        // to avoid auto-layout warnings
        let hConstraint = collectionView.heightAnchor.constraint(equalToConstant: 60.0)
        hConstraint.priority = .defaultHigh
        
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: g.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            hConstraint,
        ])
        
        collectionView.register(SingleLabelCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        collectionView.delegate = self
        
        collectionView.backgroundColor = .systemBlue
        
    

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return myData.count
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SingleLabelCollectionViewCell
        c.theLabel.text = myData[indexPath.item]
        return c
    
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        // tell the controller that a collection view cell was selected
        didSelectClosure?(indexPath.item)
    


第二行表格单元格

class SecondRowTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource 
    
    var collectionView: UICollectionView!
    
    var activeData: [String] = []
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    
    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    
    func commonInit() -> Void 
        
        let fl = UICollectionViewFlowLayout()
        fl.scrollDirection = .horizontal
        fl.minimumInteritemSpacing = 8
        fl.minimumLineSpacing = 8
        fl.estimatedItemSize = CGSize(width: 80.0, height: 60)
        
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(collectionView)
        
        let g = contentView.layoutMarginsGuide
        
        // to avoid auto-layout warnings
        let hConstraint = collectionView.heightAnchor.constraint(equalToConstant: 60.0)
        hConstraint.priority = .defaultHigh
        
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: g.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            hConstraint,
        ])
        
        collectionView.register(SingleLabelCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        collectionView.delegate = self
        
        collectionView.backgroundColor = .systemRed
        
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return activeData.count
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SingleLabelCollectionViewCell
        c.theLabel.text = activeData[indexPath.item]
        return c
    
    

表格视图控制器

class MyTableViewController: UITableViewController 
    
    let numberData: [String] = [
        "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15",
    ]
    let letterData: [String] = [
        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
    ]
    let wordData: [String] = [
        "First", "Second", "Third", "Fourth", "Fifth", "Sixth",
    ]

    var dataType: Int = 0
    
    override func viewDidLoad() 
        super.viewDidLoad()
        
        tableView.register(FirstRowTableViewCell.self, forCellReuseIdentifier: "firstRowCell")
        tableView.register(SecondRowTableViewCell.self, forCellReuseIdentifier: "secondRowCell")
    
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 2
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        if indexPath.row == 0 
            let c = tableView.dequeueReusableCell(withIdentifier: "firstRowCell", for: indexPath) as! FirstRowTableViewCell

            // set the closure so the first row can tell us one of its
            //  collection view cells was selected
            c.didSelectClosure =  [weak self] i in
                guard let self = self else  return 
                // only reload if different cell was selected
                if i != self.dataType 
                    self.dataType = i
                    self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
                
            
            return c
        

        let c = tableView.dequeueReusableCell(withIdentifier: "secondRowCell", for: indexPath) as! SecondRowTableViewCell
        switch dataType 
        case 1:
            c.activeData = numberData
        case 2:
            c.activeData = wordData
        default:
            c.activeData = letterData
        
        c.collectionView.reloadData()
        return c
    
    

集合视图单元格(由两行使用)

// simple single-label collection view cell
class SingleLabelCollectionViewCell: UICollectionViewCell 
    let theLabel = UILabel()
    
    override init(frame: CGRect) 
        super.init(frame: frame)
        commonInit()
    
    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    
    func commonInit() -> Void 
        
        theLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(theLabel)
        
        let g = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: g.topAnchor),
            theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])
        
        theLabel.backgroundColor = .yellow
        
        contentView.layer.borderColor = UIColor.green.cgColor
        contentView.layer.borderWidth = 1
    
    

结果:

点击“数字”,我们会看到:

点击“单词”,我们会看到:

【讨论】:

【参考方案2】:

这一行

self.delegate = ExistingInterestsCell() as? TableViewCellDelegate

指的是一个带有 nil outlets 的表格单元格,这就是它崩溃的原因,您需要访问一个真实呈现的单元格

编辑:在两个单元格类中添加对 tableView 的引用,并使用 indexPath 和表格获取所需的单元格并更新它的集合

if let cell = table.cellForRow(at:Indexpath(row:0,section:0)) as? ExistingInterestsCell 
  // update model
  cell.collectionView.reloadData()

【讨论】:

我只是没有包含出口,因为我的代码很长,但是 ExistingInterestsCell 类包含出口:@IBOutlet weak var collectionView: UICollectionView!,但是为什么它不起作用,或者怎么做我访问的是真实呈现的? ExistingInterestsCell() 表示ExistingInterestsCell.init() 但这并不意味着您在屏幕上看到的就是那个?问题是哪个 tableViewCell 应该是委托? 如何呈现这两个不同的单元格?按什么顺序? 单元格在屏幕上,我都可以看到。 ExistingInterestsCell 是必须使用“TableViewCell”类的 .didSelectItem 函数更新的委托。 ExistingInterestsCell 在 tableview 的 section 0 row 0 中,TableViewCell 在 tableview 的 section 1 row 0 中。

以上是关于为啥使用委托时我的 collectionView 为零?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 UICollectionView 的委托方法不起作用?

iOS如何知道使用自定义委托按下哪个按钮CollectionView

为啥我的 collectionview 单元格没有动画?

我将啥设置为表中的 collectionView 的数据源和委托

为啥我的渐变层覆盖了我的 collectionView 单元格?

Swift-使用委托将数据附加到 CollectionView 中的 TableView