I have a TableView "cannedTv". Each cell contains another TableView "valuesTv". The structure of datasource for cannedTv is { NSString *name, NSArray *valuesArr }. valuesArr is set as datasource for valuesTv. cannedTv is an expandable tableview. Initially just name is displayed, when expanded the valuesTv tableView is displayed. This is my code for didSelectRowAtIndexPath and cellForRowAtIndexPath delegate methods of the tableviews.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"ROW Selected...TAG = %d", tableView.tag);
selectedValueIndex = -1;
if (tableView == self.cannedTV) {
// USer taps expanded row
if (selectedIndex == indexPath.row) {
selectedIndex = -1;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath ] withRowAnimation:UITableViewRowAnimationFade];
return;
}
// USer taps differnt row
if (selectedIndex != -1) {
NSIndexPath *prevPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevPath] withRowAnimation:UITableViewRowAnimationFade];
}
// User taps new row with none expanded
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
return;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table View TAG = %d ROW = %d", tableView.tag, indexPath.row);
if (tableView.tag == 10) { // cell.valuesTv
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesArray objectAtIndex:indexPath.row];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(valueTextViewTapped)];
[ccell.valueTextView addGestureRecognizer:gestureRecognizer];
//[ccell.valueTextView sizeToFit];
return ccell;
} else {
// cannedTv
static NSString *cellIdentifier = #"CannedCell";
cell = (CannedCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Set Datasource & Delegate for valuesTv tableview
cell.valuesTv.dataSource = self;
cell.valuesTv.delegate = self;
if (selectedIndex == indexPath.row) {
// Do expanded cell stuff
[cell.tapInsertLbl setFont:[UIFont fontWithName:#"OpenSans" size:8.0]];
cell.tapInsertLbl.hidden = FALSE;
[cell.contentView setBackgroundColor:[UIColor lightGrayColor]];
[cell.contentView.layer setBorderColor: (__bridge CGColorRef)([UIColor redColor])];
[cell.contentView.layer setBorderWidth:1.0f];
} else {
// Do closed cell stuff
cell.tapInsertLbl.hidden = TRUE;
cell.contentView.backgroundColor = [UIColor whiteColor];
}
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name;
// Set the valuesArray so it be used in populating the valuesTv
valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
ci = nil;
} else if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.tagLbl.text = [searchResults objectAtIndex:indexPath.row];
}
[cell.tagLbl sizeToFit];
return cell;
}
}
The problem I am facing is, every time the right datasource is shown shown in valuesTv. At this moment I have 2 arrays in valuesArray with 0th element having 1 object and 1st element having 3 objects (of NSString). Datasource of cannedTv also has 2 rows in it. Sometimes, one time both rows of canndTv shows correct datasource and then both shows same datasource. Sometimes, both rows shows the same datasource. I don't understand why valuesArray can't get the right datasource. While debugging also, I found that at times valuesArray has right datasource but the tableview is showing wrongly, at times the control doesn't go to // Populates valuesTv line and thus previously set valuesArray is only shown. I tried many ways, but can't get the results as expected. Also tried to set valuesArray in didSelectRowAtIndexPath after setting selectedIndex, but that also didn't help.
I am stuck on this since yesterday and can't get thru. Where am I going wrong due to which correct datasource is not shown/reflected on the tableviews. Can you please try to help me out. Any help is highly appreciated. Thanks a lot.
UPDATE :-
Created new datasource & delegate in CannedCell object itself - it contains the valuesTv.
#interface CannedCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property NSArray *valuesDataSource; // Contains the valuesArray contents
In my VC, removed datasource for cell.values.datasource & cell.valuesTv.delegate lines. In the cellForRowAtIndexPath method for top table, set the datasource like this :-
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name; //[tagsArray objectAtIndex:indexPath.row];
//valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
cell.valuesDataSource = ci.valuesArr; // SET DATASOURCE
// This is setting proper array always
ci = nil;
}
Commented the whole if (tableView.tag == 10) { in cellForRowAtIndexPath method.
And in CannedCell :-
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (valuesDataSource != nil)
return valuesDataSource.count;
else
return 0;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesDataSource objectAtIndex:indexPath.row];
ccell.valueTextView.tag = indexPath.row;
return ccell;
}
Yet the results are as earlier only. I mean even if array of 3 objects are set as valuedDataSource, the sub-table shows only single object array. What do the think now can be the reason for this ?
As suggested by Paulw11, I created other object to handle TableViewDelegate and datasource for the sub-table. Though that didn't make any difference in the results of the problem.
I set UITableViewDelegate & Datasource in my top table's Cell class. Passed the array to be the datasource of the table.
Importantly, after setting the datasource of sub-table on calling
[cell.valuesTv reloadData]
forced the sub-table to reload its data and that finally did the trick. Till then things weren't working.
Related
I've got a tableview that allows users to add an item (a row) to an invoice (the tableview) when an existing row is tapped. That said, I can't seem to add an empty row because my code is trying to set the information in the cell with data from my specified array, but naturally, the count in the array is different from my data source (as I want the count to be +1).
E.g. I want to return 3 cells even if there are only 2 dictionaries in my array, and the third cell should be empty.
I want this because the third cell allows my user to fill out empty fields, while the fields in the previous two rows are populated with their already input data. Here's how I'm trying to return the extra row right now, but as mentioned above, it crashes my app due to the imbalance of dictionaries returned in my array.
Here's my code so far:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.allItems = [[NSMutableArray alloc] init];
self.itemDetails = [[NSMutableDictionary alloc] init];
}
//TableView delegates
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.allItems.count + 1;
}
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
static NSString *ClientTableIdentifier = #"InvoiceDetailsTableViewCell";
InvoiceDetailsTableViewCell *cell = (InvoiceDetailsTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:ClientTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"InvoiceDetailsTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
if (self.allItems.count == 0) {
} else {
cell.itemName.text = [self.allItems valueForKey:#"Item Name"][indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
InvoiceDetailsTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *itemTitle = cell.itemName.text;
NSString *itemDescrip = cell.itemDescrip.text;
NSString *itemCost = cell.itemCost.text;
NSString *itemTax = cell.itemTax.text;
[self.itemDetails setValue:itemTitle forKey:#"Item Name"];
[self.itemDetails setValue:itemDescrip forKey:#"Item Description"];
[self.itemDetails setValue:itemCost forKey:#"Item Cost"];
[self.itemDetails setValue:itemTax forKey:#"Item Tax Rate"];
[self.allItems addObject:self.itemDetails];
[self.tableView reloadData];
}
One significant problem is the line that says:
cell.itemName.text = [self.allItems valueForKey:#"Item Name"][indexPath.row];
Since your row count exceeds the number of items in your array, you will want to check the row number before accessing the array:
NSInteger row = indexPath.row;
if (row < self.allItems.count) {
cell.itemName.text = self.allItems[row][#"Item Name"]; // personally, I’d get row first, and then keyed value second
} else {
cell.itemName.text = #"";
}
You want to check to make sure that the current row is not the last (blank) row.
My Custom tableview cell content getting empty after scrolling.So pls help me with this.
My custom cell has 8 buttons and a label.First I'm showing only label that has title and on selection I'm expanding the cell and showing all buttons.So, when I select few buttons and I do scrolling,buttons that I selected getting refreshed or get back to normal state.Here is the code.Pls help me with this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"newFilterCell";
newFilterCell *cell = (newFilterCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"newFilterCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[cell.contentView.superview setClipsToBounds:YES];
}
cell.QuestionLabel.text=[orderArray objectAtIndex:indexPath.row];
NSArray * arr = [filterInfo objectForKey:[orderArray objectAtIndex:indexPath.row]];
int val = 0;
NSLog(#"%#",cell.subviews);
NSArray * cellViews = cell.contentView.subviews;
if (arr.count>0)
{
for (int i=1; i<=8; i++) {
if (i<=arr.count) {
UIButton * target = (UIButton*)[cell viewWithTag:i];;
[target setTitle:[arr objectAtIndex:i-1]forState:UIControlStateNormal];
[target addTarget:self action:#selector(selectButton:) forControlEvents:UIControlEventTouchUpInside];
}
else
{
[[cell viewWithTag:i] setHidden:YES];
}
}
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
On selection of my cell I'm expanding and reloading the tableview cells.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath==_expandIndexPath) {
_expandIndexPath = nil;
NSMutableArray *modifiedRows = [NSMutableArray array];
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationLeft];
}
else
{
selectedRow=indexPath.row;
NSMutableArray *modifiedRows = [NSMutableArray array];
[tableView deselectRowAtIndexPath:indexPath animated:TRUE];
_expandIndexPath = indexPath;
[modifiedRows addObject:indexPath];
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationLeft];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSArray * arr = [filterInfo objectForKey:[orderArray objectAtIndex:indexPath.row]];
if ([indexPath isEqual:_expandIndexPath])
{
if ([[orderArray objectAtIndex:indexPath.row]isEqualToString:#"Height"]||[[orderArray objectAtIndex:indexPath.row]isEqualToString:#"Age"])
{
return 275;
}
else
{
if(arr.count==3)
return 55*arr.count;
else
return 37*arr.count;
}
}
else
return 70;
}
UITableViewCells are getting recycled. That's why its not safe to do it your way. Your data model needs to remember your buttons and other stuff that changed. You need to apply the changes every time the cell gets created.
You need to check in the cellForRowAtIndexPath what button is pressed and then show the view correctly.
You need to remember what happend in the cells with an external data source to apply the changes you want.
In your cellForRowAtIndexPath should be something like a check for a boolean whether or not you show some stuff:
if(button_is_pressed_for_row_5 == true){
button_in_cell.hidden = true;
}else{
button_in_cell.hidden = true;
}
I am trying to keep the selected state of multiple cells on a didSelectRowAtIndexPath method. I have an edit button that I've set up that loops through every cell to select each field on my UITableView.
Here is the code for the edit button on tap that selects all my rows.
- (IBAction)editButtonTapped:(id)sender {
for (int i = 0; i < self.caseDataTableView.numberOfSections; i++) {
for (NSInteger r = 0; r < [self.caseDataTableView numberOfRowsInSection:i]; r++) {
[self tableView:caseDataTableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:r inSection:i]];
}
}
}
When calling the didSelectRowAtIndexPath method, it does the following code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
OKOperatieNoteTableViewCell *cell = (OKOperatieNoteTableViewCell *)[self.caseDataTableView cellForRowAtIndexPath:indexPath];
cell.cellIndexPath = indexPath;
[cell hideLabelAndShowButtons];}
Incase you were wondering here is the hideLabelAndShowButtons method.
- (void)hideLabelAndShowButtons {
self.caseDataKeyLabel.hidden = NO;
if (!self.disabled) {
self.caseDataValueLabel.hidden = YES;
self.textField.hidden = NO;
if ([self.inputType isEqualToString:#"switcher"] || [self.inputType isEqualToString:#"multiselect"] || [self.inputType isEqualToString:#"picker"] || [self.inputType isEqualToString:#"DatePicker"] || [self.inputType isEqualToString:#"selectContact"]) {
self.button.hidden = NO;
}else {
self.button.hidden = YES;
}
}
self.caseDataDescriptionTextView.hidden = YES;}
Now at this point, I have all my rows selected. If I scroll down and then back up the selection of these rows is not there anymore. Now I'm aware when you go in and out of the view, the cellForRowAtIndexPath method recreates these cells. The following is my cellForRowAtIndexPath method.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"caseData";
OKOperatieNoteTableViewCell * cell = [[OKOperatieNoteTableViewCell alloc]init];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (indexPath.row < _procedureVariables.count) {
if ([[[_caseDataArray objectAtIndex:indexPath.row] valueForKey:#"key"] isEqualToString:#"Procedure"]) {
[cell setLabelsWithKey:[[_caseDataArray objectAtIndex:indexPath.row] valueForKey:#"key"] AndValue:[self.model valueForKey:#"var_procedureName"]];
}else {
[cell setLabelsWithKey:[[_caseDataArray objectAtIndex:indexPath.row] valueForKey:#"key"] AndValue:[[_caseDataArray objectAtIndex:indexPath.row] valueForKey:#"value"]];
}
OKProcedureTemplateVariablesModel *variableModel = _procedureVariables[indexPath.row];
cell.variable = variableModel.value;
[cell showLabelAndHideButtons];
cell.delegate = self;
[cell setUpCellType];
} else if (indexPath.row == _procedureVariables.count) {
NSString *text = [NSString stringWithFormat:#"%# \n\n %#", [_templateDictionary objectForKey:#"indicationText"], [_templateDictionary objectForKey:#"procedureText"] ];
[cell showDescription:text];
NSLog(#"cell.caseDataDescriptionTextView.font.fontName = %#", cell.caseDataDescriptionTextView.font.fontName);
}
cell.procedureID = _procedureID;
[tableView setContentInset:UIEdgeInsetsMake(1.0, 0.0, 0.0, 0.0)];
return cell;
}
I'm just trying to figure out how to keep the selected state of these cells once the cellForRowAtIndexPath method is called. Any suggestions are welcomed.
i tried to simulate your situation, created a customCell and saved the indexpaths of selectedRows in my custom selectedPaths mutable array(initialized in viewDidLoad).
After every click i removed or added related indexpath to my array.
it worked for my case. Hope it helps.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"caseData";
NOTableViewCell *cell = (NOTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
NSLog(#"new cell created for row %d", (int)indexPath.row);
cell = [[[NSBundle mainBundle] loadNibNamed:#"NOTableViewCell" owner:self options:nil] objectAtIndex:0];
}
if ([selectedPaths indexOfObject:indexPath] != NSNotFound) // this cell is in selected state.
{
[cell.textLabel setText:#"This cell selected"];//selected state job.
return cell;
}
[cell.textLabel setText:[NSString stringWithFormat:#"%d", (int)indexPath.row]];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([selectedPaths indexOfObject:indexPath] != NSNotFound) {
[selectedPaths removeObject:indexPath];
}
else{
[selectedPaths addObject:indexPath];
}
//[tableView reloadData];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];//instead of reloading all just reload clicked cell.
}
You need to update the cell to selected and not selected explicitly in both directions in cellForRowAtIndexPath.
If not, the recycled cells will just show the value of the cell the cell was last used for until you change it.
While you are invoking the delegate method in order to call hideLabelAndShowButtons, you aren't telling the table view that you have selected the row;
- (IBAction)editButtonTapped:(id)sender {
for (int i = 0; i < self.caseDataTableView.numberOfSections; i++) {
for (NSInteger r = 0; r < [self.caseDataTableView numberOfRowsInSection:i]; r++) {
NSIndexPath *path=[NSIndexPath indexPathForRow:r inSection:i];
[caseDataTableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
[self tableView:caseDataTableView didSelectRowAtIndexPath:path];
}
}
}
Also, you aren't using the cell selection state in cellForRowAtIndexPath, so you probably need to change some code there too, but I am not sure what the relationship is between selected state and how you want to render the cell.
I have a TableView that I would like to add a checkmark to a row when it is tapped. For that, I have:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selectedCountry = [self.files objectAtIndex:indexPath.row];
NSString *Documents = [[NSBundle mainBundle] pathForResource:selectedCountry ofType:#"ppt" inDirectory:#"thepowerpoints"];
//NSLog(#"%#", selectedCountry);
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
if (newCell.accessoryType == UITableViewCellAccessoryNone) {
if (self.chosen == nil) {
self.chosen = [[NSMutableArray alloc] init];
}
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.chosen addObject:Documents];
NSLog(#"%#", self.chosen);
}else {
newCell.accessoryType = UITableViewCellAccessoryNone;
[self.chosen removeObject:Documents];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I understand that this is happening due to the dequeueReusableCellWithIdentifier on the type of Cell. My question is, what can I change, so that only the tapped rows get the checkmark, and not all of these other cells, once they start getting reused?
Move the code that decides whether a cell should be checked into cellForRowAtIndexPath. Keep a reference to the currently selected index/item and then set the checkmark on that cell. You would set unselected cells to have the accessory view set to none.
I have a UITableView which has expandable custom cells. Multiple cells can be expanded. For that I have an array expandedArray in which I store the indexPath of all cells that are expanded. Initially all cells are collapsed. I want in initial start all cells to be expanded. This is my cellForRowAtIndexPath code :-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// VISITORS LISTS TV
NSUInteger count = 0;
VisitorsListsCell *vcell = (VisitorsListsCell *) [tableView dequeueReusableCellWithIdentifier:#"VisitorsListsCell"];
if (vcell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"VisitorsListsCell" owner:self options:nil];
vcell = [nib objectAtIndex:0];
}
// Button - Round Corners
[vcell button].layer.cornerRadius = 5;
[vcell button].contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
NSString *text = [visistorsList objectAtIndex:indexPath.row];
// SET PROPERTIES OF CELL
// ....
if ([expandedArray containsObject:indexPath]) {
// Do expanded cell stuff
[vcell setButtonText:[visistorsList objectAtIndex:indexPath.row] withCount:(int)count isExpanded:YES];
[vcell.button setImage:[UIImage imageNamed:#"arrowup.png"] forState:UIControlStateNormal ];
} else {
[vcell setButtonText:[visistorsList objectAtIndex:indexPath.row] withCount:(int)count isExpanded:NO];
[vcell.button setImage:[UIImage imageNamed:#"arrowdown.png"] forState:UIControlStateNormal ];
}
[[vcell listsTableView] reloadData];
return vcell;
}
I want is when the page is loaded, all cells are expanded. I believe for that I need to add indexPath of each cell to expandedArray. But on initial run only, how do I add the indexPath ??? I can't find a way to implement it.
Is it possible, any clue. OR any other way to achieve the goal. Any help is highly appreciated. Thanks.
To add indexPath to your expanded array you should use code below. But much simpler way is to use the same methods that are used in -numberOfSections and numberOfRowsInSection:. Using tableView delegates is not a good way.
- (void)viewDidLoad {
self.expandedArray = [NSMutableArray new];
for (int itSection=0; itSection<[self.tableView numberOfSections]; itSection++) {
for (int itRow=0; itRow<[self.tableView numberOfRowsInSection:itSection]; itRow++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:itRow inSection:itSection];
[self.expandedArray addObject:indexPath];
}
}
}
Btw: the correct way to implement cell from xib:
- (void)viewDidLoad {
....
UINib *nib = [UINib nibWithNibName:NSStringFromClass([VisitorsListsCell class]) bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"VisitorsListsCell"];
}