I have a UITableView
with custom cells containing UIStackView
. As part of preparing a new cell, when the Table View adds new cells via its cellForRowAtIndexPath
method, it instantiates and adds any collection views (of type MultaCollectionView
) and any UILabel
which need to be added to the cell’s Stack View (a cell may include various collection views). In theory, a stack view contains a sequence of Labels and Collection Views.
Although labels are displaying correctly, the Collection View is not being displayed at runtime. The Collection View I’m attempting to display is defined in a .xib
Interface Builder document.
The Collection View’s numberOfSectionsInCollectionView
method is getting called, however the collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath)
method is never called.
Why is the collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath)
method never called? Why aren’t Collection Views being rendered in the Stack View?
import UIKit
private let reuseIdentifier = "Cell"
class MultaCollectionView: UICollectionView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.delegate = self
self.dataSource = self
}
class func instanceFromNib() -> UICollectionView {
return UINib(nibName: "multas", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UICollectionView
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
return cell
}
}
The parent TableView
adds Cells via the following method:
func addSubviewsToCellStack(cell: ArticuloTableViewCell, texto: [[String: String]]) {\
if isLabel == true {
let label = UILabel()
label.text = "blah"
subview = label
cell.textoStack.addArrangedSubview(subview)
} else {
let colview = MultaCollectionView.instanceFromNib()
cell.textoStack.addArrangedSubview(colview)
}
}
}
Since you are getting the call back for the numberOfSectionsInCollectionView
this would suggest the datasource is connected up correctly but if a cell is not going to be visible based on the current layout then the collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath)
will not get called until it is needed. The reason a cell might not be visible could be because your sectionInsets for the collectionView are set large enough that the first cell would be positioned outside the visible area of the collectionView.
Another reason could be that the collectionView's frame is too small to display any cells.
If your collectionview as a size of {0,0} you should get the calls as you are seeing to the numberOfSectionsInCollectionView
but will not get the calls to cellForItemAtIndexPath
since the cells will not be visible.
Perhaps try ensuring that your collectionView has a frame size that is large enough to display the cells you expect to see. You may be seeing the UILabels correctly because they have an implicit intrinsicContentSize
which ensures they display well in a stackView whereas the CollectionView is just as happy to have a size of 0,0 as it would be with any other size. Try giving the collectionView explicit width and height constraints to see if that helps.
My hypothesis would be that since this is happening inside cells of a UITableView, whenever a table view cell gets put back in the "reuse pool", the delegates of the collection view get disconnected or disabled. This would depends on the code that provide cells to the table view (which we're not seeing in the code sample).
There could also be some code elsewhere that clears the datasource from the collection view. (unlikely but worth checking)
The issue was resolved by overriding UIView
's intrinsicContentSize()
method in my UICollectionView
subclass as follows (check out this question):
override func intrinsicContentSize() -> CGSize {
return self.collectionViewLayout.collectionViewContentSize()
}
As an aside, I had also forgotten to register a class for the Cell reuse identifier. So I modified the initializer:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.delegate = self
self.dataSource = self
self.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
I FINALLY got it to work. When transitioning from a storyboard view controller to a non storyboard collection view controller you must do this while pushing to the controller :
func showChatLogController(user: User) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.user = user
chatLogController.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(chatLogController, animated: true)
}
To be more specific you must use collectionViewLayout: UICollectionViewFlowLayout