I am just starting to implement a multiselect UICollectionView. Would the below be considered "safe" code (since i assume theres some reason it is called BackgroundView instead of AccessoryView or such) ? I had the idea to save some effort, i intend to keep track of the selected items at the indexpath for further use via an array.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//....
cell.selectedBackgroundView = someView_With_A_Checkmark_Image;
[cell bringSubviewToFront:cell.selectedBackgroundView];
//...
return cell;
}
Is it safe?? Ya of course it wont cause any error. If your backgroundView is above the contentView of the cell, then what is the significance of contentView??.
Collection view cell structure
If you select an item in the collection view, collectionView will switch the BackgroundView and Selected background view. So what you can do is give valid views as background view and selected background view upon configuring your custom cell or change any properties of the cell in didSelectItem to differentiate selection. That is better.
Then one more no need to keep track of selection using a separate array. [self.collectionView indexPathsForSelectedItems] will give you selected items path at any point of time
Related
I changed the color of text for the cell clicked in the table. But after the cell is clicked, when i come back to table the text of cell has the original color. Could you give me an advice?
This is the code in "didSelectRowAtIndexPath"
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.highlightedTextColor = [UIColor blueColor];
Thank you
after the cell is clicked, when i come back to table the text of cell has the original color. Could you give me an advice?
You need to have the color for each cell stored somewhere other than in the table, so that you can reproduce the colors you want anytime the table redraws itself. Typically, you'll have some sort of data structure that stores the table's data, and that's usually the right place to save any changes the user makes. The table view's data source should have a -tableView:cellForRowAtIndexPath: method that sets the color according to what you've saved, along with any other cell attributes.
This is happen because the cells are reused, so lets say when you change text colour property of some cell it will be affected as you expect but when you scroll and that cell disappear off the screen it will be put to reuse pool and if it appears again on the screen table view takes some cell from the reuse pool but it's properties will be different so the colour won't persist.
You should keep somewhere, for example in NSMutableArray, info about which table was clicked.
You can add an index path to the array when you click the cell and in cellForRowAtIndexPath: check is this indexPath in the array and if it is change appropriate property.
The problem is that iOS throws away your cell if you scroll away and recreates it when it's needed (you scroll back to the cell).
If I were you, I would subclass UITableViewCell and overwrite
- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
In there you would have
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected: selected animated: animated];
self.textLabel.textColor = selected ? [UIColor blueColor] : [UIColor blackColor];
}
Since iOS UITableView remembers which cell is selected, this should work fine, even when it's recreated.
The reason it's happening is what others are saying: cells are reused.
Storing selection state or color will work, however if you just need to make sure that selected cells have a different color for a label than non-selected cells, there's a way that does not require to use a supporting data structure.
You just need to check if the cell being setup at - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath is currently selected or not, and that can be achieved with [tableView indexPathForSelectedRow] if your table uses single selection, or [tableView indexPathsForSelectedRows] if it uses multiple selection.
The last case requires you to find the current indexPath in the returned array, and might be slower than using the supporting array.
But if the selection is simple, then this solution is probably faster, uses less memory and is easier to read (IMO).
I'm using UICollectionView in one of my projects and when I try to call reloadData on the collection view, the cell order is always changed the same.
I create the cells something like that:
- (UICollectionViewCell *)collectionView:(UICollectionView *)aCollectionView
cellForItemAtIndexPath:(NSIndexPath *)aIndexPath {
PPhotoCVCell *cell =
[self._collectionView dequeueReusableCellWithReuseIdentifier:#"PPhotoCVCell"
forIndexPath:aIndexPath];
if (cell.photo == nil) {
PPhoto *photo = self._photos[aIndexPath.row];
cell.photo = photo;
}
cell.enableEditing = self._editing;
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
return cell;
}
The cell has two subviews of the class UIImageView, one for the image and another for an overlay if the cell is selected by the user.
When I'm in editing mode, the user can select cells. When some cells are selected and the user quits the editing mode, I set the alpha value of the overlay image view with an animation to 0.0 in the overwritten setSelected: method.
The problem is when I call the reloadData method on the collection view, the overlay image views in the selected cells hide without animation, but in other cells overlay image views appear without animation and disappear with the correct animation.
I detected that the image view for the photo is also changing when I call reloadData.
Is there any solution for this problem?
I ran into this same issue where each time I performed reloadData, the collection view would reverse it's order. Since I didn't see an answer here after 5 years, I'm posting my solution in case anyone else runs into this.
You actually have 2 options:
If you only need to reload a single cell, use the following instead of reloadData:
NSArray *thisItem = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:thisRow inSection:0]];
[self.collectionView reloadItemsAtIndexPaths:thisItem];
If you need to reload all data, use the following:
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
There's probably a reasonable explanation for this behavior somewhere, but I can't find one.
It sounds like your cells are being recycled. In your cell's prepareForReuse, make sure you're removing the overlay. Then add the overlay as appropriate in collectionView: cellForItemAtIndexPath:.
I have a UICollectionView inside of a normal UIViewController.
Inside the collectionview I have designed the reusable UI for the collectionviewcells in storyboard.
Inside of the collectionviewcell there is a label that displays the cells indexpath.row and 5 UIButtons which if selected, change color and stay selected.
I have set up the collectionview so that if more that 30 cells are requested the collectionview will page horizontally, the collectionview layout is also horizontal.
The application runs nicely, scrolls properly and lays out cells correctly.
The problem I am having is when you select for example button A in cell 1 in the collectionviewcell (which is suppose to layout 100 cells) and page over two pages (60+ Cells) to page 3, button A in cell number 75 is selected. And further more if you scroll to the end (100 cells) and scroll back to page 3, button A in cell number 75 is on longer selected, but button A in cell number 64 is selected.
Here is some snippets of code:
cell.m - controls the action from the user.
- (IBAction)bubbleButtons:(id)sender {
for(UIButton *bubbleCell in self.bubbleButtons) {
if (bubbleCell.touchInside && !bubbleCell.selected) {
bubbleCell.selected = YES;
} else if (bubbleCell.touchInside && bubbleCell.selected) {
bubbleCell.selected = NO;
}
}
}
MainViewContoller.m - sets up cell from UICollectionViewCell made in storyboard
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
Cell *cell1;
cell1 =[collectionView dequeueReusableCellWithReuseIdentifier:zCellID
forIndexPath:indexPath];
cell1.numMainLabel.text = [NSString stringWithFormat:#"%d |",indexPath.row+1];
return cell1;
I do not really understand what is wrong or what is causing this bug, I am assuming it has todo with the view being reloaded when a new part of the view becomes visible but that is just a guess. Help would be greatly appreciated.
Zach
It's probably because the reusable view is reused.
The proper way to do this is to create custom reusable view subclass.
And save the selection of those 5 button.
cell1 =[collectionView dequeueReusableCellWithReuseIdentifier:zCellID
This line here might or might not give you a new cell, it might give you a cell that is used before. So, you need to update the selection in there. Or it stay the same as the cell it's reusing.
The Uicollection only generate the cells that are been displayed at that moment, so when a cell disapier from the visible view, its been replace with the new one.
So when you seleted the cell 75 and you scroll down, until the cell 75 is no visible, and then you scroll back to cell 75, you are generating a new cell, with a new button that is not seleted because is new.
So what yo could do, is save which buttons had been selet, and in
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
ask if the button thats is been displayed at that moment need to be selected..
Hope its helps
I have an UICollectionViewController and my custom cells, and in my cellForRowAtIndexPath: method I set the cells based on indexPath.row.
But I am getting wrong results, this cell appears even after first position, and if you scroll back and forth, it pops up in random places. How do i fix that?
Here is the code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DVGCollectionViewCell *cell;
cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
if (indexPath.row == 0)
{
cell.imageView.image = [UIImage imageNamed:#"something1.png"];
cell.buyLabel.text = #"170";
cell.textLabel.text = #"11.2011";
}
return cell;
}
Cell in both UITableView and UICollectionView are recycled, that means that when one goes off screen it is put in an NSSet until you need it again. When it's need it's removed from the set ad added again at UICollectionView views hierarchy. If you do not clean the value inside the cell or set them again, the cell will show the same data when it was created.
This is made for performance reason creating cell takes more time instead of value them again.
If your problem is in layout check the layout flow object, which size did you set?
I have found the problem, once the cell contents was set it was never cleaned. So I added cleaning every cell properties as additional clause and it works fine.
You can perform any clean up necessary to prepare the view for use again if you override prepareForReuse in your custom cell implementation.
One of the answers in this SO post helped: override prepareForReuse and reset the cell to its default state. Don't forget to call super.prepareForReuse.
I noticed that UICollectionView calls collectionView:cellForItemAtIndexPath: on its data source quite a few times. For example, on each layoutSubviews all cells are "reconfigured", no matter if they were already visible or not.
When collectionView:cellForItemAtIndexPath: is called, we're expected to:
Your implementation of this method is responsible for creating,
configuring, and returning the appropriate cell for the given item.
You do this by calling the
dequeueReusableCellWithReuseIdentifier:forIndexPath: method of the
collection view and passing the reuse identifier that corresponds to
the cell type you want. That method always returns a valid cell
object. Upon receiving the cell, you should set any properties that
correspond to the data of the corresponding item, perform any
additional needed configuration, and return the cell.
The problem is that configuring a cell is not always a cheap operation, and I don't see why I should reconfigure cells that are already configured.
How can we avoid redundant configuration of the cells? Or is there something I'm not understanding correctly?
I don't think you can avoid this. The problem is that UICollectionView is so general and FlowLayout isn't the only layout. Since you are allowed to make crazy layouts, any layout change, like a layoutsubviews, could completely change the layout you want -- a grid in portrait and a triangular arrangement in landscape... The only way to know what the layout should be is to find out the location and size of each cell.
UICollection view is not cheap for lots and lots of elements.
Simply after configuring a cell just use cell.tag = 1; , then next time you can avoid reconfiguring same cell by using if(!cell.tag).
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:YourCellReuseIdentifier forIndexPath:indexPath];
if(!cell){
//Create cell ...
}
if(!cell.tag){
//Do things you have to do only once
}
else{
//DO things you have to do every time.
}
return cell;
}