I´m loading a bunch of countries to a table and allowing the user to delete. The rows directly correspond to the countries array, by using objectAtIndex:indexPath.row and I set the delete button´s tag to the same index.
When I delete the first couple of rows it´s ok but afterwards the wrong rows are getting deleted and the app crashes.
Here's how I create rows:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
NSString *currentText;
// fill in country cell content
if ([tableView tag] == 400) {
cell = [tableView dequeueReusableCellWithIdentifier:#"SettingsCountryCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"SettingsCountryCell"];
NSLog(#"is nil");
} // it´s never nil
currentText = [arrSelectedCountries objectAtIndex:indexPath.row];
int currentRow = indexPath.row;
UILabel *countryLabel = (UILabel *)[cell viewWithTag:401];
countryLabel.text = currentText;
// create button
UIButton *countryButton = (UIButton *)[cell viewWithTag:402];
[countryButton setTag:currentRow];
[countryButton setTitle:[NSString stringWithFormat:#"row%d", currentRow]
forState:UIControlStateNormal];
NSLog(#"%# - tag %d - title %# - button tag %d",
currentText,
currentRow,
countryButton.titleLabel.text,
countryButton.tag);
// on load: Bulgaria - tag 2 - title row2 - button tag 2
// after one deletion (and ever after):
// Bulgaria - tag 1 - title (null) - button tag 0
[countryButton addTarget:self
action:#selector(removeCountry:)
forControlEvents:UIControlEventTouchUpInside];
}
Here's how I remove them (i.e. countries):
- (void)removeCountry:(UIButton *)countryButton
{
// removed country
NSString *currentText = [arrSelectedCountries objectAtIndex:countryButton.tag];
NSLog(#"removed country %# at tag %d", currentText, countryButton.tag);
// ok: removed country All CEE Countries at tag 0
// ok: removed country Bulgaria at tag 0
// not ok: removed country Czech Republic at tag 1
// add to picker selection and remove from selected
[arrCountries addObject:currentText];
[arrSelectedCountries removeObject:currentText];
// refresh picker and country table
[countryPicker reloadAllComponents];
[countryTable reloadData];
[countryTable reloadSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationFade];
// position table and elements below it
[self repositionForCountryChange];
}
At first the table is showing: row0, row1, row2, etc.
After a couple of deletions: row1, row2, row3, etc.
Once this happens, the label is no longer corresponding properly to the tag. When I click delete on Croatia, delete button labeled row 1, NSLog says:
removed country Czech Republic at tag 1
... and Croatia row is not deleted although its delete button now says row2.
And why am I getting from NSLog:
title (null) - button tag 0
...when I can see in the table that there is a title and removeCountry can access button tag, although incorrectly?
You have set up the button with a tag to allow you to identify it as a subview in the cell (402) you then change the tag so it identifies the row the tag is in. When the cell is re-used, there is no view with tag 402, so your button is still identified with a tag number of its original row.
You're using tags for two purposes. Ideally, don't use them at all as they are fragile and do not make your code readable.
create a UITableViewCell subclass and identify its subviews using outlets
identify the row of the pressed button using the method I describe here which is much more flexible than tags and keeps working if you delete rows.
Related
I've been struggling with this issue for weeks and still have not found one solution. No one really gave me an answer on SO and have not found anything that helps my issue either.
When making a change of a cell's content's color, position, whatever, other cells with the same indexPath also get modified.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object{
static NSString *simpleTableIdentifier = #"cell";
self.cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (self.cell == nil) {
self.cell = [[SearchCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
This is how I create cells. Help..!
EDIT
This is a slide-out view implemented in each cell. When you swipe to right on a cell, it shows up from the left but even though only the selected cell's content should be modified, several other cell's slide-out menu also come out.
This is how I implement slide-out menu in my custom cell class.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.optionView = [[OptionView alloc]initWithFrame:CGRectMake(0, 0, self.contentView.frame.size.width , self.contentView.frame.size.width)];
self.optionView.delegate = self;
[self.contentView addSubview: self.optionView];
OptionView' delegate - called when swiped to the right
-(void)handleGesture:(UISwipeGestureRecognizer*)shouldOpenMenu{
POPSpringAnimation *anim = [POPSpringAnimation animation];
anim.property = [POPAnimatableProperty propertyWithName:kPOPViewFrame];
anim.toValue = [NSValue valueWithCGRect:CGRectMake(-self.frame.size.width/4, 0, self.optionView0.frame.size.width, self.optionView0.frame.size.height)];
anim.springBounciness = 11;
anim.springSpeed = 5;
[self.optionView0.layer pop_addAnimation:anim forKey:#"spring0"];
First, each tableview cell has different index path.
Second, you never want to save cells at class level properties. Create them locally and return from cellForIndexPath:. Your table view will take care of the cell thereon.
Third, and very important, cells are reused. So, you should reset the cell before using it. For instance, if you are setting cell colour to red in some condition and then you move the table down and this cell gets discarded and new cell is rendered by re-using the discarded cell. You would want to reset the cell colour to clear if you are not setting cell colour for that row.
Follow these rules and you should be able to fix your issue.
I have a UITableView that displays a custom XIB cell repeated for all the data in an array I have. I also have a UIBarButton item that when pressed, adds a blank entry into that data array, which then creates a new blank cell. (Because numberOfRowsInSection returns [self.array count])
The XIB cell has a clear UITextField, so all cells are editable. The textfields, for each cell, displays that data in the array, for the appropriate index.
Now, what I'm trying to do is, when the UIBarButton item 'Save' is pressed, it gathers all of the data in the cells textfields, and basically saves it all into an array. I can't find a solution anywhere, so any help is appreciated. Thanks in advance!
EDIT: I solved my own problem. For those wondering what to do:
for (int i = 0; i < [self.dataArray count]; i++) {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
for (id subview in [cell.contentView subviews]) {
if ([subview isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField *)subview;
self.updatedArray = [NSMutableArray arrayWithObject:textField.text];
NSLog(#"Cell Text: %#", self.updatedArray);
}
}
}
You could just loop through all the cells in a table view and add the contents of the text field to an array.
The example is assuming all the cells are in section 0 but you could change it to a nested loop with the number of sections playing a factor.
var saveData = [String]();
for i in 0..<tableView.numberOfRowsInSection(0){
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: i, inSection: 0)) as! NameOfYourSubclassedCell;
saveData.append(cell.textField.text);
}
I have two UICollectionViewLayout, one for showing stacked cells and another one to show individual cells on full screen whe they're selected.
I need to animate the cell's subviews to certain positions each time the cell is selected, and then move again those subviews when the cell is deselected.
STEP 1: Cell is selected. Subview is animated from its original position to a new one. No problem here.
STEP 1: Cell is deselected. Subview is animated to another position, BUT the animation starts from the original position, not the new position I indicated in the previous step.
Is there any way to save the modified status, and start the animations from there?
UPDATE
Here's how I change layouts on cell selection (ViewController.m):
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
__weak CustomCellCollectionViewCell *cell = (CustomCellCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
// Hide single view
if ([indexPath isEqual:self.exposedItemIndexPath]) {
self.exposedItemIndexPath = nil;
[cell deselectedAnimations];
[self animateCollectionView:NO];
}
// Select single view
else if (self.exposedItemIndexPath == nil) {
self.exposedItemIndexPath = indexPath;
[cell selectedAnimations];
[self animateCollectionView:YES];
}
}
- (void)setExposedItemIndexPath:(NSIndexPath *)exposedItemIndexPath
{
if (![exposedItemIndexPath isEqual:_exposedItemIndexPath]) {
if (exposedItemIndexPath) {
self.audiosContentOffset = self.collectionView.contentOffset;
SelectedLayout *singleLayout = [[SelectedLayout alloc] initWithExposedItemIndex:exposedItemIndexPath.item];
[self.collectionView setCollectionViewLayout:singleLayout animated:YES];
}else{
self.stackedLayout.overwriteContentOffset = YES;
self.stackedLayout.contentOffset = self.audiosContentOffset;
[self.collectionView setCollectionViewLayout:self.stackedLayout animated:YES];
[self.collectionView setContentOffset:self.audiosContentOffset animated:YES];
}
_exposedItemIndexPath = exposedItemIndexPath;
}
}
selectedAnimations moves a UIView inside the cell, and deselectedAnimations moves it somewhere else when the cell is deselected. BUT, deselectedAnimations starts from the UIView's original position, not from where it was left at the end of selectedAnimations.
The cells and its subviews are based on a nib file. It seems to me that, on layout change, the cell is being rendered again following the "instructions" on the nib file, and that's why the subview is animated starting from its original position instead of where i left it on selectedAnimations.
My question is: is there any way to "save" the cell's subviews positions and start deselectedAnimations from there?
Uitableview problem again. Whenever I reload my data, the tableview would reload but it seems like the data will not start at the first row. Please refer to the image below.
I tried the following after the reload of data but still no success:
[self.tableView reloadData];
// this or the other one ... [self.tableView setContentOffset:CGPointZero animated:NO];
[self.tableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
This is how I positioned the uitableview on viewdidload
self.tableView.frame = CGRectMake(450, 20, self.tableView.frame.size.width, self.tableView.frame.size.height - 20);
EDIT 1:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"SearchResultCell";
User *user = [users objectAtIndex:indexPath.row];
UserTableCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UserTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.name.text = [NSString stringWithFormat:#"%# %#", user.firstName, user.lastName];
return cell;
}
Based on your comments, your table is being resized incorrectly when you are rotating the device to landscape mode. In your storyboard, you need to set the autoresizing on the table view in order for it to properly resize the way that you want.
Here is a good starting point for something like this:
Notice just above the Autosizing label, it shows a bunch of red bars (called springs and struts). I have them all turned on, which will maintain the distance from the edge of the superview and allow the object to change its' size in order to keep those distances the same. This should be close to what you want, and if not then you can play around with the different combinations until you get what you want.
You turn each part of it on and off by clicking on the red line.
For more details, take a look at this question: Autoresizing masks programmatically vs Interface Builder / xib / nib
In a UITableView I add a UIView as subview but ONLY for section 1. Section 1's content is loaded from a plist and the plist contains mutable content. If there are enough rows to allow scrolling, then the following happens: I scroll to the bottom, and back up, and the UITextField appears randomly on some of section 0's cells. I have no clue why this is happening! So what i do is this (in ´cellForRowAtIndexPath´):
if (indexPath.section == 0) {
//do stuff
}
else if (indexPath.section == 1) {
d = [UIView alloc] init];
[cell.contentView addSubview:d];
}
and this gets totally messed up when I scroll. The subviews appear in section 0 where they shoudnt, and on didSelectRowAtIdexPath I reload for section 1 and then subviews even appear twice (over each other)... Its a complete MESS! Please, Please help.......
Without seeing any code this seems to be an issue pertaining to reusable cells. What happens is that the cells that have scrolled off the screen are reused for the new content that is to be shown. So i reckon you need to make a distinction in cellForRowAtIndexPath for section 0 and 1 and basically use different set of cells for them.
EDIT: Ok ima give a shot to your problem here
UITableViewCell *cell;
if (indexPath.section == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellWithoutSubview"];
if (cell ==nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:#"CellWithoutSubview"] autorelease];
}
//do stuff with cell like set text or whatever
}
else if (indexPath.section == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellWithSubview"];
if (cell ==nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:#"CellWithSubview"] autorelease];
d = [[UIView alloc] init];
[cell.contentView addSubview:d];
[d release];
}
}
return cell;
So now you'll have two types of cells for the tableview that'll be reused one without the subview and one with the subview.
You must be using dequeueReusableCellWithIdentifier. The purpose of dequeueReusableCellWithIdentifier is to use less memory. If the screen can fit 4 or 5 table cells, then with reuse you only need to have 4 or 5 table cells allocated in memory even if the table has 1000 entries.
So the subviews in UITableViewCell are also cached. So when the cell is reused, you need to clean out the old view & then put in the new content.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier: #"your-id"];
if (cell)
{
//reusing old cell. reset all subviews before putting new content.
}
else
{
//Create a fresh new cell
}
You should use switch instead:
switch ( indexPath.section )
{
case 0:
{
/* do soemthing */
}
break;
case 1:
{
d = [UIView alloc] init];
[cell.contentView addSubview:d];
}
break;
}