Почему мой CollectionView равен нулю при использовании делегата?

#ios #swift #uicollectionview

#iOS #swift #uicollectionview

Вопрос:

У меня есть TableView, содержащий 2 CollectionViews, каждый из которых находится в одной из ячеек TableViewCells. Когда я выбираю элемент в первом CollectionView, я хочу обновить второй CollectionView. Я использую шаблон делегирования, но он не работает, потому что мой второй CollectionView кажется нулевым, когда я хочу использовать метод .reloadData, но почему и как я могу добиться обновления второго 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 при неявном развертывании необязательного значения: file Suggest_Accounts_and_Docs/ExistingInterestsCell.swift, строка 13

Комментарии:

1. Попробуйте преобразовать код в читаемый формат, трудно понять, в чем здесь проблема.

2. Извините, но это лучшее, что я мог придумать, stack overflow, похоже, не видит мои блоки кода, поэтому мне приходится добавлять их 5 раз, нажимая значок кода, как только я использую свой return, блок кода заканчивается, очень странно, может быть, я делаю это неправильно

Ответ №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 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
}
 

Here is a complete example…

First Row Table Cell

 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
 

ссылается на ячейку таблицы с нулевыми выходами, поэтому она вылетает, вам нужно получить доступ к реальному представленному

Редактировать: добавьте ссылку на ваш TableView внутри обоих классов ячеек и, используя indexPath и таблицу, получите нужную ячейку и обновите ее коллекцию

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

Комментарии:

1. Я просто не включил выход, так как мой код очень длинный, но класс ExistingInterestsCell содержит выход: @IBOutlet слабая переменная CollectionView: UICollectionView!, но почему это не работает, или как мне получить доступ к реальному представленному?

2. ExistingInterestsCell() означает ExistingInterestsCell.init() , но это не значит, что это тот, который вы видите на экране? Вопрос в том, какая tableViewCell должна быть делегатом?

3. Как вы представляете 2 разные ячейки? в каком порядке?

4. Ячейка находится на экране, я вижу оба. ExistingInterestsCell — это делегат, который необходимо обновить с помощью функции .didSelectItem класса «tableViewCell».

5. Существующая ячейка interestscell находится в разделе 0 строка 0 табличного представления, ячейка tableViewCell находится в разделе 1 строка 0 табличного представления.