Collection View isn't being displayed, cellForItemAtIndexPath is never called - ios

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

Related

Collection view within another collection view: section header or cell?

I already have a UICollectionView which scrolls vertically and shows a collection of custom UICollectionViewCells which have a fixed size.
Now I've been asked for showing another UICollectionView in top of all other cells, which should scroll horizontally and whose cells size is dynamic (I'll only know the size after at an async network call completion). In addition, this inner collection view may not be always necessary to be shown (it depends on the data received from the network call), but if it is, it should be shown only once (on top of everything).
My question is: how the best way to deal with this second and inner collection view should be? Should I add it to the outer view controller as a different kind of cell of it, or maybe as a section header?
Maybe another approach to layout this would be better?
EDIT: More considerations:
I'd need to animate the inner collection view when I'm going to show it
The whole thing should be vertically scrollable, this inner collection view should not stick to the top of the screen
"how the best way to deal with this second and inner collection view should be?"
"Should I add it to the outer view controller as a different kind of cell of it, or maybe as a section header?"
"I'd need to animate the inner collection view when I'm going to show it"
"The whole thing should be vertically scrollable, this inner collection view should not stick to the top of the screen."
It sometimes helps to take a step back and write out your requirements, think of each one independently:
1) The first cell of the CollectionView Should Scroll Horizontally.
2) The first cell should scroll past the screen vertically.
First cell of the CollectionView needs to contain a CollectionView itself.
3a) The CollectionView's other cells are of static size.
3b) The CollectionViews's first cells are of dynamic size.
Two Cell Classes are needed, or one cell class with dynamic constrains and subviews.
4) The CollectionView's First cells should be animated.
The first cells' CollectionView needs to be the delegate of its dynamic cells. (Animation occurs in cellForItemAt indexPath)
Keep in mind that UICollectionView's are independent views. A UICollectionViewController is essentially a UIViewController, UICollectionViewDelegate and UICollectionViewDataSource that contains a UICollectionView. Just like any UIView you can subclass UICollectionView and add it to a subview of another view, say UICollectionViewCell. In this way you can add a collection view to a cell and add cells to that nested collection view. You can also allow that nested collection view handle all the delegate methods from UICollectionViewDelegate and UICollectionViewDataSource essentially making it modular and reusable. You can pass the data to be displayed in each cell of the nested UICollectionView within a convenience init method and allow that class to handle animation and setup. This is by far the best way of doing it, not only for reuse but also for performance, especially when you are creating the views programmatically.
In the example below I have one UICollectionViewController named ViewController that will be the view controller for all other views.
I also have two CollectionViews, ParentCollectionView and HorizontalCollectionView. ParentCollectionView is an empty implementation of UICollectionView. I could use the collectionView of my UICollectionViewController but because I want this to be thoroughly modular I will later assign my ParentCollectionView to the ViewController's collectionView. ParentCollectionView will handle all the cells static cells in the view, including the one containing our HorizontalCollectionView. HorizontalCollectionView will be the delegate and data source for all 'cells objects' (your data model) passed to it within its convenience initializer. That is to say that HorizontalCollectionView will manage it own cells so that our UICollectionViewController doesn't get fat.
In addition to two CollectionViews and a UICollectionViewController, I have two UICollectionViewCell classes one of static sizing and the other dynamic (randomly generated CGSize). For ease of use I also have a extension that returns the classname as the identifier, I don't like using hard coded strings for reusable cells. These cell classes are not all that different, one could use the same cell and change the cell size in sizeForItemAt indexPath or cellForItemAt indexPath but for the sake of demonstration I'm going to say that they are completely different cells that require entirely different data models.
Now, we don't want the first cell in our ParentCollectionView to be dequeued, this is because the cell will be removed from memory and thrown back into the queue for reuse and we certainly don't want our HorizontalCollectionView popping up randomly. To avoid this we need to register both our StaticCollectionViewCell and a generic cell that will only ever be used once, since I added an extension that gives me the classname for the cell earlier I will just use UICollectionViewCell as the identifier.
I'm sure you won't have much trouble figuring out the rest, Here is my full implementation:
ViewController.swift
import UIKit
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// Programmically add our empty / custom ParentCollectionView
let parentCollectionView: ParentCollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = ParentCollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setup()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setup() {
// Assign this viewcontroller's collection view to our own custom one.
self.collectionView = parentCollectionView
// Set delegate and register Static and empty cells for later use.
parentCollectionView.delegate = self
parentCollectionView.register(StaticCollectionViewCell.self, forCellWithReuseIdentifier: StaticCollectionViewCell.identifier)
parentCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: UICollectionViewCell.identifier)
// Add simple Contraints
let guide = self.view.safeAreaLayoutGuide
parentCollectionView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
parentCollectionView.leftAnchor.constraint(equalTo: guide.leftAnchor).isActive = true
parentCollectionView.rightAnchor.constraint(equalTo: guide.rightAnchor).isActive = true
parentCollectionView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
}
// MARK: - CollectionView
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Erroneous Data from your network call, data should be a class property.
let data = Array.init(repeating: "0", count: 12)
// Skip if we dont have any data to show for the first row.
if (indexPath.row == 0 && data.count > 0) {
// Create a new empty cell for reuse, this cell will only be used for the frist cell.
let cell = parentCollectionView.dequeueReusableCell(withReuseIdentifier: UICollectionViewCell.identifier, for: IndexPath(row: 0, section: 0))
// Programmically Create a Horizontal Collection View add to the Cell
let horizontalView:HorizontalCollectionView = {
// Only Flow Layout has scroll direction
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
// Init with Data.
let hr = HorizontalCollectionView(frame: cell.frame, collectionViewLayout: layout, data: data)
return hr
}()
// Adjust cell's frame and add it as a subview.
cell.addSubview(horizontalView)
return cell
}
// In all other cases, just create a regular cell.
let cell = parentCollectionView.dequeueReusableCell(withReuseIdentifier: StaticCollectionViewCell.identifier, for: indexPath)
// Update Cell.
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// 30 sounds like enough.
return 30
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//If you need your first row to be bigger return a larger size.
if (indexPath.row == 0) {
return StaticCollectionViewCell.size()
}
return StaticCollectionViewCell.size()
}
}
ParentCollectionView.swift
import UIKit
class ParentCollectionView: UICollectionView {
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
HorizontalCollectionView.swift
import Foundation
import UIKit
class HorizontalCollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// Your Data Model Objects
var data:[Any]?
// Required
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
}
convenience init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout, data:[Any]) {
self.init(frame: frame, collectionViewLayout: layout)
// Set These
self.delegate = self
self.dataSource = self
self.data = data
// Setup Subviews.
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return zero if we have no data to show.
guard let count = self.data?.count else {
return 0
}
return count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.dequeueReusableCell(withReuseIdentifier: DynamicCollectionViewCell.identifier, for: indexPath)
// Do Some fancy Animation when scrolling.
let endingFrame = cell.frame
let transitionalTranslation = self.panGestureRecognizer.translation(in: self.superview)
if (transitionalTranslation.x > 0) {
cell.frame = CGRect(x: endingFrame.origin.x - 200, y: endingFrame.origin.y - 100, width: 0, height: 0)
} else {
cell.frame = CGRect(x: endingFrame.origin.x + 200, y: endingFrame.origin.y - 100, width: 0, height: 0)
}
UIView.animate(withDuration: 1.2) {
cell.frame = endingFrame
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// See DynamicCollectionViewCell size method, generate a random size.
return DynamicCollectionViewCell.size()
}
func setup(){
self.backgroundColor = UIColor.white
self.register(DynamicCollectionViewCell.self, forCellWithReuseIdentifier: DynamicCollectionViewCell.identifier)
// Must call reload, Data is not loaded unless explicitly told to.
// Must run on Main thread this class is still initalizing.
DispatchQueue.main.async {
self.reloadData()
}
}
}
DynamicCollectionViewCell.swift
import Foundation
import UIKit
class DynamicCollectionViewCell: UICollectionViewCell {
/// Get the Size of the Cell
/// Will generate a random width element no less than 100 and no greater than 350
/// - Returns: CGFloat
class func size() -> CGSize {
let width = 100 + Double(arc4random_uniform(250))
return CGSize(width: width, height: 100.0)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
self.backgroundColor = UIColor.green
}
}
StaticCollectionViewCell.swift
import Foundation
import UIKit
class StaticCollectionViewCell: UICollectionViewCell {
/// Get the Size of the Cell
/// - Returns: CGFloat
class func size() -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: 150.0)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
self.backgroundColor = UIColor.red
}
}
CollectionViewCellExtentions.swift
import UIKit
extension UICollectionViewCell {
/// Get the string identifier for this class.
///
/// - Returns: String
class var identifier: String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
}

In UICollectionView, the order changes automatically

I have a big problem.
I scrolled in the UICollectionView.
When the drawing area is scrolled too much, the arrangement order has become disjointed.
I do not want to change the order even though scrolling.
What should I do?
help me.
let titles = ["1","2","3","4","5","6","7","8", "9", "10", "11", "12"] //titles
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = testCollectionView.dequeueReusableCell(withReuseIdentifier: "TestCell", for: indexPath) as! TestCell //customCell
let testTitle = cell.contentView.viewWithTag(2) as! UILabel
testTitle.text = titles[indexPath.row]
return cell // yeah, all done. is these code is normal. right? But when i scroll this UICollection, change the order in Outside drawing area.
}
You should definitely not be using viewWithTag. The reason for this is because cells are dequeued, which means that cells that have been taken off screen by scrolling are reused for the cells which are about to come on screen. This is done to save memory. Sometimes the problem with the order can be because previously used cells are not updated quick enough when they are presented to the user. The solution to this if you are using a network request can be to firstly to streamline your request and then cache any heavy data that is returned, such as images.
Your code should not be causing the cells to change order. I'm wondering if you have a problem with using viewWithTag. That's a fairly fragile way to find views in a collection view/table view cell.
You already have a custom cell type. I would suggest creating an outlet to your label in your custom cell type, and referencing the label that way:
class TestCell: UICollectionViewCell {
//(Connect this outlet in your cell prototype in your Storyboard
#IBOutlet titleLabel: UILabel!
}
//And in your view controller's cellForItemAt method...
cell.titleLabel.text = titles[indexPath.row]

Table views inside collection view cells?

The Issue
I am attempting to use a collection view in a view controller for cards. When a user taps on a card, it expands. At all times, I have a tableview in each card, whether it is expanded or not. I have the data loading in the table views, but only when I tap on a card to expand it or if I scroll collection view cards offscreen and back onscreen. What is a cleaner workflow to doing this that puts tableviews in each collection view cell?
This is in my main view controller:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productionBinderCell", for: indexPath) as? ProductionBinderCollectionViewCell
let suck = cell?.detailsTableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? DescriptionTableViewCell
suck?.descriptionText.text = productions[indexPath.row].productionDescription
cell?.detailsTableView.reloadData()
cell?.layer.shouldRasterize = true
cell?.layer.rasterizationScale = UIScreen.main.scale
let title = productions[indexPath.row].productionTitle
let luck = productions[indexPath.row].productionImage
let zuck = UIImage(data:luck!)
cell?.productionTitle.text = title
cell?.productionFeaturedImage.image = zuck
cell?.productionColorTint.tintColor = UIColor.blue
let backbtn = cell?.viewWithTag(1)
backbtn?.isHidden = true
return cell!
}
This is in a subclass of the tableview:
override func layoutSubviews() {
super.layoutSubviews()
self.dataSource = self
self.delegate = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productionDescription", for: indexPath) as? DescriptionTableViewCell
return cell!
}
This is in a subclass of the tableview cell I am concerned with. It only shows the uilabel text when scrolling offscreen or tapping on the collection view cell:
class DescriptionTableViewCell: UITableViewCell {
#IBOutlet var descriptionText: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Maybe I shouldn't be using a tableview at all? Thank you very much to all who help. I am very open to criticism and love learning from my mistakes (a little too often). :)
What you need, first of all, is clear definition of views and cells.
UICollectionview is a collection of UICollectionViewCell objects
UITableView is a collection of UITableViewCell objects
Do you need to embed entire table view within each card? There you have your answer. You have parent-child relationship between your UICollectionViewCell and UITableView derived classes:
Derive from UICollectionViewCell - MyCollectionViewCell - this step is mandatory.
Inside xib for MyCollectionViewCell, insert a tableview, with horizontal scrolling enabled. (this is an option inside xib attribute editor)
If needed to customize, also derive from UITableViewCell - MyTableViewCell. This step is optional.
Inside MyCollectionViewCell class file, have implementation for table view datasource and delegate methods. That way, each card will have all its table view cell children in one place
If present, define custom logic inside MyTableViewCell class file
There is no need to subclass UITableView here. Everything that needs to be customized can be done using subclassing cells.

How to remove linebreak from UICollectionViewFlowLayout

I am trying to remove line breaks for the items within my section using the UICollectionViewFlowLayout .
At the moment I have
|header |
|0-0 0-1 0-2 0-3|
|0-4 0-5 |
|header |
|1-0 1-1 1-2 0-3|
|1-4 1-5 |
and I need :
|header |
|0-0 0-1 0-2 0-3| 0-4 0-5
|header |
|1-0 1-1 1-2 1-3| 1-4 1-5
so users can horizontally scroll . On iOS i resolve this by creating two nested collectionViews but on tvOS I am unable to replicate the solution because i am unable to focus on the inner cells .
after several tries I override the preferredFocusedView variable on the tableCell where the nested UICollectionView is :
override var preferredFocusedView: UIView? {
return moviesCollection
}
This behavior allows me to swipe horizontally between the inner elements of the collection but i cannot swipe vertically to change between table cells.
I am kind of lost ,Any kind of help would be greatly appreciated.
Thanks .
This can be solved very quickly with a custom UICollectionViewLayout
Subclass UICollectionViewLayout & override the following properties and methods
var collectionViewContentSize: CGSize
func prepare()
func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
Make sure to return the correct frame for each cell
Here is my working solution that also conveniently provides some properties of UICollectionViewFlowLayout (itemSize, minimumInteritemSpacing, minimumLineSpacing).
You might not need to change anything and just add it to your project.
https://gist.github.com/kflip/52ec15ddb07aa830d583856909fbefd4
Feel free to add scrollDirection support ... its scrolling in both directions already, but you might want to change if one section should be displayed as column instead of a row...
You could also add FlowLayout-like protocol functionalities if you need more customisations based on IndexPaths (e.g. CellSize)
Updated
Here's a GIF showing the end result:
Original
I've just created a test ViewController and this seems to work alright.
Basically, what I have there is a UICollectionView with a vertical flow layout. Each cell of that collectionView has it's own UICollectionView set with an horizontal flow layout.
The only incovenient for the "out of the behavior" is that the horizontal flow layour collection view does not "remember" the selected item when coming back to it but that can be solved with a little bit of playing around.
Hope it helps. Here's my code for it:
class ViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "ParentTestCell", for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TestHeader", for: indexPath)
}
}
// Parent cell - included in the vertical collection view & includes the horizontal collection view
class ParentTestCell: UICollectionViewCell, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "TestCell", for: indexPath)
}
}
// Child cell - included in the horizontal collection view
class TestCell: UICollectionViewCell {
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
if self.isFocused {
self.backgroundColor = .red
}
else {
self.backgroundColor = .blue
}
}, completion: nil)
}
}
And here's the storyboard structre:
Just a suggestion if it can help you.
Add tableView
Create Custom TableVeiwCell which contains collection view inside the cell with horizontal scrolling.
for each section you have, will become number rows for tableview
for items in row becomes number of items for the respective collection view.
A UICollectionView lay out its cells in a very specific way when using UICollectionViewFlowLayout, I'm not sure it's possible to make that kind of behaviour with a collectionview, whitout making your own UICollectionViewLayout. See this guide on how to make a UIColloctionView with a custom layout.
I would suggest trying to solve the problem, by using nested UITableViews. Make a vertical scrolling tableview, where every cell in the tableview has it's own horizontal scrolling tableview. The cells in the horizontal tableview, will be the actual cells that the user can select. See this guide on how to do this.
This should work in tvOS as well, since the focus engine should look through the cells subviews, and find the focusable ones. I hope it helps :)

Is completely static UICollectionView possible?

At UITableView, completely static tableView config is possible. You can disconnect UITableView's datasource and put each cell on storyboard(or xib) by using IB.
I tried same thing with UICollectionView. disconnect UICollectionView's datasource. Put each cell on UICollectionView on storyboard. I built it without any errors. But it didin't work. cells were not displayed at all.
Is UICollectionView without datasource possible?
No.
Creating a static UICollectionViewController is not allowed. You must have a data source delegate.
I also want to point out that there is not a static UITableView, but a static UITableViewController. It's a difference.
You can easily create a static UICollectionViewController.
Just create every cell in interface builder, give them re-use identifiers(e.g. "Home_1" "Home_2" "Home_3"), and populate the methods as follows:
class HomeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellIdentifiers:[String] = ["Home_1","Home_2","Home_3"]
let sizes:[CGSize] = [CGSize(width:320, height:260),CGSize(width:320, height:160),CGSize(width:320, height:100)]
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIdentifiers.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifiers[indexPath.item], for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return sizes[indexPath.item]
}
}
Then set the view controller to be of the proper class, and, hey presto, a (basically) static collection. I'm sorry to say but this is BY FAR the best way to support portrait and landscape views when you have groups of controls...
I did a little experimenting and wanted to add my own method since it helped me achieve the truly static, highly custom Collection View I was looking for.
You can create custom UICollectionViewCells for each cell you want to display in your Collection View, and register them with all the Cell IDs in your Collection View, like this:
Create your static cell:
class MyRedCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Make as many of these as you want.
And then back in your Collection View Controller, register them with their corresponding cellId:
let cellIds = ["redCell","blueCell","greenCell"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(MyRedCell.self, forCellWithReuseIdentifier: "redCell")
collectionView.register(MyBlueCell.self, forCellWithReuseIdentifier: "blueCell")
collectionView.register(MyGreenCell.self, forCellWithReuseIdentifier: "greenCell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIds[indexPath.item], for: indexPath)
return cell
}
Each cell will display exactly what's in its class.

Resources