I need to display an arrays of users on a UICollectionView with 1 cell 1 user info.
Based on the user online status, will determine on whether to show the online green dot on each cell.
My main problem now is when I scroll down to display more users, the online green dot some disappear and some will still remain. But when more cells being reuse, the green dot will randomly display on other cells. What should I implement to keep all the green dots display properly?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if user.online != "online"{
cell2.onlineIcon.isHidden = true
}
}
Change your cellForItemAt code with below:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if user.online != "online"{
cell2.onlineIcon.isHidden = true
} else {
cell2.onlineIcon.isHidden = false
}
}
As you correctly mentioned cells are reused.
When you set a value of a UI element in an if clause you have to add an else clause to set a default value
if user.online != "online" {
cell2.onlineIcon.isHidden = true
} else {
cell2.onlineIcon.isHidden = false
}
or much simpler in one line
cell2.onlineIcon.isHidden = user.online != "online"
Related
I want to achieve a smooth pagination experience in UICollectionView. The idea is simple: trigger an API call when the last cell of collectionView is reached.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == dataGroup.data.count-1{
print("last cell visible" )
self.isPaginating = true
Service.shared.fetchData(page: self.page) { rawData, error in
if let error = error{
print("error while fetching app groups", error)
return
}
sleep(2)
if let data = rawData{
self.dataGroup?.data += data.data
DispatchQueue.main.async {
self.collectionView.reloadData()
}
self.page+=1
self.isPaginating = false
}
}
}
//returning cells
}
Even though I don't scroll the collectionView, if indexPath.row == dataGroup.data.count-1 becomes true and the API call is triggered. What can be the cause?
PS. I observe the data.count and pagination stops when all the data is loaded. I just removed it to simplify the code in the question.
Try to move your code in
collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
https://developer.apple.com/documentation/uikit/uicollectionviewdelegate/1618087-collectionview
And add check like :
if collectionView.indexPathsForVisibleItems.lastObject.row == dataGroup.data.count-1. {
do something
}
I make a custom calendar using UICollectionView. From the calendar when I selected some dates and move forward to next month, then back again to the previous month, the selected item is deselected. Maybe it happens for reusable cell. How can I solve this problem.
For better understand what I want:
From September I select 4,5 then move to August/July/November (In this month maybe select some other dates or not)
Then return to September. In September I want to showed 4,5 as selected
I tried this using didSelectItemAt indexPath, but when return back to the September the selected item is deselected
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateRangePickerCell {
if cell.isSelected == true {
cell.backgroundColor = .blue
cell.label.textColor = .white
cell.isUserInteractionEnabled = true
}
selectedDate = cell.date!
}
}
First create an array of selected Cells. If you are using a model to set data to cell, you can create an array of selected models. or you can create an array of selected rows.
Let's say you are using a model.
var selectedDates: [DateModel] = []
Then
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateRangePickerCell {
selectedDate = cell.date!
if !selectedDates.contains(dataSourceModel[indexPath.row]) {
selectedDates.append(dataSourceModel[indexPath.row])
}
}
}
then in your cellForItem
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if selectedDates.contains(dataSourceModel[indexPath.row]) {
cell.isSelected = true
}
}
Also make sure you remove you model when unSelected
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if selectedDates.contains(dataSourceModel[indexPath.row]) {
selectedDates.remove(dataSourceModel[indexPath.row])
}
}
*may contain some syntax error, but you can follow this path to get where you want.
Cells in UITableView and UICollectionView are reused when you scroll, that is why you should store which days selected in another place. Then, in cellForItem you should set isSelected.
Have you tried setting clearsSelectionOnViewWillAppear to false in viewDidLoad()?
A Boolean value indicating if the controller clears the selection when the collection view appears.
The default value of this property is true. When true, the collection view controller clears the collection view’s current selection when it receives a viewWillAppear(_:) message. Setting this property to false preserves the selection.
Source: Apple Developer Documentation
I have a collection view, and you can select the items in it and toggle them on and off by changing the background colour. The cells are toggled on/off thanks to a boolean I have in an arrow I made for all of the cells. I have saved the bool value but when I try to write them back into the array and use collectionView.reloadData()the app crashes. My collectionViewcode is:
extension OLLViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { //set the amount of items in the CollectionView to the amount of items in the OLLData dictionary
return OLLData.OLLCasesList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { //set each cell to a different mamber of the dict.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OLLCell", for: indexPath) as! OLLCell
cell.imageView.backgroundColor = OLLData.OLLCasesList[indexPath.item]._isSelected ? UIColor.orange : UIColor.clear //change colour if selected
let image = OLLData.OLLCasesList[indexPath.item]._imageName
cell.label.text = image
cell.imageView.image = UIImage(named: image)
let savedIsSelected = defaults.bool(forKey: Key.isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = savedIsSelected
//collectionView.reloadData() //when uncommented it crashes the app
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { //detect if case selected and reload CollectionView
let caseName = OLLData.OLLCasesList[indexPath.item]._imageName
print(caseName, OLLData.OLLCasesList[indexPath.item]._isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = !OLLData.OLLCasesList[indexPath.item]._isSelected
defaults.set(OLLData.OLLCasesList[indexPath.item]._isSelected, forKey: Key.isSelected)
collectionView.reloadItems(at:[indexPath])
collectionView.reloadData()
if OLLData.OLLCasesList[indexPath.item]._isSelected == true { //if the item is selected, add to selectedCases array
selectedCases.append(OLLData.OLLCasesList[indexPath.item]._id)
selectedCaseNames.append(OLLData.OLLCasesList[indexPath.item]._imageName)
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
else if OLLData.OLLCasesList[indexPath.item]._isSelected == false { //remove from selectedCases array
selectedCases.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._id })
selectedCaseNames.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._imageName })
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
}
._isSelectedis the boolean that says whether the cell is 'toggled'.
Any ideas would be greatly appreciated.
First of all, uncommenting that line will produce an infinite loop. cellForRowAt happens because the collection view is reloading, so calling a refresh while the collection view is refreshing is no good.
So your issue is that you don't know how to display selected cells in your collection view, right?
Here's a function that fires right before the collection view is about to display a cell:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
<#code#>
}
Inside this function, you should:
Cast cell into your OLLCell (safely if you want to be thorough)
Look at your data and see if the cell should be selected OLLData.OLLCasesList[indexPath.item]._isSelected
Ask your casted cell to change its colors/UI/appearance according to your ._isSelected boolean
Step 3 has a VERY important caveat. You should be changing the UI when ._isSelected is false AND when it's true. Because the collection view reuses cells, old UI state will randomly recur. So setting it every time is a good way to ensure the behavior you want.
Here's an example:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
//Cast the vanilla cell into your custom cell so you have access
//to OLLCell's specific functions and properties.
//Also make sure the indexPath falls in the indices of your data
if let myCastedCell = cell as? OLLCell,
0 ..< OLLData.OLLCasesList.count ~= indexPath.item
{
myCastedCell.imageView.backgroundColor = OLLData
.OLLCasesList[indexPath.item]._isSelected
? UIColor.orange
: UIColor.clear
}
}
Have a UICollectionView implemented with multiple cells.
When you click on a cell I want to change the value in my selectedArray to true.
Here is my code:
var selectedArray = [Bool](count:201, repeatedValue:false)
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
if cell?.selected == true {
selectedArray[indexPath.row] == true
print("\(selectedArray[indexPath.row])")
}
}
The output is false for every cell I click so I know something is not linked correctly but I just don't see it.
You do not need to check to see if the cell is selected. Because you are calling didSelectItemAtIndexPath, you do not need to check for this. Simply run this:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedArray[indexPath.row] = true
}
selectedArray[indexPath.row] == true - need replace '==' to '='
I want to resize a particular cell in my collection view when it is selected. I found how to do so from previous posts, but ran into a problem. I found while debugging that the selection lagged behind by one, but I am not sure why.
For example, if I select one cell by tapping it, nothing happens. If I select another cell after, then the first cell I selected is enlarged. If I select a third cell, the second is enlarged. And so on.
This is how I implemented it, and only once cell is ever enlarged at the same time like I want:
var selectedIndexPath: NSIndexPath!
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if selectedIndexPath != nil { //We know that we have to enlarge at least one cell
if indexPath == selectedIndexPath {
return CGSize(width: self.gallery.frame.size.width, height: self.gallery.frame.size.width)
}
else {
return CGSize(width: self.gallery.frame.size.width/3.0, height: self.gallery.frame.size.width/3.0)
}
}
else {
return CGSize(width: self.gallery.frame.size.width/3.0, height: self.gallery.frame.size.width/3.0)
}
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
if selectedIndexPath != nil && selectedIndexPath == indexPath
{
selectedIndexPath = nil //Trigger large cell set back to normal
}
else {
selectedIndexPath = indexPath //User selected cell at this index
}
collectionView.reloadData()
}
I found while debugging that the selection lagged in didDeselectItemAtIndexPath in the way I described above, but am not sure why.
The problem is that you are're using func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath).
Pay attention to this phrase: didDeselectItemAtIndexPath.
didSelectItemAtIndexPath is a way to go.