为什么在使用委托时我的 collectionView 为 nil?
Why is my collectionView nil when using a delegate?
我有一个包含 2 个 CollectionView 的 TableView,每个 CollectionView 都在一个 TableViewCell 中。当我 select 第一个 CollectionView 中的一个项目时,我想更新第二个 CollectionView。我正在使用委托模式,但它不起作用,因为当我想使用 .reloadData 方法时我的第二个 CollectionView 似乎为 nil,但是为什么以及如何在 selecting 一个项目时实现更新第二个 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()
}}
错误信息是:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Suggest_Accounts_and_Docs/ExistingInterestsCell.swift, line 13
这一行
self.delegate = ExistingInterestsCell() as? TableViewCellDelegate
指的是一个 table 没有插座的电池,这就是它崩溃的原因,你需要访问一个真实的呈现的
编辑: 在两个单元格 类 中添加对 tableView 的引用,并使用 indexPath 和 table 获取需要单元格并更新它的集合
if let cell = table.cellForRow(at:Indexpath(row:0,section:0)) as? ExistingInterestsCell {
// update model
cell.collectionView.reloadData()
}
这是使用协议/委托模式的错误方式。您正在创建 类 彼此过于依赖。
更好的方法是让您的第一行告诉控制器它的一个集合视图单元格已被选中,然后允许控制器更新用于第二行的数据,然后重新加载该行。
因此,您的“第一行”单元格将包含:
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...
}
在您的 table 视图控制器中,您将有一个变量,例如:
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
}
这是一个完整的例子...
第一行Table单元格
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)
}
}
第二行Table单元格
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
}
}
Table 视图控制器
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 个 CollectionView 的 TableView,每个 CollectionView 都在一个 TableViewCell 中。当我 select 第一个 CollectionView 中的一个项目时,我想更新第二个 CollectionView。我正在使用委托模式,但它不起作用,因为当我想使用 .reloadData 方法时我的第二个 CollectionView 似乎为 nil,但是为什么以及如何在 selecting 一个项目时实现更新第二个 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()
}}
错误信息是:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Suggest_Accounts_and_Docs/ExistingInterestsCell.swift, line 13
这一行
self.delegate = ExistingInterestsCell() as? TableViewCellDelegate
指的是一个 table 没有插座的电池,这就是它崩溃的原因,你需要访问一个真实的呈现的
编辑: 在两个单元格 类 中添加对 tableView 的引用,并使用 indexPath 和 table 获取需要单元格并更新它的集合
if let cell = table.cellForRow(at:Indexpath(row:0,section:0)) as? ExistingInterestsCell {
// update model
cell.collectionView.reloadData()
}
这是使用协议/委托模式的错误方式。您正在创建 类 彼此过于依赖。
更好的方法是让您的第一行告诉控制器它的一个集合视图单元格已被选中,然后允许控制器更新用于第二行的数据,然后重新加载该行。
因此,您的“第一行”单元格将包含:
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...
}
在您的 table 视图控制器中,您将有一个变量,例如:
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
}
这是一个完整的例子...
第一行Table单元格
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)
}
}
第二行Table单元格
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
}
}
Table 视图控制器
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
}
}
结果:
点击“号码”,我们会看到:
点击“字词”,我们会看到: