嵌套的 CollectionView 没有正确设置数据源或委托?
Nested CollectionView not setting Data Source or Delegate properly?
我有一个像这样的 collectionView 设置:
class TagViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, SNPostViewCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
timeline.delegate = self
UserService.posts(for: User.current,tagString: self.tagTitle!) { (postStuff) in
let posts = postStuff.0
self.postIds = postStuff.1
self.posts = posts.sorted(by: {
[=10=].timeStamp.compare(.timeStamp) == .orderedAscending
})
self.timeline.dataSource = self
self.collectionView.dataSource = self
//self.timeline.collectionViewLayout.invalidateLayout()
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionView {
return posts.count
} else if collectionView == self.timeline {
let first = posts.first?.timeStamp
let last = posts.last?.timeStamp
let months = last?.months(from: first!) ?? 0
print("no of months",months)
if let diff = last?.months(from: first!), diff <= 5 {
return months + 5-diff
} else {
return months + 1
}
} else {
preconditionFailure("Unknown collection view!")
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionView {
let post = posts[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SNPostViewCell
cell.isVideo = post.isVideo
cell.delegate = self
cell.notes.text = post.notes
cell.thumbnailURL = URL(string: post.thumbnailURL)
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
cell.timeStampLabel.text = formatter.string(from: post.timeStamp)
cell.mediaURL = URL(string: post.mediaURL)
cell.notes.topAnchor.constraint(equalTo: cell.thumbnail.bottomAnchor,constant: 0.0).isActive = true
return cell
} else if collectionView == self.timeline {
let index = indexPath.row
print(index,"index")
let calendar = Calendar.current
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let firstPost = posts.first?.timeStamp
let month = calendar.date(byAdding: .month, value: index, to: firstPost!)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SNMonthViewCell", for: indexPath) as! SNMonthViewCell
cell.monthLabel.text = dateFormatter.string(from: month!)
cell.monthLabel.textAlignment = .center
return cell
} else {
preconditionFailure("Unknown collection view!")
}
}
如您所见,加载我的帖子后,我设置了时间线 collectionView 的数据源 - 时间线是我感兴趣的内容,因为它包含嵌套的 subcollectionView。基本上它的工作原理是每个 SNMonthViewCell 都标有月份 - 例如 Jan,然后在单元格内,一个嵌套的子集合包含一个单元格代表 jan 的每一天。现在要在月单元格中为这几天的 collectionView 设置 delegate/data 来源,我这样做:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath){
if collectionView == self.timeline{
guard let monthViewCell = cell as? SNMonthViewCell else {
return
}
let index = indexPath.item
let firstPost = self.posts.first?.timeStamp
let monthDate = Calendar.current.date(byAdding: .month, value: index, to: firstPost!)
let monthInt = Calendar.current.component(.month, from: monthDate!)
let yearInt = Calendar.current.component(.year, from: monthDate!)
let postDates = dates(self.posts, withinMonth: monthInt, withinYear: yearInt)
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
}
class dayCellDelegates: NSObject,UICollectionViewDataSource, UICollectionViewDelegate {
let firstDay: Date
let monthPosts: [Post]
init(firstDay: Date, monthPosts: [Post]){
self.firstDay = firstDay
self.monthPosts = monthPosts
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
print("how many cells?")
let range = Calendar.current.range(of: .day, in: .month, for: self.firstDay)!
return range.count
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("looking for cell!")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dayCell",
for: indexPath as IndexPath)
let components: Set<Calendar.Component> = [.day]
//let contained = self.postDates.reduce(false,{Calendar.current.dateComponents(components, from: [=11=]).day == indexPath.item})
let filtered = self.monthPosts.filter { (post) -> Bool in
Calendar.current.dateComponents(components, from: post.timeStamp).day == indexPath.item
}
if filtered.isEmpty == true {
cell.backgroundColor = UIColor(red:0.15, green:0.67, blue:0.93, alpha:1.0)
}
return cell
}
}
你可以看到我使用我的外部 class dayCellDelegates 来设置这个 subcollectionView 的委托。现在在我之前提到的 SNMonthViewCell 中,我像这样设置数据源和委托:
class SNMonthViewCell: UICollectionViewCell {
@IBOutlet private weak var dayTicks: UICollectionView!
@IBOutlet weak var monthLabel: UILabel!
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDataSource & UICollectionViewDelegate>
(dataSourceDelegate: D) {
dayTicks.delegate = dataSourceDelegate
dayTicks.dataSource = dataSourceDelegate
dayTicks.reloadData()
}
}
太棒了...但是,我的 subcollectionView 的委托方法从未被调用过。我怎么知道?因为我放在前两个方法中的那些第一个打印语句从未被调用过....
我的时间表是什么样的?
不知何故,仅仅一个月的时间,none 天的单元格就被绘制出来了。我认为主要是因为这些委托和数据源方法没有被用来绘制它们。我在这里做错了什么?
让我们看看当您设置数据源并委托给 SNMonthViewCell
单元格时发生了什么
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath){
if collectionView == self.timeline{
...
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
}
您实例化 dayCellDelegates
对象,将其设置到您的单元格中,仅此而已。 dayCellDelegates
将在函数退出时释放。
很简单,您需要做的是保存对 dayCellDelegates
对象的引用,这样它们就不会被释放。你可以用不同的方式,但我建议使用字典。
首先,您需要在 TagViewController class 中创建 属性 private var dataSources: [IndexPath : dayCellDelegates] = [:]
。现在,当调用 willDisplay
方法时,您需要将 dayCellDelegates
对象保存到字典中
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath){
if collectionView == self.timeline{
guard let monthViewCell = cell as? SNMonthViewCell else {
return
}
let index = indexPath.item
let firstPost = self.posts.first?.timeStamp
let monthDate = Calendar.current.date(byAdding: .month, value: index, to: firstPost!)
let monthInt = Calendar.current.component(.month, from: monthDate!)
let yearInt = Calendar.current.component(.year, from: monthDate!)
let postDates = dates(self.posts, withinMonth: monthInt, withinYear: yearInt)
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
dataSources[indexPath] = dayDelegatesInstance
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
}
但是,每次调用方法时,您都需要重写委托。要解决此问题,您需要检查 dayCellDelegates
之前是否保存过
if let dayDelegatesInstance = dataSources[indexPath] {
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
} else {
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
dataSources[indexPath] = dayDelegatesInstance
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
我有一个像这样的 collectionView 设置:
class TagViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, SNPostViewCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
timeline.delegate = self
UserService.posts(for: User.current,tagString: self.tagTitle!) { (postStuff) in
let posts = postStuff.0
self.postIds = postStuff.1
self.posts = posts.sorted(by: {
[=10=].timeStamp.compare(.timeStamp) == .orderedAscending
})
self.timeline.dataSource = self
self.collectionView.dataSource = self
//self.timeline.collectionViewLayout.invalidateLayout()
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionView {
return posts.count
} else if collectionView == self.timeline {
let first = posts.first?.timeStamp
let last = posts.last?.timeStamp
let months = last?.months(from: first!) ?? 0
print("no of months",months)
if let diff = last?.months(from: first!), diff <= 5 {
return months + 5-diff
} else {
return months + 1
}
} else {
preconditionFailure("Unknown collection view!")
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionView {
let post = posts[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SNPostViewCell
cell.isVideo = post.isVideo
cell.delegate = self
cell.notes.text = post.notes
cell.thumbnailURL = URL(string: post.thumbnailURL)
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
cell.timeStampLabel.text = formatter.string(from: post.timeStamp)
cell.mediaURL = URL(string: post.mediaURL)
cell.notes.topAnchor.constraint(equalTo: cell.thumbnail.bottomAnchor,constant: 0.0).isActive = true
return cell
} else if collectionView == self.timeline {
let index = indexPath.row
print(index,"index")
let calendar = Calendar.current
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let firstPost = posts.first?.timeStamp
let month = calendar.date(byAdding: .month, value: index, to: firstPost!)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SNMonthViewCell", for: indexPath) as! SNMonthViewCell
cell.monthLabel.text = dateFormatter.string(from: month!)
cell.monthLabel.textAlignment = .center
return cell
} else {
preconditionFailure("Unknown collection view!")
}
}
如您所见,加载我的帖子后,我设置了时间线 collectionView 的数据源 - 时间线是我感兴趣的内容,因为它包含嵌套的 subcollectionView。基本上它的工作原理是每个 SNMonthViewCell 都标有月份 - 例如 Jan,然后在单元格内,一个嵌套的子集合包含一个单元格代表 jan 的每一天。现在要在月单元格中为这几天的 collectionView 设置 delegate/data 来源,我这样做:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath){
if collectionView == self.timeline{
guard let monthViewCell = cell as? SNMonthViewCell else {
return
}
let index = indexPath.item
let firstPost = self.posts.first?.timeStamp
let monthDate = Calendar.current.date(byAdding: .month, value: index, to: firstPost!)
let monthInt = Calendar.current.component(.month, from: monthDate!)
let yearInt = Calendar.current.component(.year, from: monthDate!)
let postDates = dates(self.posts, withinMonth: monthInt, withinYear: yearInt)
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
}
class dayCellDelegates: NSObject,UICollectionViewDataSource, UICollectionViewDelegate {
let firstDay: Date
let monthPosts: [Post]
init(firstDay: Date, monthPosts: [Post]){
self.firstDay = firstDay
self.monthPosts = monthPosts
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
print("how many cells?")
let range = Calendar.current.range(of: .day, in: .month, for: self.firstDay)!
return range.count
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("looking for cell!")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dayCell",
for: indexPath as IndexPath)
let components: Set<Calendar.Component> = [.day]
//let contained = self.postDates.reduce(false,{Calendar.current.dateComponents(components, from: [=11=]).day == indexPath.item})
let filtered = self.monthPosts.filter { (post) -> Bool in
Calendar.current.dateComponents(components, from: post.timeStamp).day == indexPath.item
}
if filtered.isEmpty == true {
cell.backgroundColor = UIColor(red:0.15, green:0.67, blue:0.93, alpha:1.0)
}
return cell
}
}
你可以看到我使用我的外部 class dayCellDelegates 来设置这个 subcollectionView 的委托。现在在我之前提到的 SNMonthViewCell 中,我像这样设置数据源和委托:
class SNMonthViewCell: UICollectionViewCell {
@IBOutlet private weak var dayTicks: UICollectionView!
@IBOutlet weak var monthLabel: UILabel!
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDataSource & UICollectionViewDelegate>
(dataSourceDelegate: D) {
dayTicks.delegate = dataSourceDelegate
dayTicks.dataSource = dataSourceDelegate
dayTicks.reloadData()
}
}
太棒了...但是,我的 subcollectionView 的委托方法从未被调用过。我怎么知道?因为我放在前两个方法中的那些第一个打印语句从未被调用过....
我的时间表是什么样的?
不知何故,仅仅一个月的时间,none 天的单元格就被绘制出来了。我认为主要是因为这些委托和数据源方法没有被用来绘制它们。我在这里做错了什么?
让我们看看当您设置数据源并委托给 SNMonthViewCell
单元格时发生了什么
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath){
if collectionView == self.timeline{
...
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
}
您实例化 dayCellDelegates
对象,将其设置到您的单元格中,仅此而已。 dayCellDelegates
将在函数退出时释放。
很简单,您需要做的是保存对 dayCellDelegates
对象的引用,这样它们就不会被释放。你可以用不同的方式,但我建议使用字典。
首先,您需要在 TagViewController class 中创建 属性 private var dataSources: [IndexPath : dayCellDelegates] = [:]
。现在,当调用 willDisplay
方法时,您需要将 dayCellDelegates
对象保存到字典中
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath){
if collectionView == self.timeline{
guard let monthViewCell = cell as? SNMonthViewCell else {
return
}
let index = indexPath.item
let firstPost = self.posts.first?.timeStamp
let monthDate = Calendar.current.date(byAdding: .month, value: index, to: firstPost!)
let monthInt = Calendar.current.component(.month, from: monthDate!)
let yearInt = Calendar.current.component(.year, from: monthDate!)
let postDates = dates(self.posts, withinMonth: monthInt, withinYear: yearInt)
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
dataSources[indexPath] = dayDelegatesInstance
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}
}
但是,每次调用方法时,您都需要重写委托。要解决此问题,您需要检查 dayCellDelegates
之前是否保存过
if let dayDelegatesInstance = dataSources[indexPath] {
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
} else {
let dayDelegatesInstance = dayCellDelegates(firstDay: (monthDate?.startOfMonth())!, monthPosts:postDates)
dataSources[indexPath] = dayDelegatesInstance
monthViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: dayDelegatesInstance)
}