I have a method which is designed to clear all UITableViewCellAccessories and I get the following error when it is called
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible.'
Here is the method
-(void)clearTableViewAccessories{
NSIndexPath *indexPath = [NSIndexPath new];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark){
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
The way you are initialising the indexPath object, creates an index path of length 0 and no path, which is formed by a section and a row.
You should create the indexPath with the designated initialiser
indexPathForRow:inSection:
Anyway I don't think your implementation is the best way to solve your problem. With your current setup you need to create an indexPath for every cell you want to create iterating through all of them, and you will get nil for any non-visible cell.
The best thing would be to start looking at UITableView dataSource methods, first of all tableView:cellForRowAtIndexPath: and inside here make the decision whether to clean the cell or not.
In your cellForRowAtIndexPath check if the cell should have no accessory. For example, you could modify your datasource or just use an ivar BOOL noAccessory. Set the accessory accordingly. Then simply call
[self.tableView reloadData];
Check your number or rows and number of sections , I had a similar issue which I solved by correctly populating number of rows in each section( if you have rows in section ). If you update the question with your data source eg array for section, and Rows , will be easier to answer
Related
I have custom cell having switch in few cells at right side. what I want is to store value of specific cell on switch change event. Table view has number sections so I can't set tag for switch because I need section as well as row to obtain index path.
Any suggestion any alternative but I have to use UISwitch in section based table view.
Thanks
In your custom cell add properties which help you identify the information the cell represents. Index path, indexes for your data model etc...
Then add a block property to the cell which you can call to tell a UITableView or any other piece of code when a cell switch changes. e.g.
#property (nonatomic,copy) void (^onSwitchChange)(UITableViewCell *cell);
Inside your custom cell code, add an action handler for the UISwitch. When it fires, call self.onSwitchChange(self) which will notify the code which registered an onSwitchChange block that a switch has changed and on which cell.
In your table view when you create the cell, set the onSwitchChange block as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
<snip>
YourUITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:yourCellIdentifier forIndexPath:indexPath];
cell.onSwitchChange=^(UITableViewCell *cellAffected){
// Add code to deal with the swicth switch using properties of cellAffected
... Your handler code here ...
}];
<snip>
}
This lets you handle all the changes in the table view controller. Hope this helps.
The answer from #Jageen works. I had to find out which superview the cell is, mine was one more level higher.
UITableViewCell *cell = (UITableViewCell*)[sender superview].superview.superview;
NSIndexPath *indexPath = [self.myTableView indexPathForCell:cell];
NSLog(#"Section %ld Row : %ld",(long)indexPath.section,(long)indexPath.row); // print row section and index
You can still create tags even if you have sections if you have some idea of about max rows in a largest section. For example if you think there will be 1000 rows in a section then you can create tag using following formula.
tag = section * 1000 + row;
later in your IBAction of switch you can find out the indexpath (section and row) using following:
section = tag/1000;
row = tag%1000;
If you have no idea of how many rows your section will have you can find out the cell using sender.superview.superview (be careful if have added any other views in hierarchy).
Rory McKinnel's answer is still the cleanest solution for your problem.
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 have got the NSIndexPath from a uitableview before but I'm not sure why this doesn't work for collectionview? Any help would be greatly appreciated!
Here's the code I need to fix:
NSIndexPath *indexPath = [self.collectionView indexPathForSelectedRow];
sqlColumns *author = [self.favoriteExercises objectAtIndex:indexPath.row];
Just use indexPathsForSelectedItems in place of indexPathsForSelectedRow.
But this instruction will return more than one indexPath if you select more than one item. You should restrict the selection to one item by setting the allowsMultipleSelection of the UICollectionView to NO.
That said, Xcode has a brilliant autocompletion feature, you could have started to type "index" and it would have shown indexPathsForSelectedItems directly, you can also refer to the excellent documentation provided with Xcode. I Hope it will help you!
UPDATE 1 - How to grab the indexPath from the array
the indexPathsForSelectedItems method will return 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.
Now to access this object, you have to ask yourself, how do I access an array? You do some research and then you will come to this conclusion:
NSArray *arrayOfIndexPaths = [self.collectionView indexPathsForSelectedItems];
NSIndexPath *indexPathImInterestedIn = [arrayOfIndexPaths firstObject];
//Voila
There is no method indexPathForSelectedRow: in collection views because collection views are not limited to rows. You can have rows, columns, circles, spirals, rows AND columns, or an almost limitless list of different ways to arrange cells.
Do a search in the UICollectionView class reference for methods who's names begin with "indexPath".
The closest match is probably:
indexPathsForSelectedItems:
That method works for single or multiple selections. It returns an array of indexPaths instead of a single indexPath (much like the table view method indexPathsForSelectedRows for table views that support multiple selections).
There are also methods indexPathForCell:, indexPathForItemAtPoint:, and indexPathsForVisibleItems.
Gents,
I need some help here. It's almost like I know what I'm trying to do but I've been unable to get it work when coding it out. The scenario is I have a view controller with a table view in it. My table view has three different dynamic prototypes, since I have three different kinds of cells. Now, all I want to do, is to specify which prototype cell to generate in which row. I have also given each of the prototype cells unique identifiers in storyboard.
The way I understand things is that my cellforRowatIndexpath methods needs to understand which kind of cell to display in that row, and then select the identifier as such, dequeue and set the content as required.
Correspondingly, This is my code what I'm trying to do:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath * )indexPath
{
if(indexPath.row==0)
{
static NSString *CellIdentifier = #"HelloCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// configure your cell here...
return cell;
}
}
Unfortunately, things don't work as planned and my app crashed giving me a
2013-02-07 16:47:39.859 ProjectsABC30068:c07] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSIndexPath setTableViewStyle:]: unrecognized selector sent to instance 0x83812e0' error.
There are no issues with the table view as such since I've set the datasource and delegate properly. And also made an outlet for the table view.
I've also tried to change the if statement in cellforRowAtIndexPath to if(myTable.indexPathForSelectedRow==0). This produces the dynamic prototype in all the cells but at least the app doesn't cease.
What do you guys think is the problem?
I don't know how to correctly use index path, if someone could help me with that, I would appreciate it.
With Storyboard and Dynamic prototype cells, you don't have to worry about or check if dequeueReusableCellWithIdentifier: returns a nil cell.
What you have to do:
For each row, determine the correct cell identifier and feed that to dequeueReusableCellWithIdentifier:
For each dynamic prototype cell, make sure you setup the correct identifier (select each cell on the storyboard, then in Attributes Inspector under Table View Cell section fill Identifier)
Let's have an example. Say you have three prototype cells, with identifier as 'Cell1', 'Cell2' and 'Cell3'. And assume you have three rows, each to display with one of the prototype cells.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"Cell%d", indexPath.row + 1];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.textLabel.text = identifier; // just show the identifier as title
return cell;
}
You said it's reporting:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSIndexPath setTableViewStyle:]: unrecognized selector sent to instance 0x83812e0
You get this when cellForRowAtIndexPath is not returning a valid UITableViewCell.
What value did you return from tableView:numberOfRowsInSection:? The reason I ask is that your cellForRowAtIndexPath will fail for any row except the first one. If your tableView:numberOfRowsInSection: indicated a value greater than 1, then your app will crash.
To confirm this, you might want to examine what indexPath is at the the start of your cellForRowAtIndexPath, by adding this line at the start of the method:
NSLog(#"%s: section = %d; row = %d", __FUNCTION__, indexPath.section, indexPath.row);
That way you can make sure what rows you're trying to generate a cell for and that you're correctly handling it.
Later you say:
I've also tried to change the if statement in cellforRowAtIndexPath to if(myTable.indexPathForSelectedRow==0). This produces the dynamic prototype in all the cells but at least the app doesn't cease.
That definitely isn't going to work and it implies a confusion of how cellForRowAtIndexPath works. It has nothing to do with currently selected cells. You respond to numberOfSectionsInTableView and tableView:numberOfRowsInSection: by telling it which cells your are able to create, and iOS will then call cellForRowAtIndexPath repeatedly for each of the cells it determines might be visible given the number of sections and number of rows.
Make sure that your numberOfSectionsInTableView and tableView:numberOfRowsInSection: methods only return values that you correctly handle in your cellForRowAtIndexPath.
For further reading, I'd suggest you go through the Table View Programming Guide for iOS or google "UITableView tutorial".
(This was a little long to be left as a comment so I've put it here even if it doesn't directly answer the question)
Exception BreakPoint :
Go to breakpoint navigator
At the bottom of the navigator you have '+' button
Then it will add a breakpoint for you to customize, just click done and you are set.
Now when an exception is thrown you will be brought to the line that have trigger the exception. And most importantly you will be able to see your whole call stack of that moment. Very useful for debugging.
There is a lot more 'funky' stuff that can be done with breakpoint in Xcode since 4.2 (not sure exactly when)
Which number is the first object in a NSArray? I originally thought it was 0 but I may be wrong.
Also which number is the first cell in a UITableView? I thought the first indexpath.row was 1 but I also may be wrong?
Are there any links anyone can point me to, where Apple explains this?
Thanks!
-(BOOL) textFieldShouldReturn:(UITextField *)thetextField {
CustomCell *cell = (CustomCell*)[thetextField superview];
NSIndexPath *indexPath = [thetableView indexPathForCell:cell];
NSLog(#"%i", indexPath.row);
Index 0 represents the first element in NSArray.
As for UITableView, you have both a row and section number in an index path. So the first element of the first section is when indexPath.row = 0 and indexPath.section = 0.
In any array, the first object is at index 0. In case of a tableView, it is basically populated with an array, and the first object, which is indexPath.row == 0 denotes first row.
So the answer to both = 0 !
The first object should be 0 as objc is kind of C.
As for the first object of UITableView,it depends on your datasource. If your application has only a section, the datasource usually is a NSArray. Then the first cell corresponding to the datasource has the 0 index (we usually get the index through
-[NSIndexPath row].
If you have several sections, the the datasource usually is a NSArray of NSArray. Then the first cell corresponding to the datasource is the first object in the first NSArray in the datasource.
You should read the UITableView Programming Guide.
You are assuming that you get a cell with this:
CustomCell *cell = (CustomCell*)[thetextField superview];
Is that assumption correct?