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

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.

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.

Condition that should change configuration of a cell is not working, probably because of how the feed is populated

I am using XCode 9, and I have created a collection view programmatically. I have a function called populateFeedCollectionViewCell, where I use the data I have gotten from the server to populate each cell of my collection view. I call this fuction at cell for item.
In my populateFeedCollectionViewCell function I check if the Item has an image of not, and when it doesn't have an image, I tell the image view to be frame 1,1, by altering its constraints.
For some reason, this isn't working properly. All the items that should be altered are being altered with success, but other items are being altered as well (just some of them).
I make the xcode print me the item when it enters the condition where I alter the constraints, and the weird thing is that the items arent being printed (therefore aren't entering the condition by mistake), but are changing its constraints.
My guess is that when the override cell for item is being called, it is mistaking the index or something. But I don't know how to be sure that that is the problem nor how to fix it.
Here is my code:
Where I call populateFeedCollectionViewCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomFeedCell
presenter.populateFeedCollectionViewCell(cell: cell, item: itemArray[indexPath.row])
cell.shareButton.tag = indexPath.row
cell.delateButton.tag = indexPath.row
return cell
}
Where I give the condition, inside populateFeedCollectionViewCell:
if item.mediaUrl == "empty"{
debugPrint("entrou em empty", "media url:", item.mediaUrl, "title:", item.title)
cell.feedImageView.heightAnchor.constraint(equalToConstant: 206.0).isActive = false
cell.feedImageView.widthAnchor.constraint(equalToConstant: 341.0).isActive = false
cell.feedImageView.widthAnchor.constraint(equalToConstant: 1).isActive = true
cell.feedImageView.heightAnchor.constraint(equalToConstant: 1).isActive = true
} else {
cell.feedImageView.widthAnchor.constraint(equalToConstant: 1).isActive = false
cell.feedImageView.heightAnchor.constraint(equalToConstant: 1).isActive = false
cell.feedImageView.heightAnchor.constraint(equalToConstant: 206.0).isActive = true
cell.feedImageView.widthAnchor.constraint(equalToConstant: 341.0).isActive = true
}
The result on my feed:
ps: notice that when it doesnt have an image it is correct, but when you see the image as a line on top of the text, the constraints were applied when they shouldnt have been. Also, when I scroll the feed, other items change their constraints to the wrong way.
Image 1 (constraints applied correctly)
Image 2 (constraints applied when shouldn't be)
Image 3 (constraints not when shouldn't be)
Since there is cell resuing inside cellForItemAt , every time the cell reused it creates other constraints which conflicts , then cause unexpected results , you need to clear all constraints of feedImageView before creating new ones
Or better to create the constraints inside xib/custom class , then hook them as outlets and play with there constants
if item.mediaUrl == "empty"{
cell.feedImageViewWidth.constant = 1.0
cell.feedImageViewheight.constant = 1.0
} else {
cell.feedImageViewWidth.constant = 341.0
cell.feedImageViewheight.constant = 206.0
}
where
var feedImageViewWidth:NSLayoutConstraint! // or #IBOulet weak var .....
var feedImageViewheight:NSLayoutConstraint!
Warning:
Don't think constraints with
.isActive = false
deactivates old ones , No they add another deactivated constraint

Multiple UICollectionView Data Sources in One UIViewController

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
}

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.