Getting reference of a UITextField which is on a UITableViewCell? - iphone

Actually i am using next and previous button for moving one to another cell and each cell has a textfield so when i am clicking on next button it moves me to the next cell and by getting this cell reference i can make the text field become first responder but when i am clicking on previous button it returns me no reference.
The code which i am using for next and previous is given below
- (IBAction)nextPrevious:(id)sender
{
NSIndexPath *indexPath ;
BOOL check = FALSE;
if([(UISegmentedControl *)sender selectedSegmentIndex] == 1){
if(sectionCount>=0 && sectionCount<8){
//for next button
check = TRUE;
sectionCount = sectionCount+1;
indexPath = [NSIndexPath indexPathForRow:0 inSection:sectionCount];
}
}else{
//for previous button
if(sectionCount>0 && sectionCount<=9){
check = TRUE;
sectionCount = sectionCount-1;
indexPath = [NSIndexPath indexPathForRow:0 inSection:sectionCount];
}
}
if(check == TRUE){
//[registrationTbl reloadData];
UITableViewCell *cell = [registrationTbl cellForRowAtIndexPath:indexPath];
for(UIView *view in cell.contentView.subviews){
if([view isKindOfClass:[UITextField class]]){
[(UITextField *)view becomeFirstResponder];
break;
}
}
[registrationTbl scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
// UITextField *field = (UITextField *) [cell.contentView viewWithTag:indexPath.section];
// [field becomeFirstResponder];
}
Any small suggestion will be much appreciated. Thanks in advance

The problem lies in the scrolling. When you scroll to the top of the next row, the previous row gets removed and reused for the last visible row, meaning that the method cellForRowAtIndexPath: will probably return null, as the cell is not currently available.
The quick&dirty fix would involve scrolling to Middle or a little displaced so the cell is still visible. The not-so-quick-nor-dirty would involve making a procedure that scrolls the table to make sure the cell is visible, and then when the scrolling stops, set the textfield it as the first responder.
(Edit) To explain a little more this last approach. Let's say that you add a new variable NSIndexPath *indexPathEditing. The delegate method tableView:cellForRowAtIndexPath: would have:
if (indexPathEditing && indexPathEditing.row == indexPath.row && indexPathEditing.section == && indexPath.section)
{
// Retrieve the textfield with its tag.
[(UITextField*)[cell viewWithTag:<#Whatever#>] becomeFirstResponder];
indexPathEditing = nil;
}
This means that if indexPathEditing is set, and the current row that is being loaded is visible, it will automatically set itself as the firstResponder.
Then, for example (in your nextPrevious: method), all you need to do is:
indexPathEditing = [NSIndexPath indexPathForRow:0 inSection:sectionCount];
[registrationTbl scrollToRowAtIndexPath:indexPathEditing
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
[registrationTbl reloadData];
The row will appear, the tableView:cellForRowAtIndexPath: called, and it will get automatically set as the firstResponder.
Also, notice that instead of doing a for with isKindOfClass, it's easier to set a tag number, and then retrieve the object with viewWithTag:, I incorporated this in the example.

Related

Cost of iterating over cells in a UITableView

Inside each UITableViewCell of my UITableView, I have a UIScrollView. The scroll view is setup so that when the user swipes right a menu will appear. This is similar to the behavior of the cells in the iPhone Twitter app. When a user swipes upon another cell I iterate over all visible cells to tell the UIScrollView to scroll back to the cell content (i.e. its initial position). The iteration is done in the scrollViewWillBeginDragging method with the following code:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if( [scrollView tag] == 90 ) {
NSLog(#"Dragging a scroll view inside a cell!");
for (UITableViewCell *cell in self.tableView.visibleCells) {
[(UICellContentScrollView *)[cell viewWithTag:90] scrollRectToVisible:CGRectMake(0.0f, 0.0f, 320.0f, [cell frame].size.height) animated:YES];
}
}
}
In the method viewDidDisappear I iterate again over all cells to reset various things like so:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
for( NSUInteger section = 0; section < [[self tableView] numberOfSections]; section++ ) {
for( NSUInteger row = 0; row < [[self tableView] numberOfRowsInSection:section]; row++ ) {
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
// resetting cell here
}
}
}
My question is if I am (a) going about this the right way and (b) does anyone have any recommendations on a better solution considering the table view may be storing up 50 (no more than 100) items.
Check out the NSNotification documentation. You could register all of your UITableViewCell objects to receive a notification you could call something like "cellWasSwiped" or "needToResetCells" or whatever. Then whenever you want to reset the cells you just post the notification. All of your UITableViewCell objects that are registered to receive it will get the notification and can then call whatever method you need.

Set first cell to highlighted on view's load

I have a UISplitViewController. When the app launches initially, the detail view is showing the detail data for the first row. However, the cell in the table is not highlighted, since it hasn't been selected yet. How can I set it to selected by default when the app loads initially?
I added this to cellForRowAtIndexPath but its not highlighting. It highlights fine when I actually select a cell.
if (indexPath.row == 0)
{
cell.selected = YES;
}
-(void)selectFirstRow {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
// optional:
// [self tableView:myTable willSelectRowAtIndexPath:indexPath];
[myTable selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
// optional:
// [self tableView:myTable didSelectRowAtIndexPath:indexPath];
}
Then call [self selectFirstRow]; wherever you need to.

How to create a toolbar between UITableView rows

I am interested on how tweetbot does the following:
I would like to create the same thing with my app, where if you click on a row, it pops
an additional UIToolBar and pressing on any other row will dismiss this view with animations.
The logic I think is simple, you just need to add a subView to the UITableViewCell when pressed and shift the rest of the content up, but how do you actually dismiss it when I press the other row?
The best way to do this is to add a dummy cell below the cell that was tapped.
First you need to keep track of what cell is been tapped and act accordingly.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//if user tapped the same row twice let's start getting rid of the control cell
if([indexPath isEqual:self.tappedIndexPath]){
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
//update the indexpath if needed... I explain this below
indexPath = [self modelIndexPathforIndexPath:indexPath];
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
//if in fact I tapped the same row twice lets clear our tapping trackers
if([indexPath isEqual:self.tappedIndexPath]){
self.tappedIndexPath = nil;
self.controlRowIndexPath = nil;
}
//otherwise let's update them appropriately
else{
self.tappedIndexPath = indexPath; //the row the user just tapped.
//Now I set the location of where I need to add the dummy cell
self.controlRowIndexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
}
//all logic is done, lets start updating the table
[tableView beginUpdates];
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationNone];
}
//lets add the new control cell in the right place
if(self.controlRowIndexPath){
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
//and we are done...
[tableView endUpdates];
}
Whenever you have that dummy cell present you have to make sure to send the correct count.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(self.controlRowIndexPath){
return modelArray.count + 1;
}
return self.modelArray.count;
}
Also, return the appropriate height for your ControlCell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath isEqual:self.controlRowIndexPath]){
return 45; //height for control cell
}
return 70; //height for every other cell
}
Lastly, remember the control cell is a dummy. Is not part of the model, thus you have to account for that. If the user taps a row that is above the last tapped row is ok but when the new tapped row is below that control cell you have to make sure you access the right row in your model. In other words, account for that fake cell in the middle of your view.
- (NSIndexPath *)modelIndexPathforIndexPath:(NSIndexPath *)indexPath
{
int whereIsTheControlRow = self.controlRowIndexPath.row;
if(self.controlRowIndexPath != nil && indexPath.row > whereIsTheControlRow)
return [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
return indexPath;
}
I hope this helps.
In tableView:didSelectRowAtIndexPath:, you remove the tool view from the last selected cell. If there is no such view, you create a new one. Then you add this view to the newly selected cell. Save the indexPath of the selected row.
In tableView:heightForRowAtIndexPath:, you check if the indexPath is the same as the saved indexPath. If they are equal, you return a height that is the height of both views. If it is not equal, just return the height of the "real cell".
Put all your calls in didSelectRowAtIndexPath between [tableView beginUpdates] and [tableView endUpdates] to get animation for the height change.
rjgonzo's code works fine except for the case where you only have 1 row in the tableview. When there's only 1 row (and 1 object in the tableview datamodel) you'll get an NSRange exception when you call insertRowsatIndexPath(s). To fix this I checked to see if the datamodel has only 1 object and if so then I add the row to the current indexpath (instead of the controlindexpath) which results in the row logically being added above the first row and then I call moveRowAtIndexPath to interchange the rows after calling [self.tableView endUpdates]. The animation shows as expected with the control row appearing to slide down from the 1st row.
if(self.controlRowIndexPath){
//Check to see if the datamodel only has 1 object
if([self.objects count]==1){
//If so then insert the row above the row
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
else
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
[self.tableView endUpdates];
//if the row was inserted above the 1st row then switch the rows
if([self.objects count]==1)
[self.tableView moveRowAtIndexPath:self.controlRowIndexPath toIndexPath:indexPath];
I would not add a subview to a UITableViewCell, I would add another row to the UITableView. That way, the UITableView will take care of the animation. (And I don't think that's possible to animated UITableViewCell height changes...)
Use simply
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
do add a row. And
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
to remove it.
#rjgonzo this works great but there is a minor issue on how you keep the indexPathToDelete. Since it's just another pointer to self.controlRowIndexPath, once you clear or reassign the self.controlRowIndexPath, indexPathToDelete will not be what you wanted, and tableView deleteRowsAtIndexPaths:withRowAnimation: call, you will get an SIGBART crash.
so, instead of
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
and
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationNone];
}
the following code should work fine:
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = [NSIndexPath indexPathForRow:self.control_row_index_path.row inSection:self.control_row_index_path.section];
...
...
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete.row != 0){
NSLog(#"row to delete %d", indexPathToDelete.row);
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete] withRowAnimation:UITableViewRowAnimationNone];
}
Here's what I'm doing to get the animation to be clean.
In addition to the strategy user fluchtpunkt suggests (that is, adding a subview to the cell, updating the cell height via heightForRowAtIndexPath, beginUpdates, and endUpdates), I'm finding the following measures to be helpful with the animation:
The tableviewcells have a background image. Otherwise, the added subview/toolbar is visible through the cell just before the tableview animates the height change.
The tableviewcell is 'behind' the view that is the cell below it, otherwise again the subview/toolbar will show too soon. I'm using [tableview sendSubviewToBack:cell]; and it's taking care of that.
This is clean for me, but not exactly like Tweetbot. Interestingly, Tweetbot's animation seems to pull the toolbar down as the bottom of the cell animates down. It seems like some additional animation must be taking place, or my conspiracy theory is that it is actually adding the subview to the top of the cell below the selected cell, and then performing the lengthening and animation on the cell below.

setting multiple UITextView's to editable causes cursor to blink between them

I'm attempting to set the UITextViews in a set of UITableViewCell's to editable when the user taps the "edit" button for the UITableView. Each UITableViewCell has a UITextField (cell.blurb). This code will set each UITextView to editable, however, the cursor alternates very rapidly between each one. I'm pretty sure it's a responder chain issue, (they're all becoming first responders maybe?) however I can't seem to remedy it. I've tried having each UITextView resignFirstResponder (save for the first one in the list), but it does nothing. In the table cell xib they're uneditable.
//set all text areas to editable and opaque
int numSections = [_tableView numberOfSections];
for (int s = 0; s < numSections; s++) {
int numRows = [_tableView numberOfRowsInSection: s];
for (int r = 0; r < numRows; r++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:r inSection:s];
CustomTableViewCell *cell = (CustomTableViewCell *)[_tableView cellForRowAtIndexPath:indexPath];
[cell blurb].editable = YES;
[[cell blurb] resignFirstResponder];
//set the first row's UITableView to the first responder
if (s == 0 && r == 0)
[[cell blurb] becomeFirstResponder];
}
}
The only solution I managed to make work is create two UITextViews, one editable, one not with the same frame and properties, and manage opacity of each according to editable state. Quite lame isn't it ?
Another thing, I had to implement this in my delegate (which happens to be the UITableViewCell subclass):
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
if (!self.editing)
return NO;
return YES;
}
otherwise, for an unknown reason, the end of the UITableView animation (when it quits editing mode) triggers again becomeFirstResponder on one of the UITextView.
Here is a thread on devforums https://devforums.apple.com/message/290194
if (s == 0 && r == 0) {
[[cell blurb] becomeFirstResponder];
} else if ([[cell blurb] isFirstResponder]) {
[[cell blurb] resignFirstResponder];
}

iPhone: Jump to next uitextfield in uitableview, how to?

In my iPhone project I'm using a UITableview with UITableViewCells containing UITextfields. I have seen in many apps that it is possible to use a next button to jump to the next textfield in the next cell. What is the best way to accomplish this?
My idea is to get the indexPath of the cell with the textfield that is being editing and then get the next cell by cellForRowAtIndexPath. But how can I get the indexPath of the cell I'm currently editing?
Thanks!
Keep references to the UITextField instances in your table view.
Assign unique tag values to your UITextField instances.
In your last text field, you might set its Return key type, which changes the keyboard's Return key label from "Next" to "Done": [finalTextField setReturnKeyType:UIReturnKeyDone];
In the UITextField delegate method -textFieldShouldReturn:, walk through the responders:
- (BOOL) textFieldShouldReturn:(UITextField *)tf {
switch (tf.tag) {
case firstTextFieldTag:
[secondTextField becomeFirstResponder];
break;
case secondTextFieldTag:
[thirdTextField becomeFirstResponder];
break;
// etc.
default:
[tf resignFirstResponder];
break;
}
return YES;
}
Assuming the UITextField was added to the UITableViewCell like below
UITableViewCell *cell;
UITextField *textField;
...
textField.tag = kTAG_TEXTFIELD;
[cell.contentView addSubview:textField];
...
You can get the current index path via
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
if([textField.superView.superView isKindOfClass:[UITableViewCell class]]) {
UITableViewCell *cell = (UITableViewCell *)textField.superView.superView;
NSIndexPath *indexPath = [self.myTableView indexPathForCell:cell];
}
...
The next row's UITextField would then be
NSIndexPath *indexPathNextRow = [NSIndexPath indexPathForRow:(indexPath.row+1) inSection:indexPath.section];
UITableViewCell *cellNextRow = (UITableViewCell *)[self.myTableView cellForRowAtIndexPath:indexPathNextRow];
UITextField *textFieldNextRow = (UITextField *)[cellNextRow.contentView viewWithTag:kTAG_TEXTFIELD];
Hope it helps!