Multiple UICollectionView Data Sources in One UIViewController - swift

I have a question about the implementation.
My users will be able to select from a dropdown what they want to see in the collection view. Depending on the choice, the dataset and potentially section/cell types will change.
Should I have one UICollectionView with all of the cells registered and based on the users choice modify my cellForItemAt?
Or should I have a UICollectionView per choice and then swap them depending on the choice?
What is the best and more professional implementation?

If the cells being displayed are similar in nature (they have the same structure of data aka picture, text, description, name, etc) then you should use one re-useable cell.
Having one cell makes your code much cleaner. You can also then set an observable property inside your cell that you set with the data. Pass the data to cell and then inside the cell class, it will be responsible for laying out the code.
let cell = someDequeuedCell
cell.data = somedata
return cell
Then inside the cell class,
var data: SomeData? {
didSet {
//Determine data and layout cell
if data == 'apple' {
//Layout apple cell
} else if data == 'orange' {
//Layout orange cell
} else {
//Layout other cell
}
}
}
This approach is very commonly used, however if each cell varies more than just data being changed. You should register multiple different cells for each datatype.
Inside the collectionView
if dataType == "apple" {
let cell = appleCell
cell.data = someData
return cell
} else if dataType == "orange" {
let cell = orangeCell
cell.data = someData
return cell
} else {
let cell = otherCell
cell.data = someData
return cell
}

Related

UICollectionViewDiffableDataSource cellProvider called more often than expected

I'm using the UICollectionViewDiffableDataSource to populate my UICollectionView. After receiving a list of items via REST API, I create a new snapshot and apply it like this:
DispatchQueue.main.async {
var snapshot = NSDiffableDataSourceSnapshot<RegionSection, DiffableModel>()
snapshot.appendSections(RegionSection.allCases)
snapshot.appendItems(self.spotlights, toSection: .Spotlights)
snapshot.appendItems(self.vendors, toSection: .Vendors)
self.dataSource?.apply(snapshot, animatingDifferences: animated)
}
When setting up my cells in the cellProvider, I asynchronously load images from a URL. I noticed, that the first cell would frantically flick through all the images that are loaded and end up displaying a different image than it was supposed to. (For example the image intended to be displayed by the last cell).
I decided to investigate and figured out that the cellProvider closure is called twice as many times as expected. Also the collectionView.dequeueReusableCell function behaves weirdly for the first half of the calls as it returns the same cell each time even though there are no cells in the collectionView that could be dequeued.
My cellProvider closure:
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, entry) -> UICollectionViewCell? in
if let spotlight = entry as? Spotlight{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "spotlightCell", for: indexPath) as! SpotlightCell
cell.nameLabel.text = spotlight.title
cell.subtitleLabel.text = spotlight.subtitle
cell.categoryLabel.text = spotlight.type.getDescription().uppercased()
cell.imageView.loadImage(fromUrl: spotlight.titlePictureUrl)
return cell
}else if let vendor = entry as? Vendor{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "vendorCell", for: indexPath) as! VendorCell
cell.nameLabel.text = vendor.title
cell.assortmentLabel.text = vendor.assortmentDescription
cell.imageView.loadImage(fromUrl: vendor.titlePictureUrl ?? vendor.pictureUrls?.first ?? "")
if let distance = vendor.distance{
cell.distanceLabel.text = (distance/1000) < 1 ? (distance.getReadableString(withDecimalSeparator: ",", andDecimalCount: 0) + "m entfernt") : ((distance/1000).getReadableString(withDecimalSeparator: ",", andDecimalCount: 0) + "km entfernt")
}
return cell
}
return nil
}
Here is an example:
I create a snapshot containing 4 vendor entries (For simplicity I didn't add anything in the other section for this example)
The cellProvider is called 4 times (for each indexPath and entry) and the cell that is dequeued is the same one each time.
The cellProvider is called another 4 times (again, for each indexPath and entry) and this time the cells are different each time.
For each time the cellProvider is invoked I call loadImage, which tries to find an image for the URL in my image cache and if it cannot found one loads it asynchronously.
Since all calls happen almost simultaneously every image is loaded twice and the first cell displays one image after another until the last of the 4 URLSessions it initiated returns.
I can't imagine it is expected behaviour for the dataSource to call it's cellProvider closure that often and I simply can't figure out why this happens or find anything in the documentation on this.
I hope someone can explain to me why this happens and in case this is expected behaviour how to properly set up cells with asynchronous image loading using a DiffableDataSource.
EDIT:
The solution that worked for me was to use absolute instead of estimated sizes for my cells, as suggested by #Norb Braun!
Setting the estimated size to none fixed this issue for me. This solution may not work for you when you are required to use self sizing cells but if your cells keep the same size regardless the content you could give it a try.

Get the cell in a row and column (Cocoa)

I need to get the cell contained in a column at a specified row without using the tableview functions. For example:
for a in 0...tableview!.numberOfRows {
let column = tableview.tableColumns[2]
let cell = cell in column at the row a
}
Do you have any solutions?
EDIT: in Cocoa
I solved with this (to retrieve the values of an item in each cell), thanks Willeke
for a in 0...tableview!.numberOfRows-1 {
let cell = tableview.view(atColumn: 2, row: a, makeIfNecessary: false) as? cella
print(cell?.tipo.title)
}

Good practice to use guard/if-statements to style multiple CollectionvViewCells?

I have a dilemma where I have in my CollectionView each collectionviewcell is displaying different data and have different border/background styles to them. for example, a code snippet
let monsters: Monsters!
cell.graphViewIBO.isHidden = true
cell.monsblIBO.isHidden = true
cell.powersBgIBO.isHidden = true
guard indexPath.row != 0 else {
cell.indexOne(monsters)
return cell
}
guard indexPath.row != 1 else {
cell.indexTwo(monsters)
return cell
}
guard indexPath.row != 2 else {
cell.indexThree(monsters)
return cell
}
I feel like this may be bad practice and am uncertain whether I should continue doing it this way, or just use a scrollview and style each uiview in it independently.
Because as of right my storyboard contains several objects in one collectionviewcell that I need to hide/show depending on which index is being presented. Can I continue using a CollectionView for this? What method is the right method for this approach?
I think what you want to use is a switch statement. Your code would look as follows:
let monsters: Monsters!
cell.graphViewIBO.isHidden = true
cell.monsblIBO.isHidden = true
cell.powersBgIBO.isHidden = true
switch indexPath.row {
case 0:
cell.indexOne(monsters)
case 1:
cell.indexTwo(monsters)
case 2:
cell.indexThree(monsters)
default: break
}
return cell
If you are changing your cells a lot, you can create different subclasses of UICollectionViewCell, which you create depending on which index. If you are using each type of cell only once, a UIScrollView would be better. But if you are using the same type of cell more than once (and just change some text for example), you should use a UICollectionView.

Swift - read above cell value in tableview

Basically I am trying to get a value from a custom table view cell, as I have made 5 prototype cells - each with a different tableViewCell class. However, what I am trying to do is to read the data from the cell above. I have called my classes: TableViewCell1, TableViewCell2 etc... So on the cellForRowAtIndexPath, I have the code like this:
if indexPath.row == 1 {
let cell2 = tableView.dequeueReusableCellWithIdentifier("secondCell", forIndexPath: indexPath) as! TierCell2
return cell2
}
This works, but I have also tried to do it like this, to be able to access the value above (with no luck) - which only results in an optional value error.
let cell1:TableViewCell1 = TableViewCell1()
cell2.PriceText.text = cell1.PriceText.text
Any ideas on how I might accomplish this?
I suggest you then try creating one custom table view cell with the 5 price tier labels that you want to display. Then in the cellForRowAtIndexPath you just deque a cell as the custom class and fill in all the tier information that you already have in your dictionary and return that cell.

TableView Cells / Visual Format Language / Keeps adding same constraints?

Creating UITableViewCells including elements programmatically seems to be working except for the constraints. Or maybe it is. Everytime the cell reappears the same constraints will be added to the element. I'll have 20 of the same constraints added to a uiLabel after scrolling tableView. Here is my sublcass (condensed):
class TimeSlotCell: UITableViewCell {
let lblTime = UILabel()
var viewCons = [String:AnyObject]()
var previousCageView:UIView!
var myConstraints = [NSLayoutConstraint]()
func configureCell(row:Int,cageCount:Int) {
lblTime.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(lblTime)
placeConstraint("H:|-5-[time\(row)]", view:"time\(row)")
placeConstraint("V:|-17-[time\(row)]", view: "time\(row)
}
func placeConstraint(format:String, view:String) {
viewCons[view] = lblTime
let navViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: viewCons)
myConstraints += navViewConstraint
NSLayoutConstraint.activateConstraints(myConstraints)
}
And in my VC i'm calling the configureCell func within my cellForRowAtIndexPath.
Is there a way to check if an existing constraint exists? Is there a way to make sure only one of my constraints can be added? Or is there a person out there with a much better solution? Thanks in advance.
There is a dataSource for your tableView, when you configure your cell, you send the cell model to your cell. Whenever the dataSource changes, the cell model sent to the cell will change as well. So what you need to do is move most of 'addSubview' to the cell`s initialization. If you have to do cell.contentView.addSubview with the cell model. You have to judge if the cell model you send to the cell is the same as the former one. If it is equal, do nothing, else reset the view config.