I'm working with UICollectionView using PSTCollectionView
Library. I've to create a grid where user can select and deselect
images by tapping on UICollectionViewCell. I've to show checkBox like
image if cell is selected. And uncheckedBox image if cell is
deselected. I am able to select cell and display checkBox image.And
also can deselect. But when I select next cell, the previous deselected
cell also get selected and shows checkBox image. This is the method I declared in UICollectionViewCell subClass
-(void)applySelection{
if(_isSelected){
_isSelected=FALSE;
self.contentView.backgroundColor=[UIColor whiteColor];
self.selectImage.image=[UIImage imageNamed:#"unchecked_edit_image.png"];
}else{
_isSelected=TRUE;
self.contentView.backgroundColor=[UIColor whiteColor];
self.selectImage.image=[UIImage imageNamed:#"checked_edit_image.png"];
}
}
And here is my code for didSelectItemAtIndexPath and
didDeselectItemAtIndexPath
- (void)collectionView:(PSTCollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"didSelect method called");
FriendImageCell *cell = (FriendImageCell*)[imageGrid cellForItemAtIndexPath:indexPath];
[selectedImages addObject:[[list objectAtIndex:indexPath.item] objectForKey:#"thumbnail_path_150_150"]];
[cell applySelection];
}
- (void)collectionView:(PSTCollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"did deselect called");
FriendImageCell *cell = (FriendImageCell*)[imageGrid cellForItemAtIndexPath:indexPath];
[selectedImages removeObjectAtIndex:indexPath.item];
[cell setSelected:NO];
[cell applySelection];
}
Can anyone please make me understand whats wrong with my code ? Make
me correct if I'm doing anything wrong. Tried many answers on
stack-overflow but nothing worked. Any help would be appreciated.
Thanks in advance.
After days of back and forth discussion. I think I finally understand what your problem really is. You must have forgotten to set the allowsMultipleSelection to YES. So whenever a new cell is selected, your previous cells got deselected.
allowsMultipleSelection
This property controls whether multiple items can be selected simultaneously. The default value of this property is NO.
And in my previous answer, I also suggested you to make your own boolean array to keep track of the selected items. However, I just realized that you don't have to. indexPathsForSelectedItems gives you an array of selected index paths.
indexPathsForSelectedItems
An array of NSIndexPath objects, each of which corresponds to a single selected item. If there are no selected items, this method returns an empty array.
As a matter of fact, you don't even have to implement the didSelectItemAtIndexPath, and didDeselectItemAtIndexPath. By default, these two delegate methods will call setSelected: for you. Therefore, a more appropriate way to do is move your applySelection code in to setSelected.
Overwrite the setSelected: method in your custom UICollectionViewCell.
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
// Change your UI
if(_isSelected){
self.contentView.backgroundColor=[UIColor whiteColor];
self.selectImage.image=[UIImage imageNamed:#"unchecked_edit_image.png"];
}else{
self.contentView.backgroundColor=[UIColor whiteColor];
self.selectImage.image=[UIImage imageNamed:#"checked_edit_image.png"];
}
}
Related
I have been searching and reading all over but couldn't find any conclusive method to achieve what I want to and hope to find help here...
I have a UITableView which allows the user to add multiple Flavours and Percentages to a Recipe. I have implemented the method to add or delete rows of Flavours with a custom Cell / Nib and it works perfectly well.
The issue I'm facing now, is how to retrieve the values the user has provided per added row.
(Edit for Clarity: My problem is not the populating of data, but only the dynamic reading of all data so I can save it)
I do manage to get the values for the visible rows (I do understand how the Reuseidentifier and the Tableview works, per se that for memory management's sake, iOS only keeps track of the visible rows), but not the hidden ones.
I assume in theory that I have to create an Array of Cells outside of 'cellForRowAtIndexPath' which maintains all cells. But then I'm facing another conceptual problem that my custom Nib / cell doesn't show.... basically:
How can I then use / register a nib without using the dequeingidentifier
Or in General, how can I solve the overall problem to be able an read all user entered values per row
Here the code I'm using within my cellForRowAtIndexPath. As mentioned adding and remove cell works like a charm, that isn't the issue...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeFlavourTableViewCell *cell;
int section = (int)indexPath.section;
if(section==0)
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"RecipeFlavourCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I have seen some Libraries doing it (e.g. XLForm) but do not understand (also when checking their sources) how they iterate through the values and overcome this dequeuing problem...
Any help is highly appreciated
EDIT 2: here the code I'm using to iterate through the cells in order to save the data, but as said I can only iterate through the visible cells:
- (IBAction)saveRecipe:(id)sender {
NSInteger dynamicRows = [self.tableView numberOfRowsInSection:1];
for (int i=0; i<dynamicRows; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:1];
RecipeFlavourTableViewCell *cell = (RecipeFlavourTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
NSLog(cell.flavour.text);
}
}
After 2 days of searching I finally came up with a solid solution. In case someone bumps into the same problem of dynamic forms with a tableview, here the solution:
As we understand, what ever cell is created in cellForRowAtIndexPath, it only persists as long as it is displayed. As soon as you scroll and the cell disappears, it gets automatically thrown out of memory. This behaviour makes it impossible to iterate through all cells at a later stages.
The steps to follow in order to make it work are as follows:
Preparation
Create an NSObject with all properties you want to persist in one form cell (-> cellObject)
In the ViewDidLoad of your controller create a NSMutableArray which will contain the cellObjects (-cellsArray)
Add as many cellObjects to the cellsArray as you initially want to appear in the Tableview
In numberOfRowsInSection return the count of you cellsArray
In the cellForRowAtIndexPath build your cells as usual BUT add a Textfield Delegate (self) to every Textfield in a cell
TextField Delegate
Implement:
- (void)textFieldDidEndEditing:(UITextField *)textField
and update your cellsArray Objects every time a Textfield ends editing. Per se, get the cellObject for the row and edit the properties with the value of the TextField
Add Row
When ever you add a row, just add an empty cellObject to your cellsArray and use the beginUpdates / insertRowsAtIndexPaths / endUpdates on your tableView (NOT reloadData as the already typed in data would get lost). Also add the following at the very beginning of your addRow method, as you want to make sure that if the user adds a row while editing a textfield, the latter gets persisted as well:
[self.view.window endEditing: YES];
Remove Row
Same as Add Row just reverse, remove the cellObject from your cellsArray and use deleteRowsAtIndexPaths on your tableView
Save Data
Now comes the trick: since you ought to always persist your data when a field ends editing mode, there is one case you need to cover: What if the user pushes "Save" when the focus is set on one TextField? Well at the very beginning of your Save Action insert the following:
[self.view.window endEditing: YES];
This make sure the the textFieldEndEditing will be triggered one last time for the current textField and that its data will also be persisted.
Finally iterate through your cellsArray and do whatever you want with it (validate, save etc)...
That's it, hope this can help anyone else as I couldn't find any valuable explanation anywhere else...
Lets assume that you have an NSArray and that it contains data you want to show. Your code should look something like this:
// Add this property to the class and fill it in with data you want to show
#property NSArray flavourElements;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RecipeFlavourTableViewCell *cell;
int section = (int)indexPath.section;
if(section==0)
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"RecipeFlavourCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// At this point your cell is ready for showing
// And you can change values in it by getting element from array that contains data
cell.flavorTextField = flavourElements[indexPath.row].flavour
cell.precentageTextField = flavourElements[indexPath.row].precentage
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I wrote a code for accessing elements without knowing what you actually have, so you will need to adjust it a little bit to fit your app.
I'm getting some weird behavior in my UITableViewController.
I've subclassed UITableViewCell and created by own "visited" property within it.
- (void)setVisited:(BOOL)visited animated:(BOOL)animated
{
[self setVisited:visited];
...
}
I set this property when I create the cell in tableView:cellForRowAtIndexPath: (the only place I create it) like below:
if (cell == nil) {
cell = [[ArticleListViewCell alloc] initWithReuseIdentifier:CellIdentifier art:art index:indexPath.row];
[cell setVisited:NO animated:NO];
}
Later, in tableView:didSelectRowAtIndexPath:, I set this property to YES:
ArticleListViewCell *cell = (ArticleListViewCell *) [tableView cellForRowAtIndexPath:indexPath];
[cell setVisited:YES animated:NO];
However, when I select a cell and then return to this UITableView, which currently has 10 cells, I find that not only has the cell I selected become "visited", but also another cell has as well. It's hard to explain, but if I select the 1st cell, the 7th also becomes visited, if I select the 2nd, the 8th also becomes visited, and so on. Does anyone know why this is, and how I should go about fixing it?
I've checked this question, but it doesn't seem to help much.
This is caused by cell reuse. You need to set visited every time, not just when you create the cell.
if (cell == nil) {
cell = [[ArticleListViewCell alloc] initWithReuseIdentifier:CellIdentifier art:art index:indexPath.row];
}
BOOL visited = ... // value for this indexPath
[cell setVisited:visited animated:NO];
And in your didSelectRow method you need to update some sort of data structure keeping track of which row has been visited. You use this data to set the value properly in the cellForRow method.
Do not use the cell to keep track of state. The cell is a view. Your data source needs to track the state.
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 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
I have a tableview, where when the user selects the cell it will set the accessorytype to UITableViewCellAccessoryCheckmark.
Now when I navigate to the previous screen, then go forward to this tableview, my cells remains checked.
Is there a way to uncheck all of them? I guess basically set all of them to UITableViewCellAccessoryNone.
I tried using reloadData, when the view appear, but that doesn't seem to trigger the cellForRowAtIndexPath (this is where my logic is to set the accessorytype of the cells)
I think this may do what you want. I assume you want to uncheck all the cells as part of responding to tableView:didSelectRowAtIndexPath: but you could insert this code anywhere:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Uncheck any visible, checked cells.
NSArray *visibleCells = [tableView visibleCells];
for (UITableViewCell *cell in visibleCells) {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
// Now do whatever else you want in response to a row being selected.
}
You should set the accessorytype in tableView:willDisplayCell:forRowAtIndexPath: instead of cellForRowAtIndexPath so that it is updated whenever it is displayed (and not just when created).
There is a tableview method called clearsselectiononviewwillappear that you might have accidentally overwritten. The default behavior should be YES. Check out Apple's doc
on UITab