In my view controllers, a lot of my cellForItemAt methods have computations in them. In the following example, I get the amount, image, and balanceAtDate and pass them into my view:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SummaryCell.cellId, for: indexPath) as! SummaryCell
let amount = filteredTransactions[indexPath.row].transactionAmount
let image = UIImage(named: filteredTransactions[indexPath.item].transactionCategory!.categoryName)
let balanceAtDate: Double = realm.objects(Transaction.self).filter("transactionDate <= %#", transactionResults[indexPath.item].transactionDate).sum(ofProperty: "transactionAmount")
cell.configure(with: image, and: amount, and: balanceAtDate)
return cell
}
It has come to my attention that I shouldn't make any computations or calculations in cellForItemAt because it is called for every cell and it will slow down the application.
I create 3 constants: amount, image, and balanceAtDate. Do I need to put all three somewhere else, or just the balanceAtDate?
Where, then, do I put a computation that requires the indexPath.item to get something like the balanceAtDate?
Related
I am having some trouble displaying images from the assetsCassets into my collectionViewCells.
I have 4 images named "Avenues", "Streets", "Hotels", "Restaurants"
My code for instantiating the array with the images is the following:
var arrayFotos = ["Avenues", "Streets", "Hotels", "Restaurants"]
And then to display it on the collectionViewCell, I do the following code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCellFotos", for: indexPath) as! FotosCollectionViewCell
for image in arrayFotos {
cell.imgFoto.image = UIImage(named: image)
}
return cell
}
However, it is only returning one of my four images. How could I display the four images into my collectionViewCell??
I presume that it is only displaying the "Restaurants" image. This is because you are running a for loop, resetting the image to be displayed by cell.imgFoto, starting first with "Avenues" and then ending with "Restaurants".
To fix this, it depends what are you trying to accomplish. Are you trying to display all four images in one cell? Or have four different cells, each with one image?
If you want the former, then you need to create four different image views in your cell, and then you can set them like this in the cellForItemAt method:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCellFotos", for: indexPath) as! FotosCollectionViewCell
cell.imgFoto1.image = UIImage(named: arrayFotos[0])
cell.imgFoto2.image = UIImage(named: arrayFotos[1])
cell.imgFoto3.image = UIImage(named: arrayFotos[2])
cell.imgFoto4.image = UIImage(named: arrayFotos[3])
return cell
If you want the latter, then you need to return 4 cells or arrayFotos.count (assuming you have only one section) in the func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int method.
Then, you can do the following in the cellForItemAt method:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCellFotos", for: indexPath) as! FotosCollectionViewCell
cell.imgFoto.image = UIImage(named: arrayFotos[indexPath.row])
return cell
This will create four different cells, each has its own image view set by the data in arrayFoto depending on the indexPath.row.
I am using UICollectionFlowLayout to establish minimumLineSpacing between collectionItems, I am trying to find a way to set the spacing to zero for some cells so there is no spacing, i.e they appear to be 'merged' while leaving others with their spaces intact, is it possible to make alterations in the cellForItemAt method for example to achieve this?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customColCell", for: indexPath) as! customColCell
if indexPath.row == 5 {
self.minimumLineSpacing = 100 // -- this does not result in an individual change...
}
cell.textLabel.text = indexPath.row.description
return cell
}
is it possible to make alterations in the cellForItemAt method for example to achieve this
No. What you're asking to do is not how a UICollectionViewFlowLayout behaves by default. You will need to write a collection view layout subclass.
I use this code to show a collection of images, which are scrollable. However, I would like to add a property to make it possible to identify each cell or image in this case. I want a property to every cell that can hold a string.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PalletsCollectionViewCell", for: indexPath) as! PalletsCollectionViewCell
cell.imgImage.image = imageArray[indexPath.row];
return cell;
}
Haven't found anything while reading the swift documentation.
What I want to do is to add events to the cells. If the user touches a cell, a function will be called. This function performs an API request with the property value as parameter.
I want to reload only single cell after user select the contact (method didSelect contact). If the number of item in cell getting bigger, the process is getting slower. I also try to implement in DispatchQueue.main.async { code } and it still take some time to load.
cellForItemAt and noOfItemInSection (AddCell) - Total cell 4
var indexPaths = [IndexPath]()
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
indexPaths.append(indexPath)
if indexPath.item == 1 {
let addCell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId, for: indexPath) as! AddCell
addCell.btnAdd.addTarget(self, action: #selector(handleOpenContact), for: .touchUpInside)
addCell.invitees = invitees
return addCell
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
cellForItemAt and noOfItemInSection (SubAddCell)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: subAddId, for: indexPath) as! SubAddCell
cell.delegate = self
cell.invitee = invitees[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return invitees.count
}
didSelect (ContactPicker)
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let invitee = Invitee()
invitee.name = contact.givenName
if contact.isKeyAvailable(CNContactPhoneNumbersKey) {
for phoneNumber: CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value
print("\(a.stringValue)")
invitee.phoneNo = a.stringValue
}
}
self.invitees.append(invitee)
for index in self.indexPaths {
if index == [0, 0] {
print(index)
self.collectionView?.reloadItems(at: [index])
}
}
}
I have obtain the value of indexPaths at cellForItemAt method. If the invitee contains 5 items, the print(index) will print 19 values. I dont know why. Below is the image.
Design of the UI
How to optimize the reloadItems?
Any help is really appreciated. Many thanks.
I think you have this indexPaths.append(indexPath) in cellForItemAt so that why the indexPaths can increase after reload data. You might use NSSet instead of NSArray to make sure your objects are unique.
And with this: cell.invitee = invitees[indexPath.item], did you implement didSet for invitee?
If you did, I think need to prepare data in background queue first. E.g.
DispatchQueue(label: "queue").async {
let image = <# process image here #>
let name = <# process name here #>
DispatchQueue.main.async {
self.imageView.image = image
self.label.text = name
}
}
Hope my tips could help you!
Collectionviews are the most powerful UI tool of iOS, but if done wrong, or if misused (which happens, don't worry), they can be worse for your UX. . To view how this stuff is affected read up on XCode Tools and how to use them to debug your UI performance.
Important note to consider:
frame-by-frame operations (ie, loading your collectionviewcell, expanded, or unexpanded) should take no more than 17 ms to execute for smooth scrolling.
UICollectionView performance - _updateVisibleCellsNow
So you need to make your cells as simple and efficient as possible. That's the best place to start, especially if your expandable cells themselves contain complex subviews like collectionviews.
I just updated to Swift 3.
My collection views were working great before the update. I did the recommended changes to make the compiler happy, but now I'm having this problem.
Image views that are in my custom UICollectionViewCells are simply not appearing anymore. Neither programmatically generated image views nor prototype image views are appearing.
I've given the image views background colors to check if my images are nil. The background colors aren't appearing, so it is safe to assume the image views are not appearing at all.
The cells themselves ARE appearing. Each image view has a label underneath, and the label is displaying properly with the correct text.
The most confusing part is that sometimes the image views DO appear, but there seems to be no rhyme or reason as to why or when.
My code is pretty standard, but I'll go ahead and share it anyway:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return searchClubs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: HomeCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
cell.barLabel.text = searchClubs[indexPath.row].name
cell.imageCell.image = searchClubs[indexPath.row].image
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height / 2
return cell
}
func feedSearchClubs(child: AnyObject) {
let name = child.value(forKey: "Name") as! String
//Image
let base64EncodedString = child.value(forKey: "Image")!
let imageData = NSData(base64Encoded: base64EncodedString as! String, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)
let image = UIImage(data:imageData! as Data)
//Populate clubs array
let club = Club.init(name: name, image: image!)
self.searchClubs.append(club)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
Since Xcode 8 you have to call layoutIfNeeded() to calculate size (in your case you need to know cell.imageCell.frame.height) and position from auto layout rules or use a fixed value of cornerRadius.
cell.imageCell.layoutIfNeeded()
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height / 2
OR
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = 5
The imageCell's frame isn't set up ready yet in the cellForItemAt method.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: HomeCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
cell.barLabel.text = searchClubs[indexPath.row].name
cell.imageCell.image = searchClubs[indexPath.row].image
return cell
}
Instead put the setting up of layer on willDisplay since cell.imageCell.frame.height will have its value.
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cell: HomeCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height / 2
}