UITableView scrollToRowAtIndexPath only scrolls sometimes - iphone

I have a UITableView with several UITableViewCells and inside these cells are UITextFields. I implemented a UIToolbar for switching between the different textfields. I can go to the next textfield in the next cell or to the previous textfield. This works fine until a cell should become first responder which is currently not visible in the tableview.
I figured out that
[self.tableview cellForRowAtIndexPath:indexPath]
returns nil and that manually scrolling to the cell with the textfield, which should become first responder, removes the problem. Therefore I tried scrolling to the cell before changing it to become first responder.
I tried that with
[[self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
but unfortunately the tableview does not scroll to this cell but to all other cells. When I add a breakpoint to this line the problem doesn't occur. It also looks like the tableview is scrolling to the cell but then scrolls down again.
Here is the code of cellForRowAtIndexPath:
UITableViewCell *cell;
static NSString *AttributeCellIdentifier = #"AttributeCellIdentifier";
cell = [tableView dequeueReusableCellWithIdentifier:AttributeCellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"AttributeCell" owner:self options:nil];
cell = attributeCell;
self.attributeCell = nil;
UITextField * textField = (UITextField *)[cell viewWithTag:1];
[textField setKeyboardType:UIKeyboardTypeDecimalPad];
[textField setInputAccessoryView:[self inputAccessoryView]];
}
UITextField * textField;
textField = (UITextField *)[cell viewWithTag:1];
textField.placeholder = #"C(Probe)/mol/l";
return cell;
Here is the code for switching to the previous textfield:
UIView * currentResponder = [self.view findFirstResonder];
UITextField * newFocusTextField;
UITableViewCell * cell = (UITableViewCell*)[[currentResponder superview] superview];
NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
newFocusTextField = (UITextField*)[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]] viewWithTag:1];
[currentResponder resignFirstResponder];
[newFocusTextField becomeFirstResponder];
UITableViewCell * newCell = (UITableViewCell*)[[newFocusTextField superview] superview];
NSIndexPath * newIndexPath = [self.tableView indexPathForCell:newCell];
[self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
Hope this helps.

The code on your post and the problem definition is not hundred percent clear, but there was a bug/glitch in scroll methods' working for iOS 3.0 (i guess specifically 3.0, as far as i remember). I couldnot figure out the reason for it to happen, but it was crashing all the time, although it was OK for iOS 4.x.
The fix was adding a
[table reloadData]
before the scroll line. I know it doesnt make sense, and I know it is not good practice especially if the cell rendering is expensive, but it did solve the problem for that case. I dont know if your problem stems from the same issue, but you may just give it a try...

I tried several thinks and everything works now fine. I removed:
[currentResponder resignFirstResponder];
And after removing that line the tableview scrolled fine and I can directly set the pointer to the new cell with the newFocusTextField and make it firstResponder with:
[newFocusTextField becomeFirstResponder];
I have a nextTextField method which works fine with
[currentResponder resignFirstResponder];
so I don't really understand why the problem occurs but I think scrolling to the cell and then just setting the new firstResponder is better then resigning the currentResponder first.
Thank you for helping anyway ;)

Related

Table Cell SubView Iteration Not Finding TextField

I've created a table where each cell holds both a text label and a text field. I'm adding the textfields as such [cell addSubview:passwordField]; and from a visual perspective they appear and are editable, etc....
The problem arises when I attempt to retrieve the entered values from the textfields. I iterate over the cells and try to grab the subview (IE the textfield), however, my iteration only discovers the text label.
Here is the code I'm using to search:
for(NSInteger i =0; i < [tableView numberOfRowsInSection:0]; i++){
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:path];
UIView* subView = [[cell.contentView subviews]lastObject]; // I've also tried object at index here
// Anything beyond this is just matching....
Another approach I took was recursively searching the subviews, but, again that yielded no results.
You have added your textField on subView of cell.
[cell addSubview:passwordField];
While you're trying to find it on cell.contentView.
Add your textField as a subView of cell.Contentview
[cell.contentView addSubview:passwordField];
And find it in this way -
for(UIView *view in [cell.contentView subviews])
{
if([view isKindOfClass:[UITextfield class]])
{
UITextField *textField = (UITextField *)view;
NSLog(#"%#",textField.text);
}
}
Why not have a datasource mapped to the TableView and just retrieve / update the values in the datasource. You can then call reloadRowsAtIndexPaths to load just the row you just changed. Trying to iterate through the TableView rather than just updating the datasource seems very inefficient.
Instead of UIView* subView = [[cell.contentView subviews]lastObject]; you can try to find it as:
for(UIView *view in [cell subviews])
{
if([view isKindOfClass:[UITextfield class]]){
// view is the reference to your textfield
}
}
That way you can add other UIViews as subviews and still get the reference of the textfield without having to keep track of its subview index.
2 things occur to me:
In the long run it'll be easier to create a UITableViewCell which contains a UITextField which is accessible as a property. You can either use a nib to layout the cell or do it programmatically in the cells init method. This approach will make your code easier to manage.
You need to consider cell reuse. If you are reusing cells (which you should be) then you will need store the fetch the value from the textfield before it is reused.

UITableViewCell from contentView subview

I have created the cells with labels and using checkaMarksAccessory. The few last cells have UITextFields which can user modifi, and those have selector on UIControlEventEditingDidEnd where i want change the state of the cell to checked.
How can i get the cell in the selector? Doesn't have the object some parentView?
The way i inserting the object to cell.
UITextField *textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 25, 200, 30)];
[textfield setBorderStyle:UITextBorderStyleRoundedRect];
[textfield addTarget:self action:#selector(vybavaDidFinishEdit:) forControlEvents:UIControlEventEditingDidEnd];
[cell.contentView addSubview:textfield];
I'm not sure if it's safe to assume cell.contentView.superview == cell. Might Apple change this? I doubt it. But, I don't see anywhere in the documentation that says a cell's content view is a direct subview of the cell.
If you've added a UIGestureRecognizer to one of your subviews of the cell's content view, then you can get a reference to the cell with:
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[gestureRecognizer locationInView:self.tableView]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
Table View Animations and Gestures sample code uses indexPathForRowAtPoint: this way.
If you must traverse superviews, I think using a function like the one below is a bit safer.
UITableViewCell *ACMContentViewGetCell(UIView *view)
{
while ((view = view.superview)) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return (UITableViewCell *)view;
}
}
return nil;
}
But that function still assumes contentView is within its cell, which I also didn't see anywhere in the documentation.
So perhaps, the best solution is to rearchitect your code so that you don't need to get cell from contentView, or if you must, then add an instance variable from the subview of contentView to cell.
ok so the way is to use superview. The superview is component which own the object. If i want get the UITableViewCell from UITextField i used [[UITextField superview] superview].

UITableView discloure indicator going nuts

I have a UITableView (on a UIViewController) which is pushed via a navigationController. Along with pushing, I select with which array i want to populate the table. The code for pushing is like this:
if(self.newView == nil)
{
NewView *viewTwo = [[NewView alloc] initWithNibName:#"Bundle" bundle:[NSBundle mainBundle]];
self.newView = viewTwo;
[viewTwo release];
}
[self.navigationController pushViewController:self.newView animated:YES];
newView.tableArray=newView.arrayWithOptionOne;
[newView.tableView reloadData];
All works well and the table gets reloaded every time. However in the last row of section 0, there is a switch which loads section 1.
The last row of section 1 is tappable (didSelect…) and it loads a modalView. On this last rod I added a disclosure indicator and also the blue background when tapping. The table has sliders, labels, etc. So the customization is quite long:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil;
static NSString *kDisplayCell_ID = #"DisplayCellID";
cell = [tableView dequeueReusableCellWithIdentifier:kDisplayCell_ID];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:kDisplayCell_ID] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
else
{
UIView *viewToRemove = nil;
viewToRemove = [cell.contentView viewWithTag:1];
if (viewToRemove)
[viewToRemove removeFromSuperview];
UIView *viewToRemove2 = nil;
viewToRemove2 = [cell.contentView viewWithTag:2];
if (viewToRemove2)
[viewToRemove2 removeFromSuperview];
}
if (indexPath.section==0) {
UIControl *cellValueS = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewKey] objectAtIndex:indexPath.row];
[cell.contentView addSubview:cellValueS];
}
if (indexPath.section==0 && indexPath.row==3) {
UIControl *cellValueL = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewLabel] objectAtIndex:0] ;
[cell.contentView addSubview:cellValueL];
}
if (indexPath.section==1 && indexPath.row==0){
UIControl *cellValueS = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewKey] objectAtIndex:indexPath.row] ;
[cell.contentView addSubview:cellValueS];
}
if (indexPath.section==1 && indexPath.row==1) {
UIControl *cellValueS = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewKey] objectAtIndex:indexPath.row] ;
[cell.contentView addSubview:cellValueS];
UIControl *cellValueL = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewLabel] objectAtIndex:0] ;
[cell.contentView addSubview:cellValueL];
}
if (indexPath.section==1 && indexPath.row==2) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
So far also works ok.
The problem is that when I go back to the previous view and select another option to populate the table, when it's pushed again, I see the disclosure indicator and blue selection background on other rows on the same section. I've observed that it depends on where the table is scrolled.
I've tried to understand why does it happen, but i can't. I've somehow solved the problem by setting newView to nil and releasing it and then allocating it again before it gets pushed again.
Am I doing something wrong here? or why is the disclosure indicator and tapping background appearing where they are not supposed to be?
Thanks in advance!
action of the switch
-(void)extraOptionsSwitchAction:(id)sender{
switch(extraOptionsSwitch.isOn) {
case 1:
// [self.tableView beginUpdates];
[self.tableView insertSections: [NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
//[self.tableView reloadData];
// [self.tableView endUpdates];
break;
case !1:
// [self.tableView beginUpdates];
[self.tableView deleteSections: [NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
// [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView reloadData];
// [self.tableView endUpdates];
break;
}
}
It has to to with reusing cells. You probably don't differentiate between the different cell types in your cell creation method - so one cell that went offscreen can easily be reused for another (different type) cell. Further, you seem to add subviews over and over again - only do that when you instantiate the cell (with alloc/int), and not when configuring.
Also, for different cell types, use different identifiers (you didn't show this code).
The programming guides have good example on table views and their cells and reuse pattern. It's worth reading a couple of times - it's easy to get wrong and is a main topic for performance tuning.
Edit
Now that you added more code, another problem seems to be here:
if (indexPath.section==1 && indexPath.row==2) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
}
You are missing an else part. The selectionStyle and accessoryType are set to what they were set to before - you miss to configure the cells correctly for all other cells than that special one.
Each cell type should really get its own identifier though. If the adding/removing of subviews work as expected is hard to tell from that code.
One thought: As you aren't really reusing a lot of the cells here anyhow you could even disable the reuse by changing
static NSString *kDisplayCell_ID = #"DisplayCellID";
cell = [tableView dequeueReusableCellWithIdentifier:kDisplayCell_ID];
to
static NSString *kDisplayCell_ID = nil;
This would just always produce a new cell. I wouldn't recommend this in the general case, though.
This is due to cellReusability, and it has, for a long time, wasted so many developers' time and effort. If you knew the concept of cell reusability, you would know that while scrolling, the index of the row and sections remain the same (although you expect it to be different for a different position on the uiTableView).
The only way is to subClass the UITableViewCell and create your own CustomUITableViewCell, and implement that with the disclosure indicator, or resist your input to just a small TableView that fits the screen and make scrollable = NO.

DTGridView doesn't interact with UILabel

I am using DTGridView with a subclass of DTGridViewCell with a UILabel and UITextField to do an in-place cell editing. That idea worked for me in UITableView like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
EditableDetailCell *cell = (EditableDetailCell *)[tableView cellForRowAtIndexPath:indexPath];
[[cell textField] setFrame:CGRectMake(8, 0, cell.frame.size.width, cell.frame.size.height)];
[[cell textField] becomeFirstResponder];
[[cell mylabel] setText: nil];
}
The text disappear and the textfield appears. The same thing doesn't work for me with
- (void)gridView:(DTGridView *)agridView selectionMadeAtRow:(NSInteger)rowIndex
column:(NSInteger)columnIndex
{
cell textField] setFrame:CGRectMake(8, 0, cell.frame.size.width, cell.frame.size.height)];
[[cell textField] becomeFirstResponder];
[[cell label] setText: nil];
}
Can anyone please help me? Thanks a lot.
There was a little problem in my question. For the second code I did
MyCustomCell* cell = (MyCustomCell *)[aGridView cellInfoForRow:rowIndex column:columnIndex];
before i make cell changes. That works and respondos to my first question. But now I have a second problem. If I scroll off the selected cell and later came it back to show, then the reused cell shows the content with some problem. Example: textField has not become first responder any more, cell is not selected, and the custom cell is drawn in a different cell. How can I solve that problems?

UITextField in UITableViewCell - adding new cells

I am trying to create a table view similar to the YouTube video uploader view in the Photo Gallery on the iPhone.
Here's the basic setup.
I have a custom UITableViewCell created that contains a UITextField. Displaying the cell in my table works great and I can edit the text with no problems. I created an event hook so I can view when the text has changed in the text field.
[textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]
What I want to do is this. When the user first edits the text I want to insert a new cell into the table view below the current cell (newIndexPath is calculated prior to the proper position):
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
Problem is when I run the cell insert code the cell is created but the text field's text updated briefly, but then the keyboard is dismissed and the text field is set back to an empty string.
Any help would be awesome! I've been banging my head about this one all day.
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
if (section == 0)
return 2;
else
return self.tags.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
cell = (SimpleTextFieldTableCell *)[tableView dequeueReusableCellWithIdentifier:tagCellIdentifier];
if (cell == nil)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"SimpleTextFieldTableCell" owner:nil options:nil] lastObject];
}
((SimpleTextFieldTableCell *)cell).textField.delegate = self;
((SimpleTextFieldTableCell *)cell).textField.tag = indexPath.row;
((SimpleTextFieldTableCell *)cell).textField.text = [self.tags objectAtIndex:indexPath.row];
[((SimpleTextFieldTableCell *)cell).textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
- (void)textFieldDidChange:(id)sender
{
UITextField *textField = sender;
[self.tags replaceObjectAtIndex:textField.tag withObject:textField.text];
if (textField.text.length == 1)
{
[textField setNeedsDisplay];
[self addTagsCell];
}
}
- (void)addTagsCell
{
NSString *newTag = #"";
[self.tags addObject:newTag];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:self.tags.count - 1 inSection:1];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
}
Only thing i can think of here is that perhaps when you insert a row the whole table view is reloaded, if you did not add your cell properly to the cell queue, they wont come back in the state that they were , therefore you are seeing empty cells as a result of the insert, just a guess, hope it helps.
Quick update:
Shouldn't matter but I notices you don't need:
[self.tableView beginUpdates]
[self.tableView endUpdates]
since you are performing one operation. Not sure if that matters (it shouldn't).
Update:
I should have said that such issues are pretty common. Here is a post related to your issue
http://www.bdunagan.com/2008/12/08/uitextview-in-a-uitableview-on-the-iphone/
Also, others have abstracted this out. Specifically I have tried this with no such issues:
http://furbo.org/2009/04/30/matt-gallagher-deserves-a-medal/
You could use:
http://github.com/joehewitt/three20/
But it has a bit of a learning curve.
Another Stackoverflow question tackling this issue:
Editing a UITextField inside a UITableViewCell fails
Excuse me not answering your issue directly, but I think that the solution might be contained in one of these links.
Original Answer:
Try:
[newTextField setNeedsDisplay];
Sometimes the tableviews can be "sticky" with updating UITextView/UITextField content.
If that doesn't work, be sure that you're backing model is also updated properly. You haven't displayed any code indicating you updated the model (although I assume you did, otherwise it would have likely thrown an exception).