I need to detect when a tableview has finished reloading data. There was an older solution where you could subclass the tableview then overload the reloadData method, however apparently that no longer works because tables are handled on multiple threads now and reloadData is called before cellForRowAtIndexPath.
My question is, has there been any solution to this problem since the change?
My problem is I am losing the pointer to a textField when the table reloads its data, so the first responder I am trying to set to the next text field (to auto focus on the next data input field), is lost.
This is essentially a repeat of #wain 's answer, but I thought I would add a little code.
You can keep a reference to the index path of the cell that owns the active text field (as a property).
Then, in cellForRowAtIndexPath: something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//I would hold a reference to the text field as a property on a subclass of UITableViewCell so that you can check for whether it exists.
if (!cell.textField) {
cell.textField = [[UITextField alloc] initWithFrame:cell.contentView.frame];
[cell.contentView addSubview:cell.textField];
}
return cell;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *)textField.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
self.indexPathForActiveTextField = indexPath;
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField
{
MyTableViewCell *cell = (MyTableViewCell *)textField.superview.superview;
NSIndexPath *ip = [self.tableView indexPathForCell:cell];
[self.tableView reloadData];
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:ip.row+1 inSection:ip.section];
MyTableViewCell *theNewCell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:nextIndexPath];
if (theNewCell) {
[theNewCell.textField becomeFirstResponder];
}
return YES;
}
Store the NSIndexPath of the table cell that holds the text field that should be the first responder. When you want to change the first responder you can ask the table view for the cell at that index path, then find the text field and make it first responder.
If the table view gets reloaded, in cellForRowAtIndexPath: check the index path and make the 'new' text field the first responder.
In this way you can set the first responder at any time and you can't loose the reference to it as the reference is to a location, not an object (which will be reused or removed).
UITableView uses a pool to reuse displayed cell. The target cell is possibly reused in other row. Storing the NSIndexPath like Wain suggested is good untill you not reorder the cells or delete some entry from the datasource. Define a key in the model, that you set the firstResponder according to that. Hope i did not misunderstood the problem.
Related
I know that dequeueReusableCellWithIdentifier:forIndexPath is called by tableView method inside the tableviewcontroller. And if I understand correctly, tableView method is called several times until all the cells are populated. But what I don't know is where do you get the value for the parameter IndexPath? I want to use dequeueReusableCellWithIdentifier:forIndexPath for a method that I created because I want to access my cell to copy some values of its properties.
NOTE:
I already populated my cell, which means that I successfully used the method tableView.
(Edit) ADDITIONAL INFO:
I'm trying to create a profile and edit profile tableviews. Inside the profile tableview, I displayed the name, address, contact#, etc., of the user. Also, I have a segue called edit profile. In the edit profile, I have textfields for each category (name, address, etc.). What I want to do is, if I edit the contents of the textfields, I should be able to display the new contents in my profile tableview. An example case would be: in the profile view I'm displaying -> name:human, address:earth (each in its own cell). Now if I go to editprofile tableview, I will edit the contents such that -> name:alien, address:mars. After that, there is a button called 'apply' to end editing of contents and go back to profile tableview. If I go back to profile view, the display should now be name:alien, address:mars and not name:human, address:earth.
Here is some code if it's any help. The code is called by a button in tableviewcontroller. "MyCell" is the class of my cell. This code is not working properly. I hope someone can help me fix this.
- (IBAction)updateCopies:(id)sender {
static NSString *ident = #"MyCell";
NSIndexPath *indexPath;
//create cell
MyCell *cell = [self.tableView dequeueReusableCellWithIdentifier:ident forIndexPath:indexPath];
//create variable for accessing cells
int row = [indexPath row];
_labelValues[row] = cell.textField.text
}
You should only use dequeueReusableCellWithIdentifier when you need to supply the table view with a cell to display. If you want to get the UITableViewCell object at a certain index, you should use cellForRowAtIndexPath.
Your problem
What you really need is a model class. You can then pass this to the edit controller, which changes the properties. Then when you return to the tableView, you can reload it and display the new properties.
What you could also do is create a delegate protocol for your edit profile controller, something like EditProfileViewControllerDelegate with something like:
protocol EditProfileViewControllerDelegate {
- (void)editProfileViewController:(EditProfileViewController *)controller didUpdateName:(NSString *)name address:(NSString *)address;
}
You can implement this delegate in your table view controller and use it to update the values when the text is changed. However, this quickly becomes unwieldy, I would not recommend it over using a proper model class.
You can get indexPath using CGPoint..You can use dequeueResusableCell for reusability of the cell..
- (IBAction)updateCopies:(id)sender {
CGPoint position = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:position];
//create variable for accessing cells
MyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
int row = [indexPath row];
_labelValues[row] = cell.textField.text
}
Hope it helps you..
Use this
- (IBAction)updateCopies:(id)sender {
MyCell *parentCell = (MyCell *)sender.superview;
while (![parentCell isKindOfClass:[MyCell class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentCell = parentCell.superview;
}
UIView *parentView = parentCell.superview;
while (![parentView isKindOfClass:[UITableView class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentView = parentView.superview;
}
UITableView *tableView = (UITableView *)parentView;
NSIndexPath *indexPath = [tableView indexPathForCell:(MyCell *)parentCell];
NSLog(#"indexPath = %#", indexPath);
}
Well I got what you want to accomplish.
Firstly, there is a delegate which is being called when you click/select a cell and go to the Edit Profile page. That delegate is
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
///
}
Make a global variable, say selectedIndexPath which holds the current cell index path which is being edited. Update this value each time when you go to edit profile page.
Like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectedIndexPath = indexPath;
// code to go to edit page...
}
Now in your updateCopies Method, do like this
- (IBAction)updateCopies:(id)sender {
//get the existing cell with the indexPath
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[selectedIndexPath]];
[self.tableView endUpdates];
//rest of your code goes here...
}
I have a custom UITableViewCell with a UITextField (which is linked to the custom cells class). I am trying to access the textField from my VC class.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
menuCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if ([indexpath row] == 2) {
menuCell.nameTextField.delegate = self;
}
return cell;
}
-(void) textFieldDidEndEditing:(UITextField*) textfield
{
}
How do I get the textFields text from textFieldDidEndEditing?
Depending on where you want to access this text depends on how difficult it is.
Want to access the text in cellForRowAtIndex - (very easy)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
menuCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if ([indexpath row] == 2) {
menuCell.nameTextField.delegate = self;
}
NSString * text = menuCell.nameTextField.text;
return cell;
If you want to access the text anywhere in the VC and the menuCell is unique (there is only one of them) - (medium difficult)
In your header file add the custom cell as a class
#class menuCell;
This means you can set it a variable in the interface
menuCell * _menuCell;
Next in cellForRowAtIndex you want to allocate this custom cell
- (UITableViewCell *)tableView:(UITableView *)tableView_ cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == bCustomCellSection) {
if (!_menuCell) {
_menuCell = [tableView_ dequeueReusableCellWithIdentifier:bProfileNameCell];
_menuCell.nameTextField.delegate = self;
}
_menuCell.nameTextField.placeholder = #"Name";
_menuCell.selectionStyle = UITableViewCellSelectionStyleNone;
return _menuCell;
}
...
}
This means that we now have access to the menu cell from anywhere in the VC and can get the text by calling
_menuCell.nameTextField.text
Multiple custom cells with multiple textfields - (tough)
I have never done this but would probably do it one of two ways
a) Create an array and as we are creating the custom cells add a pointer to the textFields to the array each time. We can then access the textField we want from that array
For this method I would add the custom cells to a mutable array defined in the interface
NSMutableArray * cellsArray;
remember to initialise it in viewDidLoad
cellsArray = [NSMutableArray new];
Then in cellForRowAtIndex i would add the cell each time
menuCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
[cellsArray addObject: menuCell];
This obviously depends on how many sections we have. If we have more than one section it gets more complicated again:
Then we would need to add an array for each section to an overall array. This is quite complicated and could have a whole question on its own, there is a good link of how to do this here:
Once you have an array of cells (or an array of arrays of cells) you can call the cell you want based on the indexPath and get the textField
b) Call a pointer to the specific cell we want
menuCell * menuCell = [self tableView:table cellForRowAtIndexPath:indexPath];
and then get the textField from this cell as we did previously.
Remember you can calculate your own indexPath if you want to create one outside of cellForRow:
NSIndexPath * indexPath = [self.tableView indexPathForCell:cell];
This method is pretty good if you want to access a specific cell but a bit cumbersome if you want to access it a lot and keep having to call this code all over your VC
Hope this helps
If you are asking how to get the text from the delegate method textFieldDidEndEditing, then you simply do this:
-(void) textFieldDidEndEditing:(UITextField*) textfield
{
NSString *textFieldText = textfield.text;
}
However, if you have multiple textFields and you want to know what textfield is calling the delegate, you could tag your textField:
[myTextField setTag:indexPath.row]
and then put a if statement in the delegate textFieldDidEndEditing like this:
-(void) textFieldDidEndEditing:(UITextField*) textfield
{
if(textfield.tag == index0) do something..
else if(textfield.tag == index1) do something..
}
What I have
1). Container has UITableView, which has two custom UITableViewCells.
2). Core Data has certain entity which has a text to be displayed at
UITableViewCell each time I get into the View.
What i am doing ?
1) I have chosen -viewWillAppear method which gets invoked each time the view is visible.
2) In -viewWillAppear, I retrieved the data from core data.
3) Retrieved particular cell from UITableView
NSUInteger idxArr[] ={2,0}; // 2 nd section, 0th Row.
NSIndexPath *cPath = [NSIndexPath indexPathWithIndexes:idxArr length:2];
myCell *tCell = (myCell *)[self.settings cellForRowAtIndexPath:cPath];
tCell.myLabel.text = rec.servername; // rec.servername is from DC.
When I checked in the lldb,
tCell was nil.
Questions:
1) It is the right way of getting the Cell ?
2) Or, By the time -viewWillAppear, does the UITableView not Ready ?
I am sure.
You should populate the cells by conforming to tableView dataSource protocol and then in your viewWillAppear you should call reloadData on your tableView.
After calling reloadData for tableview, We need to call -scrollToRowAtIndexPath: before getting cell from -cellForRowAtIndexPath:.
Because, As we are calling a row in section 2, it might not be in the visible area until we scroll. So, cellForRowAtIndexPath: returns nil.
Method -cellForRowAtIndexPath: shouldn't be called programically. It's a data source method for UITableView and it contain some cell reuse optimalizations. If you update the view after scrolling down and up -tableView:cellForRowAtIndexPath will be called again and your changes won't be visible.
If you want to update specific cell you should update make changes in:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellId" forIndexPath:indexPath];
YourData *data = //Get your data here
if (data.isReady) {
cell.tf.text = data[indexPath.row].text;
} else {
cell.tf.text = #"Not ready yet. Need to reload this cell later";
}
return cell;
}
And then call method below when you finish fetch your data.
[self.tableView reloadRowsAtIndexPaths:(NSArray *) withRowAnimation:UITableViewRowAnimationFade];
If you want to reload whole tableView (usually it's not slow) as #salaman140 says you can call [self.tableView reloadData] to update all visible cells.
If I were you I wouldn't use:
NSUInteger idxArr[] ={2,0}; // 2 nd section, 0th Row.
NSIndexPath *cPath = [NSIndexPath indexPathWithIndexes:idxArr length:2];
I would (is much more clear):
NSIndexPath *cPath = [NSIndexPath indexPathForRow:0 inSection:2];
I have a list of NSStrings stored in an NSMutableArray, each value in the NSMutableArray has been assigned to a UITableViewCell. Using the following method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
How do I figure out which cell was selected. I'm looking for some kind of value through NSIndexPath or any other method to change the icon of a UITableViewCell when it is selected. After I know which cell has been selected, I'd reassign the cell to the new image, and then reload the data using: [self.homeworkTable reloadData].
I'm looking for some kind of simple return value, like 0 if the first cell is selected, etc. I've tried utilizing the NSIndexPath with no luck, as an NSIndexPath is not an NSString. How would I go about doing this?
EDIT
I understand the answer may already be on Stack Overflow, I simply haven't found it, and redirecting me to the answer would be very much appreciated.
You can get it directly didSelectRowAtIndexPath (that's the whole point!) using indexPath.row
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
int index = indexPath.row;
//....
It will match the index of your datasource array.
Sometimes you will need to get it from outside didSelectRowAtIndexPath deleguate method, you can do it the following way:
NSIndexPath *indexPath = [self.homeworkTable indexPathForSelectedRow];
int index = indexPath.row;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
You should use indexPath.row, this gets you the row that you selected in this tableview.
You should also check out the documentation on NSIndexPath:
https://developer.apple.com/library/Mac/documentation/Cocoa/Reference/Foundation/Classes/NSIndexPath_Class/Reference/Reference.html
I have a UITableView in my application where I have custom UITableViewCell which contains a UITextField. What I would like to do is get the correct index of the row when I select a particular textfield from that row. Unfortunately, I am only able to get the correct index of the row if I actually click on the row, and not when I select the textfield itself. I am implementing the <UITextFieldDelegate>, and when I select a particular UITextField, I call the method:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
int rowTest = [_table indexPathForSelectedRow].row;
int rowTest1 = [_cell.tireCodeField tag];
NSLog(#"the current row is: %d", rowTest1);
NSLog(#"and this row is: %d", rowTest);
return YES;
}
The problem is that the value for the row that I am getting is from whenever the method:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
...
}
get's called. It is as if there is a disconnect between the UITextField and the row in the table it resides in. Is there a way for me to select a particular textField, and get the
Is there a way for me to get the index of the row by selecting the UITextField that resides within it, instead of selecting the row itself?
Thanks in advance to all who reply.
The way this is usually done, is to give the text field a tag equal to the indexPath.row, or if you have multiple sections, some mathematical combination of the section and row (like 1000*indexPathSection + indexPath.row).
Well, assuming that the cell is the direct superview or the text field, you can directly ask for the text field's superview, cast to UITableViewCell, and then ask your instance of UITableView for the index path of that cell. Here's an example:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *)textField.superview; // cell-->textfield
//UITableViewCell *cell = (UITableViewCell *)textField.superview.superview; // cell-->contentView-->textfield
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
return YES;
}
If you're looking for a more dynamic solution that works across multiple versions of iOS, then you'll probably want to use the following quoted from #Marko Nikolovski here
// Get the cell in which the textfield is embedded
id textFieldSuper = textField;
while (![textFieldSuper isKindOfClass:[UITableViewCell class]]) {
textFieldSuper = [textFieldSuper superview];
}
// Get that cell's index path
NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)textFieldSuper];
This method crawls up the superview until it encounters a UITableViewCell. This keeps the code working even when the cell's view hierarchy changes, like it did from iOS 6 to 7.
I thought I'd post this slightly (!) late answer as I was trying to do this for ages and then remember another method (which if I say so myself is one of the best ways) of getting the index path for a cell when a UIButton was tapped.
In a similar way you can get the CGPoint of the cell.
-(NSIndexPath *)indexPathForTextField:(UITextField *)textField
{
CGPoint point = [textField convertPoint:CGPointZero toView:self.tableView];
return [self.tableView indexPathForRowAtPoint:point];
}
-(void)textDidChangeForTextFieldInCell:(UITextField *)textField
{
NSIndexPath *indexPath = [self indexPathForTextField:textField];
// Now you can update the object at indexPath for your model!
}
I think this is far neater than relying on tags or the even yuckier method of looking at superviews!
As it seems you are working with tags,using the UITextFieldDelegate,you can declare this methods in order to select the row.
-(void)textFieldDidBeginEditing:(UITextField *)textField{
int rowTest1 = textField.tag;
[myTableview selectRowAtIndexPath:[NSIndexPath indexPathForRow:rowTest1 inSection:0] animated:YES scrollPosition:UITableViewScrollPositionNone];
}
-(void)textFieldDidEndEditing:(UITextField *)textField{
[textField resignFirstResponder];
}
UITableViewCell *textFieldCell = (UITableViewCell*) [[[textfield superview]superview]superview];
NSIndexPath *indexPath = [self.editProfileTableView indexPathForCell:textFieldCell];