Incorrect indexPath against UITableView after calling reloadData from textFieldDidEndEditing - objective-c

So I'm trying to emulate the way in which the standard Apple code works on the contacts app when managing telephone numbers. Specifically here, I'm working on deleting a row from a tableview if its empty and the user navigates to any other row
My problem is that as the tableview reload causes the UITextField to resign its responder, I need to set the responder again for the text field navigated to by the user
I have the UITextField delegate and am handling the usual textFieldShouldBeginEditing , textFieldDidBeginEditing , textFieldShouldEndEditing , textFieldDidEndEditing
In order to handle the functionality, my code is within textFieldDidEndEditing, whereby I remove the data from the tableview array, and as the tableview has 2 sections, I am calling:
[MyTableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
During textFieldDidBeginEditing I save the indexPath of the textField being edited using:
EditableCustomCell *textFieldCell = (EditableCustomCell *)[[textField superview] superview];
NSIndexPath *indexPath = [MyTableView indexPathForCell:(EditableCustomCell *)textFieldCell];
responderIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
I then use the following code to set the correct rows textField to be the first responder:
EditableCustomCell *customCell = (EditableCustomCell *)[MyTableView cellForRowAtIndexPath:responderIndexPath];
[customCell.editableTextField becomeFirstResponder];
Everything seems fine until near the end of the processing, when all of a sudden textFieldDidBeginEditing starts to return section 0 row 0 for the indexPath (even though when examining the tag value or text contained return the correct values for the textfield)
Below is a log from the start of the process explained above:
- textFieldDidEndEditing started <-- start of delete processing
- textFieldDidEndEditing - tableviewData - replacing object at index 1
CustomTableViewController.deleteRow - delete called for indexPath section 1 ... row 1
- reloading MyTableView
- CustomTableViewController-cellForRowAtIndexPath started
CustomTableViewController-cellForRowAtIndexPath section=1 row=0
CustomTableViewController-cellForRowAtIndexPath ending
CustomTableViewController-cellForRowAtIndexPath section=1 row=1
CustomTableViewController-cellForRowAtIndexPath ending
CustomTableViewController-cellForRowAtIndexPath section=1 row=2
CustomTableViewController-cellForRowAtIndexPath ending
- textFieldShouldBeginEditing started
indexPath for textFieldShouldBeginEditing is : section 1 row 1
- textFieldShouldBeginEditing ending
- textFieldDidBeginEditing started
indexPath for textFieldDidBeginEditing is : section 1 row 1 text 3 tag 1
- textFieldDidBeginEditing ending
- textFieldDidEndEditing ending <-- end of delete processing
- textFieldDidBeginEditing started
- textFieldDidBeginEditing ... setting responderIndexPath section 0 row 0
indexPath for textFieldDidBeginEditing is : section 0 row 0 text 123 tag 0
- textFieldDidBeginEditing ending
As can be seen from the last part of the log, after textFieldDidEndEditing completes, textFieldDidBeginEditing is called but returns section 0 and row 0 (the rows stay displayed and visible throughout)
I can neither understand why this is called, or why it is not returning the correct indexPath. As can be seen, the text is returned for the value (in this case the entered value of 123) and I have verified this with other data and other rows (for both text and tag for the textfield)
Perhaps my setting of becomeFirstReponsder within textFieldDidEndEditing is incorrect, although if that's true, I'm at a loss as to where to process this
Hoping someone out there with a better understanding of this can help me out, as you can tell, I've been through this for several hours without any resolution
Thanks
Izzy
EDIT 1: In the code, all that is called is becomeFirstReponder before textFieldDidEndEditing completes. When you look at the log after cellForRowAtIndexPath has completed, it enters and exists the textfield TWICE, once for the row that was below the row just removed, and then again when it returns 0 for the section/row of the indexPath ? I am failing to understand what sequence of events is causing this post table reload firing of methods
EDIT 2: Is it just me, or does it seem really strange that there is NO textfieldSHOULDbeginEditing prior to the textFieldDidBeginEditing at the end of the log ? Is it that I am screwing with the internal process by reloading the table during textFieldDidEndEditing ? Is there a better place to do this (ie. remove a row and reload the tableview to show the UI update) ?

OK, to answer my own question ...
After alot of debugging, it seems that ...
I'm inteferring with the standard UI workflow by reloading the tableview during textfieldDidEndEditing
Calling reloaddata destroys the tableview cells (and all objects contained within the cells ie. UITextFields, UILabels, etc) and recreates them, causing
By moving this call out to a standalone button, it functioned as expected without the indexPath returning 0 (I still dont fully understand the call stack when it gets to this point, but hey)
Now my BIG problem is deciding when to programatically call the reload, as I want it driven off the event of leaving the text field ... I feel another question coming on :\
Hope my ramblings help someone out in the future that tries to do a similar thing ...

Related

OSX NSTableView insertRowAtIndexes

i already checked Using NSTableView insertRowsAtIndexes solution but It does not solve my problem.
i want insert row in nstableview at particular index(add dynamically)
index Set is valid, still it causes Program crash
NSIndexSet *indexSet=[NSIndexSet indexSetWithIndex:i];
[myTableView insertRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectFade];
1)is any thing wrong in my code?
2)Is there any another way to add row at particular index(add dynamically)?
The code is correct but first you have to insert the model object in the data source array to keep model and view in sync.
I got My mistake..... problem In other Code so This code is fine
But I want to add some points About insertRowsAtIndexes: method
Hope it will helps to other people
1)Dont called reloadData() because you are adding particular number of rows so calling reloadData() will reload all data and it will causes crash
2) Calling this method multiple times within the same beginUpdates and endUpdates block is allowed, and changes are processed incrementally
3)Most Important thing is indexSet must be within range
if you are Enter valid indexSet then The numberOfRows in the table view is automatically increased by the count of indexes.
4)you can select animation according to your need
Sample Code :
[yourTableView beginUpdates];
NSIndexSet* theIndexSet = [NSIndexSet indexSetWithIndex:[self.yourTableContents count]-1];
[yourTableView insertRowsAtIndexes:theIndexSet withAnimation:NSTableViewAnimationEffectFade];
[yourTableView endUpdates];

How do you correctly get a row value from a table view?

I have implemented code that returns 0 every time. I'm trying to remove a string from a mutable array with the row value selected after hitting a button.
Related code:
- (IBAction)remove:(id)sender {
NSIndexPath *selectedIndexPath = [_tableView2 indexPathForSelectedRow];
[names removeObject:[NSString stringWithFormat:#"%i",selectedIndexPath.row]];
testLabel.text=[NSString stringWithFormat:#"%i",selectedIndexPath.row];
[_tableView2 reloadData];
}
The test label shows 0 every time the button is pressed, no matter where the tableview is selected.
Let me know if there is other relevant code (like the tableView) that you want me to post.
For a UITableView, the selected row concept is only valid while the user is touching the row. For that reason indexPathForSelectedRow is documented as returning “An index path identifying the row and section indexes of the selected row or nil if the index path is invalid.”
My opinion is that you are obtaining a nil result, and later calling the row method in that nil results in the zero that your are seeing as your name.
The solution depends on the rest of your data source implementation, but probably will involve storing the tapped index in didSelectRowAtIndexPath: to later use it in your remove method.
(I supposed that you are not using the allowsMultipleSelectionDuringEditing option nor are you editing the table).
Instead of:
[names removeObject:[NSString stringWithFormat:#"%i",selectedIndexPath.row]];
Try:
[name removeObjectAtIndex:selectedIndexPath.row];
Also, instead of using #"%i", try using #"%d" as mentioned here
Hope this helps!

Dynamically updating sections of UITableView without using reloadData

First off I sincerely apologize if this has been answered. I have been battling with this for too long. I would appreciate any kind of help.
I am developing an application that gets data asynchronously from different web sources and parses it into objects that are handled by my main Model. This model provides information for a UITableView. When data is received and parsed, i need to modify the number of sections and rows in the table view. I first receive the data that decides the number of sections, and then asynchronously data that modifies the number of rows in each section and their content. I have the table views data sources set up.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [self.sijainti.pysakit count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
//if ([[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count] == 0) return 1;
NSLog(#"ajatStr of section: %d count: %d",section, [[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count]);
return [[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count];
}
The object names are in Finnish (long story). The second line of numberOfRowsInSection was commented to ease debugging. Now, because the data is dynamic i need to update the interface when i have data ready for display, or the user wont know about it until they scroll away and back again. I have a method for this:
- (void)updateRowWithCell:(SCPysakki *)valmisPysakki {
if (!valmisPysakki.isInArray) return;
int section = [self.sijainti.pysakit indexOfObject:valmisPysakki];
[self.tableView beginUpdates];
NSIndexSet *sectionSet = [NSIndexSet indexSetWithIndex:section];
[self.tableView reloadSections:sectionSet withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
This is called only after the number of sections is known. I do NOT want to use reloadData due to efficiency, and animation. I used to have this set up so that i had only rows and was just drawing rows inside of the rows. But once the rows became taller than the iphone screen, it became very unclear, because you couldn't see the title. So i made them sections, and the rows drawn real rows. Then i was suddenly faced with a problem. reloadRowsAtIndexPaths: withRowAnimation: worked absolutely fine but using reloadSections: withRowAnimation: gave me this exception:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1914.84/UITableView.m:1037
2012-09-17 04:11:27.769 pysappi[2351:f803] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
I was able to eliminate this by adding the [self.tableView endUpdate] and [self.tableView endUpdate] to the update method. But oh no, that only works if I:
Use reloadData after receiving the data deciding the number of sections (which in turn shortens the tableview so that after the whole update the user at the top of the table after every single update. I would rather update the sections dynamically, without using reloadData, except when removing Sections, but if I try to do this i get the exception again.)
update the sections after receiving data for only one Row (so if I try to update all the rows in the section once I've received all their data, I get the exception again.)
Now this creates a situation where creating a good user interface becomes impossible. I'm convinced that the underlying problem is not solved with the start and end updating calls, but that just fixes it for some very very odd reason.
I also tried adding the rows manually with insertRowsAtIndexPaths: and updating them manually also, but this returns the exact same error. During a long debugging progress i have been able to determine that this exception rises after cellForRowAtIndexPath: gets called and it returns a cell for one of the new rows that was added during the updating, even though the numberOfRowsInSection should have updated the amount of rows. I also suppose that if those few circumstances apply this is why the start and end updating methods make the row updating work.
The only thing that has worked outside of what i explained above, is using only reloadData, but that is just practically impossible considering the amount of updating.
This became a lot longer than i thought, and its 4.30 AM. Not the first time I'm up this late thanks to this exception. If you need more information ask. Thank you in advance.
(Answered in the comments. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
#deleted_user wrote:
number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), - you had a section with zero rows and did an update with a row - cocoa didnt see that as an insert and failed you

reloadData calls numberOfSections, numberOfRows, not cellForRowAtIndexPath

first of all sorry if this isn't formatted correctly, first time doing this. I've been using stackoverflow to find help for a long time now and it's been very helpful (thank you all), but this is the first time I've posted a question of my own. This question has been asked many times, but when I call [myTable reloadTable] the methods numberOfSectionsInTableView and numberOfRowsInSection are called but not cellForRowAtIndexPath.
Every answer I've seen when searching has been a variation of:
1) The tableView is nil
2) numberOfRowsInSection is 0
3) tableView's delegate/data source not set
4) calling reloadTable on the wrong uiTableView.
None of these are the case for me so I'm wondering what else could be wrong.
What I'm trying to do:
I have a custom uitableviewcell with a button on it, when the button is pressed I want to change the attributes of the cell (for an example, let's just say I want to change the cell title to "Button Pressed"). I then want to pause for a moment and then delete the cell.
Not sure if this is important, but my tableView is inside a UIViewController, and the viewController is the delegate and dataSource.
I have a method (below) that fires when the button is pressed and the cell attributes are changed as necessary, however when I try to refresh the table to show the changes it doesn't work. The first time [remTable reloadData] is called, numberofsectionsintableview and numberofrowsinsection are called but not cellforrowatindexpath. However, the second time [remTable reloadData] is called all three methods are called and everything works correctly.
doneCell.remName.text=#"BUTTON PRESSED";
[remTable reloadData];
NSLog(#"sleep");
sleep(1);
[remList removeObject:doneReminder];
[remTable reloadData];
To test this I put nslog statements at the beginning of numberofsections, numberofrows and cellforrow, the output is the name of the method followed by the number (for numberofsections/numberofrows).
Output:
numberofsections 1
numberofrows 3
sleep
numberofsections 1
numberofrows 2
cellforrow
Any ideas as to why cellforrow's not being called the first time? Thanks in advance, please let me know if there's anything else I can add to clarify.
A table view doesn't actually request its cells until it needs to display them. When you call reloadData, it will immediately request the number of sections and rows so that it knows how big it needs to be. It doesn't ask for the cells themselves until it is asked to display itself. Views are automatically displayed as necessary at the beginning of each run loop.
Execution doesn't return to the run loop until your code returns. When you call sleep, you don't execute any code, but you also don't return. This means the run loop doesn't get a chance to display the table, so it never asks for any cells. What you need to do is return from your method and ask that another method gets called in 1 second to remove the doneReminder. An easy way to do this is to use the performSelector:withObject:afterDelay: method. This method adds a timer which will call the method you requested after the given delay, which means you can return to the run loop and let the table display.
doneCell.remName.text=#"BUTTON PRESSED";
[remTable reloadData];
[self performSelector:#selector(removeDoneReminder) withObject:nil afterDelay:1.0];
- (void)removeDoneReminder {
[remList removeObject:doneReminder];
[remTable reloadData];
}
If remlist and remTable are not instance variables, you can use the withObject: parameter to send them to the removeDoneReminder method.
CellForRowAtIndexPath is part of UITableView datasource protocol, make sure that remtable.datasource is point to self
so when you initialize the TableView, you should add
remtable.datasource = self;
i always did this, after
remtable.delegate = self;
Hope this help
Try using delayed performance:
[remtable performSelector:#selector(reloadData) withObject:nil afterDelay:0.1];
Don't know if it will help because I don't know what else you're doing.

indexpath as parameters

I got an table which is filled with VM's and i have 2 variable of the datatype NSIndexPath called:
selectedindexpath(the row the user clicks on) and
selectedindexpathFortheVMtoTurnOn(so it knows which row to reload when the VM went on);
which is both made in the .h as #paramters(retain) ..
i got a function that turns on the VM's and a function that CHECKS every 1 sec with a NSTimer if the guest is finally on then it reloads the row of selectedindexpathFortheVMtoTurnOn
im doing the same for OFF/REBOOT but i have got a problem with this.. im not able to request an action for more then 1 VM.. because it is overwriting the selectedindexpathFortheVMtoTurnOn value with the last row which i sent an action to and by doing so the table responds wierd and the app crashes..
so thats why i want to give the indexpath.row value as parameters with the NSTimer to the function that checks every 1 sec i have tried a few things but none have worked.. below the code of how i am giving the parameters
timertocallguest=[NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(gettheguestyouaskedforTOTURNON:)
userInfo:selectedindexpathFortheVMtoTurnOn
repeats:YES];
and the function:
-(void)gettheguestyouaskedforTOTURNON:(NSIndexPath *)therow
and when i try to do a NSLog("%d",therow.row); it crashes...
and with NSLog("%d",therow); i get a whole diffrent value of the selectedindexpathFortheVMtoTurnOn that it used to had..
what am i doing wrong.?
sorry if its a wall of text. but i realy need to fix this problem as this is getting released as BETA nextweek in the appstore for the company where my traineeperiode is.
ty in advance.
EDIT:
i will try to show it with pictures this time.
At first im at the view with VM's
http://imageshack.us/photo/my-images/194/schermafbeelding2011052l.png/
after that i can press on the button with the arrow and i get to choose what i want to do.
http://imageshack.us/photo/my-images/822/schermafbeelding2011052r.png/
an activity indicator appears and it stops when the action is done.(which has to do with the NStimer that checks every 1 sec)
http://imageshack.us/photo/my-images/828/schermafbeelding2011052y.png/
http://imageshack.us/photo/my-images/811/schermafbeelding2011052t.png/
but when i try to do an action at 2 or more VM's the activity indicator on the last row i selected spins *x faster(depends on how manny actions i send.. 2 actions means it spins 2 times faster..(so weird)
.. i hope this is enough for you to understand what i mean now =)
The method triggered by an NSTimer object always has the timer as the argument. You can get the row like this.
-(void)gettheguestyouaskedforTOTURNON:(NSTimer *)timer {
NSIndexPath *indexPath = (NSIndexPath*)[timer userInfo];
NSLog(#"%d", indexPath.row);
}