Sorry for the vague title but I wasn't completely sure was to call it. I have a list of collection view cells in a collectionview these cells just have a white background. I have a total of 20 cells and I want the first one to have a cyan background and the fourth to have a green background. My issue is that if the list is big enough and I scroll the colors seem to be random sometimes 4 green and 2 cyan at the top instead of just 1 cyan and 1 green. I think this is due to using index path.row in the func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell method and assigning colors based on indexpath.row. I think index path.row changes as I scroll so when I scroll to the bottom index path.row is the item at the top of the screen not the top of the list. I understand this is not the proper way to achieve this, is there anyway to get the first/last item from the list instead of the first/last currently on my screen? Is there a better way to go about this entirely?
Here is a quick example of what the issue looks like - https://gyazo.com/e66d450e9ac50b1c9acd521c959dd067
EDIT:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int` is return 20 and in `func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell` this is what I have - `let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Invite Cell", for: indexPath) as! InviteCell
print(indexPath.row)
if indexPath.row == 0 {
cell.InviteCellContainer.backgroundColor = UIColor.cyan
} else if indexPath.row == 5 {
cell.InviteCellContainer.backgroundColor = UIColor.green
}
return cell
}
Cells are reused. Make sure that any UI element has a defined state in cellForItemAt:
In your code the state is undefined if the row is not 0 and not 5. So you need to add a case for all other indexes:
if indexPath.row == 0 {
cell.InviteCellContainer.backgroundColor = UIColor.cyan
} else if indexPath.row == 5 {
cell.InviteCellContainer.backgroundColor = UIColor.green
} else {
cell.InviteCellContainer.backgroundColor = UIColor.gray // or what the default color is
}
return cell
A more descriptive syntax is a switch expression
switch indexPath.row {
case 0: cell.InviteCellContainer.backgroundColor = UIColor.cyan
case 4: cell.InviteCellContainer.backgroundColor = UIColor.green
default: cell.InviteCellContainer.backgroundColor = UIColor.gray
}
Assuming your code isn't faulty, which I can't tell because you didn't include any of it, it looks like you should be calling collectionView.reloadData() after each cellForItemAt. Let me know what happens when you do this.
You should set the background color regardless of its place
if(indexPath.row == 0) {
cell.InviteCellContainer.backgroundColor = UIColor.cyan
} else if(indexPath.row == 5) {
cell.InviteCellContainer.backgroundColor = UIColor.green
} else {
cell.InviteCellContainer.backgroundColor = UIColor.white
}
return cell
It might be because you haven't defined the cell in a separate class and used the function prepareForReuse() to set the background back to white. Cells are reused within a collectionView so sometimes if you set the data (and don't reset it) it will remain the same when the cell is used again.
Hope this helps!
Related
I have a ViewController that have a collectionView and I managed to make it selectable and all but the problem is that I have a checkmark image that stays in the first cell when the VC opens but in fact the cell is not selected at all and still the checkmark is there!
Code of the VC:
var selected = IndexPath(item: 0, section: 0)
var properties = connectedProperties(StatusCode: 0)
var propertiesNew = connectedProperties(StatusCode: 0)
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return properties.Result?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dashboardCollectionViewCell", for: indexPath) as? dashboardCollectionViewCell else { return UICollectionViewCell() }
let currentPropertie = properties.Result?[indexPath.row]
cell.checkMarkButton.isHidden = !(indexPath == selected)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedCell = properties.Result?[indexPath.row]
changeCustomerKey.DefaultsKeys.keyTwo = indexPath.row
changeCustomerKey.DefaultsKeys.keyThree = selectedCell!.id!
let previous = selected.dropLast()
selected = indexPath
collectionView.reloadItems(at: [previous, selected])
}
If you want to tell your collection view to select a specific cell, you need to call the UICollectionView method selectItem(at:animated:scrollPosition:).
The "tricky bit" is that you can't call that until the collection view has finished populating itself (by calling your data source methods) and the first cell has been added to the collection view.
You might need to resort to something a bit hacky like adding an "initialDisplay" bool property who's value starts as true.
In your data source method that returns cells, check if initialDisplay==true and the requested indexPath is (0,0). If so, set initialDisplay=false, and fire a one-shot timer with a short delay. In the timer's closure, call selectItem(at:animated:scrollPosition:). The timer delay will return control to the event loop and give the system time to add the cell to the collection view.
There might be a better way to do this, but I can't think of it offhand, since you can't be sure when you will be asked to return your cell at IndexPath (0,0)
I am designing a menu tab bar with collectionview, and I want to change the color of the label when it is selcted.
Everything works fine but when the selected item is not in the screen anymore(due to scroll out of the screen), then the func inside didDeselectItemAt is not working anymore.
Is there anyway to solve this problem? Below is the code:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == self.productMenuCollectionView {
guard let cell = self.productMenuCollectionView.cellForItem(at: indexPath) as? ProductMenuCollectionViewCell else {
return
}
cell.label.textColor = UIColor.black
} else {
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.productMenuCollectionView {
let cell = self.productMenuCollectionView.cellForItem(at: indexPath) as! ProductMenuCollectionViewCell
cell.label.textColor = CustomColor.primary
} else {
}
}
You are observing this behaviour because the cells are reused, so one cell can be used for one index path, but when that index path scrolls out of view, and new index paths scrolls into view, the same cell object could be used for one of the new cells. Whenever you dequeue a cell, keep in mind that you might be reconfiguring old cells!
So what happens is, one of the old selected cells moves out of view, and gets reconfigured for use at a new index path. Your code presumably removes the selected color from that cell at that time, so when you scroll back up, the color is gone.
What you should do is, in ProductMenuCollectionViewCell, override isSelected:
override var isSelected: Bool {
didSet {
if isSelected {
self.label.textColor = CustomColor.primary
} else {
self.label.textColor = UIColor.black
}
}
}
And in cellForItemAtIndexPath:
if collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false {
cell.isSelected = true
} else {
cell.isSelected = false
}
I try to change the background colour of specific cell on button action. Color of cell is changing but but I scrolling this collection view the color of cell misplace from there original position
when I scrolling this collection view which contain question number, color position in this control misplaced like in this image
How I can handle this problem that collection view cell color never change their position automatically.
This is my code on button click to change the color :
let indexs = IndexPath(row: currentQuestion, section: 0)
let celi = questionNumberCollection.cellForItem(at: indexs)
celi?.backgroundColor = .blue
Problems is
You are changing the background color of the cell but you're not maintaining the state of the cell anywhere in your model which is important in the case where the cell is reused while scrolling.
Solution:
A simple and standard solution might be maintaining a state variable in a model or as a separate array, and change the background color in the cellforRowatIndexPath method.
Example:
struct Question {
var desc:String
var mark:Int
var status:AnsweringStatus
}
enum AnsweringStatus {
case notAttented,correct,inCorrect
}
class ViewController:UIViewController,UICollectionViewDataSource {
var dataSource:[Question]!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ShowCell", for: indexPath) as! ShowCell
switch dataSource[indexPath.row].status {
case .notAttented:
cell.backgroundColor = .gray
case .correct:
cell.backgroundColor = .red
case .inCorrect:
cell.backgroundColor = .green
}
return cell
}
}
Have showcased only the parts necessary to solve the issue. So on click of the button just changing the state in the respective model object using the index path and reloading the collection view will do the job.
Provide more insights about the issue, if this doesn't work for you.
I think your problem was that CollectionViews and TableView reuse the cells.
In your CollectionViewCell class use this method to reset the reused Cell to default values or Colors.
#IBAction func onButtonTappet(sender: UIButton) {
let indexs = IndexPath(row: currentQuestion, section: 0)
let cell = questionNumberCollection.cellForItem(at: indexs) as? MyCell
cell?.onButtonTapped = true
}
class MyCell: UICollectionViewCell {
var onButtonTapped: Bool = false {
didSet { checkBackgroundColor() }
}
override func prepareForReuse() {
checkBackgroundColor()
}
override func awakeFromNib() {
checkBackgroundColor()
}
private func checkBackgroundColor() {
self.backgroundColor = onButtonTapped ? myTappedColor : myDefaultColor
}
}
so i am experiencing two problems right now with my (pretty basic) tableview. i will paste my code below but here is the general gist: i'm making a dictionary app, so each cell contains two labels for the two languages. if a cell gets tapped, i'd like for: 1. cell height increase, 2. font size increase, 3. text align center, 4. cell background change color.
i've also included code to 'reset' a selected cell if it is selected a second time - this seems to be causing my second problem
the problem is when a cell is selected:
those four things happen to cells which are currently offscreen.
the program crashes when: i select cell 0, the cell changes which is fine, but when i click on any cell that is offscreen at the time (say cell 10) the program will crash giving me this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// reset previous cell but only if selectedRow isn't -1 which is initial value
if selectedRowIndex != -1{
let previousCell = convoTable.cellForRowAtIndexPath(NSIndexPath(forRow: selectedRowIndex, inSection: 0)) as! PhraseTableViewCell
previousCell.backgroundColor = UIColor.whiteColor()
previousCell.lang2Label.font = UIFont(name: (previousCell.lang2Label?.font?.fontName)!, size: (previousCell.lang2Label?.font?.pointSize)! - 5)
previousCell.lang2Label.textAlignment = .Right
}
//make changes to new cell that was pressed upon
if selectedRowIndex != indexPath.row{
print("new cell got selected")
cellSelected = true
// save the selected index
self.selectedRowIndex = indexPath.row
let cell = self.convoTable.cellForRowAtIndexPath(indexPath) as! PhraseTableViewCell
cell.lang2Label.font = UIFont(name: (cell.lang2Label?.font?.fontName)!, size: (cell.lang2Label?.font?.pointSize)! + 5)
cell.backgroundColor = UIColor.cyanColor()
cell.lang2Label.textAlignment = .Center
// update the height for all the cells
self.convoTable.beginUpdates()
self.convoTable.endUpdates()
cell.lang1Label.frame = CGRectMake(3,0, cell.bounds.width, cell.bounds.height/3)
cell.lang2Label.frame = CGRectMake(-3,cell.bounds.height/3, cell.bounds.width, cell.bounds.height/3 * 2)
}
else {
print("same cell pressed")
cellSelected = false
selectedRowIndex = -1
}
self.convoTable.beginUpdates()
self.convoTable.endUpdates()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PhraseTableViewCell
cell.lang1Label.text = items![indexPath.row].eng
cell.lang2Label.text = items![indexPath.row].prs
return cell
}
thank you all very much, this help is much appreciated.
so there is no real answer to the questions i posed, the best workaround is to just have another array which maintains the states in the cells. sucks i know but really the best "solution"
Hi so I'm using a side scrolling UICollectionView to display groups of people that the user makes. The groups are stored on my server and when the view loads, they load from the server. However I want the first cell to always be the same which is a cell which lets you create groups. This is the layout i need.
I know how to use multiple different custom cells, but how do I make it so the first cell is static and the cells after load content from my servers? Thanks :)
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return familyName.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell : AddGroupCollectionViewCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Add", forIndexPath: indexPath) as! AddGroupCollectionViewCell
return cell
} else {
let cell : FriendGroupsCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! FriendGroupsCell
cell.groupImage.image = UIImage(named: "pp")
cell.groupNameLabel.text = familyName[indexPath.row]
return cell
}
}
This is my code and it misses out the first person in the array because the index path skips over it. How can i modify this so it works
UICollectionViewCell is leveraging reusing techniques to improve performance. Remember this. Nothing can be static in a cell, because this cell later will be on another index.
You can use collectionView:cellForItemAtIndexPath: to make the first cell always load the same images/labels via indexPath.row == 0
You can use prepareReuse method to clean up the resources in the cell. So if cell No.2 is going to be the new No.1 cell, it get a chance to clean up old resources.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : AddGroupCollectionViewCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Add", forIndexPath: indexPath) as! AddGroupCollectionViewCell
if indexPath.row == 0 {
cell.groupImage.image = UIImage(named: "new")
cell.groupNameLabel.text = "new"
} else {
cell.groupImage.image = UIImage(named: "pp")
cell.groupNameLabel.text = familyName[indexPath.row]
}
return cell
}