Bring up Keyboard for cell in UITableView? - iphone

I have UITableView that has custom cells representing a player. When I add a new player a new data item is added and [UITableView reloadData] is called. Part of the cell is a UITextView that contains the player name. How do I bring up the keyboard for it?
I could use becomeFirstResponder if I had access to the UITextView in the cell but it hasn't actually been created yet. Since reloadData doesn't actually perform the creation of the new cell yet.
Someone else must have solved this before. Any tips on how to make this work would be greatly appreciated.
My code snippet:
-(IBAction)addPlayerPressed:(id)sender {
Player *newPlayer = [Player alloc];
[players addObject:newPlayer];
[table reloadData];
// Scroll to bottom showing the new player location
NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([players count] - 1) inSection:0];
[table scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
// cell is always nil :(
PlayerTableCell *cell = (PlayerTableCell *)[table cellForRowAtIndexPath:scrollIndexPath]
[cell.nameField setfirstResponder];
}

Couple of things:
You're probably better off using UITextField instead of UITextView. UITextField is ideal for single-line entries like what you're describing here.
As for your solution, here's what I'd recommend. (Also took the liberty of cleaning up your memory management)
Add a BOOL property to your Player class called, say, 'needsKeyboardDisplay', then set it to yes after you create a new instance.
-(IBAction)addPlayerPressed:(id)sender {
Player *newPlayer = [[Player alloc] init];
newPlayer.needsKeyboardDisplay = YES;
[players addObject:newPlayer];
[newPlayer release];
[table reloadData];
// Scroll to bottom showing the new player location
NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([players count] - 1) inSection:0];
[table scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
Then, in cellForRowAtIndexPath, insert this after you've otherwise set up your cell:
Player *playerForCell = [players objectAtIndex:indexPath.row];
if (playerForCell.needsKeyboardDisplay)
{
[nameField becomeFirstResponder];
playerForCell.needsKeyboardDisplay = NO;
}
All that said, from a user experience perspective, the (somewhat) standard iPhone experience for editing details in a long list of items like this is to do it in another view. It's up to you, of course.

Can u make the textfields delegate the UITableviewController instead Of the table cell?

In your PlayerTableCell class, you could put some code in the -initWithStyle:reuseIdentifier: method that checks to see if it should become first responder and, if so, does. You probably have a PlayerController class or something similar; you might make a delegate method for the custom table view cell or something similar.

Related

Scrolling to a certain custom cell when loading the table

I'm bulding a celander application and i want the calender to get scrolled to the current month when started. i used custom cell for each month. the code that i found to make this happen from this website is this:
[self.bahraincld reloadData];
NSIndexPath *scrollto = [NSIndexPath indexPathForRow:2 inSection:1];
[self.bahraincld scrollToRowAtIndexPath:scrollto
atScrollPosition:UITableViewScrollPositionTop animated:YES];
I'm using it in the view did load. it's giving me a signal SIGABRT.
is this code correct? where should i use it if i want it to work whenever the user starts the calendar.
Thanks.
You better put this code into viewDidAppear, because your tableView haven't loaded the data yet, so the cells are not created. In your case SIGABRT, is because row number 2 is beyond bounds.
Here is what i did, hop it helps someone.
//in the view will appear reload your table.
-(void)viewWillAppear:(BOOL)animated
{
[self.yourtable reloadData];
}
//in the view did appear, value = defining which costume cell to move to.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
int value = 5;
NSIndexPath *scrollto = [NSIndexPath indexPathForRow:value inSection:0];
[self.yourtable scrollToRowAtIndexPath:scrollto
atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

How to trigger scrollsToTop when UITableView is nested inside UIScrollView

I was wondering if anyone had had any luck triggering scrollsToTop (or by any other means) for a UITableView from the user tapping on the status bar when the UITableView is nested inside a UIScrollView that also observes this function? I know this probably isn't good practice, but in this instance the UX kind of calls for this type of hierarchy.
Either way, I've seen a whole bunch of proposals ranging from private methods (obviously not going to happen) to adding fake windows over the status bar (also not going to happen).
Ok, so the answer here is two fold:
You cannot have more than one UIScrollView (or classes deriving from or using UIScrollView - i.e. UITableView) on the same UIView with the property scrollsToTop set to YES. Pick the one you want to have the feature and make sure all others are no
For example, do this:
scrollView.scrollsToTop = NO;
tableView.scrollsToTop = YES; // or not set
Implement the UIScrollView delegate method scrollViewShouldScrollToTop: and return YES if the calling UIScrollView is the UITableView.
Props to this answer for mentioning the non-multiple scrollsToTop option.
Just wanted to share a little function I wrote that helps debug these situations. As others have mentioned, you have to make sure only ONE scroll view has scrollsToTop turned on. If you embed complex view hierarchies it may be difficult to figure out which scroll view is the culprit. Just call this method after your view hierarchy is created, like in viewDidAppear. The level parameter is just to help indentation and you should seed it off with 0.
-(void)inspectViewAndSubViews:(UIView*) v level:(int)level {
NSMutableString* str = [NSMutableString string];
for (int i = 0; i < level; i++) {
[str appendString:#" "];
}
[str appendFormat:#"%#", [v class]];
if ([v isKindOfClass:[UITableView class]]) {
[str appendString:#" : UITableView "];
}
if ([v isKindOfClass:[UIScrollView class]]) {
[str appendString:#" : UIScrollView "];
UIScrollView* scrollView = (UIScrollView*)v;
if (scrollView.scrollsToTop) {
[str appendString:#" >>>scrollsToTop<<<<"];
}
}
NSLog(#"%#", str);
for (UIView* sv in [v subviews]) {
[self inspectViewAndSubViews:sv level:level+1];
}}
Call it on your view controller's main view.
In the log, you should see >>>scrollsToTop<<< next to every view that has it turned on, making it easy to find the bug.
One thing that helped me fix this problem is:
Verify every other UIScollView in the view hierarchy is set to scollsToTop = NO
Add sub view controllers as child view controllers using addChildViewController: It is not sufficient to only add the view as a subview to the parent's view.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[gridTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
Hoping to help you :)

UIViewController with textField and tableView

I have the following problem:
I got a UIViewController and it contains a textfield and a tableview. I push the view from another one, so that I got a navigationbar on top. I enabled the edit-button, and navigation also works fine.
What my problem is:
-the edit button is not working. I was looking through here, but I couldn't get it fixed
-when I enter data in to the textfield, the tableview gets the data, reloading is working, but it doesn't show any animation, and no rows are added ...
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[controller insertCostumer:textField.text];
[textField resignFirstResponder];
[tabViewitem reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:controller.sizeOfNavi+1 inSection:1];
NSMutableArray *arr = [[NSMutableArray alloc]init];
[arr addObject:indexPath];
[tabViewitem insertRowsAtIndexPaths:arr withRowAnimation:UITableViewRowAnimationTop];
return YES;
}
This is my textfieldshouldreturn method, I also tried out textfieldshouldendediting and textfielddidendediting and so on :-)
Hope somebody has any clue how to do it.
I have no direct solution but I think I found a useful link for you:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/11315-textfieldshouldreturn-not-called-probrem.html
This person has some problems to while calling the 'textFieldShouldReturn' method.

Need help with table view detail view. I can't seem to write values to the UILabel's text property?

I have a TableView setup as my "root view controller" and a detail view controller called "DetailController" to allow me to display the detail view for what the user selects in the main table view. The navigation is working correctly - when I select a row in my table the screen slides to the left and I'm presented with my detail view, but I can't update the detail view's UILabel text properties. It's like it is just ignoring my UILable set property methods (see code below) as it isn't giving me any errors or warnings.
I've tested that I do have the values I need when I hit the "tableView:didSelectRowAtIndexPath:" method, but for some reason I can't seem to update the detail view's UILabels' text properties. Using NSLog's I can see that I am getting the values and passing them correctly to the UILabels. However the detail view isn't showing the passed values in its UILabels.
The strange thing is that I CAN set the detail view's title property correctly, just not the UILabel properties, which is very confusing!
Here's my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (self.detailView == nil) {
DetailController *detail = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
self.detailView = detail;
[detail release];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[theList getItem:cell.textLabel.text]];
[self.detailView.theID setText:#"LOOK AT ME NOW!"];
self.detailView.thePrice.text = [[dict objectForKey:#"PRICE"] stringValue];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:self.detailView animated:YES];
NSString *newTitle = [[NSString alloc] initWithString: #"Viewing Attributes for: "];
newTitle = [newTitle stringByAppendingString:cell.textLabel.text];
self.detailView.title = newTitle;
[dict release];
[newTitle release];
}
Thanks for any help on this! I love this community - you guys are awesome!
I Found An Answer! I think...
After much trial and error I was able to figure out a solution, but the solution doesn't entirely make sense to me and still leaves me with questions.
Siegfried suggested that I move my "pushViewController" message to the end of the method so that the last six lines of my code were changed to:
// Pass the selected object to the new view controller.
NSString *newTitle = [[NSString alloc] initWithString: #"Viewing Attributes for: "];
newTitle = [newTitle stringByAppendingString:cell.textLabel.text];
self.detailView.title = newTitle;
[dict release];
[newTitle release];
[self.navigationController pushViewController:self.detailView animated:YES];
}
When I moved it to the last line (as shown above) my program would crash with an EXC_BAD_ACCESS error. I still don't understand why moving that line of code to the end of my method caused the program to crash?
However, this got me thinking - I was able to successfully change the title of the new detail view, but not the view's UILabel properties. So I moved my pushViewController method back to where it was and then moved my methods for setting the new view's UILabels underneath the pushViewController method and BOOM! Everything worked! The new detail view was being pushed when the user clicked on the row in my table and its properties (title and UILabels) were all being set correctly.
Why does this work?
I remember reading about how the iPhone handles views, something called "lazy loading" or "lazy instantiation"? I think this means that I can't change the properties of my detail view UNTIL I've "pushed" it, which is when the object is actually created in memory and its properties become available?
If this is correct, that would kind of explain the error I was getting when I moved the pushViewController method to the end of my code - I was trying to change the title property on a view that wasn't instantiated. However, that doesn't completely make sense because I was trying to change the UILabel properties of the view in my original code, which was before the view was actually pushed and the program wasn't crashing?
So my code that is currently working looks like the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (self.detailView == nil) {
DetailController *detail = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
self.detailView = detail;
[detail release];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[theList getItem:cell.textLabel.text]];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:self.detailView animated:YES];
// Moved the "set property" methods to happen after the detail view is pushed
[self.detailView.theID setText:#"LOOK AT ME NOW!"];
self.detailView.thePrice.text = [[dict objectForKey:#"PRICE"] stringValue];
NSString *newTitle = [[NSString alloc] initWithString: #"Viewing Attributes for: "];
newTitle = [newTitle stringByAppendingString:cell.textLabel.text];
self.detailView.title = newTitle;
[dict release];
[newTitle release];
}
I like that the code is working, but I would really like to understand what was and is actually happening. Can anyone offer additional insight here?
Thank you!
Move
[self.navigationController pushViewController:self.detailView animated:YES];
to last line.
You may try doing:
NSString *strText = [NSString stringWithFormat:#"%#",[[dict objectForKey:#"PRICE"] stringValue]];
self.detailView.thePrice.text = [NSString stringWithFormat:#"%#",strText];
Check wheather strText is containing the expected value and is not nil.

Clicking on UITextField in a UITableViewCell

I have an issue where when a textField is clicked on in a UITableViewCell, the method tableView:didSelectRowAtIndexPath: does not get invoked. The problem is, I need to scroll my tableView into proper position, otherwise the keyboard goes right over the first responder.
I have to then move code like this:
[[self tableView] scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
into both my tableView delegate method and in my UITextField delegate method, textFieldDidBeginEditing:.
Is the best way to just create a new method, pass to it the indexPath of the cell/textfield being clicked, and call the method from both the tableView delegate and the UITextField delegate? better way of going about it?
I found the following works well (It assumes you're in a table view controller)
- (void)textFieldDidBeginEditing:(UITextField *)textField{
CGPoint pnt = [self.tableView convertPoint:textField.bounds.origin fromView:textField];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:pnt];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
There are a couple of ways to fix this issue. What happens is that the tableViewCell delegates the touch event to its subviews which causes the textfield to handle the touch in stead of your cell.
To fix this:
Set the UITextfield's userinteractionEnabled property to NO, then when you get the didSelectRowAtIndexPath message you re-enable userInteractionEnabled and call the TextField's becomeFirstResponder. On v2.2 you don't even need to set the userInteractionEnabled flag, I have not tested this with other versions however the documentation is quite clear that you should have this enabled. in the tableViewController you simply need to have the indexpath saved until you get the UIKeyboardDidShow message
Create a delegate for the UITextField that reports back to your tableViewController so that you can set the scrolling offset from there.
register for the keyboard events and then figure out the scrolloffset by checking what textfield is in editing mode
You can set your controller as the delegate of your UITextField, then adjust your table view in either textFieldDidBeginEditing: or textFieldShouldBeginEditing:
I did not find any solutions that work for me in the web. After days of Googling and experimenting, I finally have this issued well nailed. It is a complex bug in Apple iPhone as you will see in the end of this post.
If you ran into an issue like me as follows:
having tableviewcell larger than half of the iphone screen (Do not confused with Apple's UICatalog's examples have a short tableview cell of less than 50 points, not applicable here.),
having more than one uitexfields in the cell or combination of uitextfield and uitextview or uiwebview in the cell,
Tapping between uitextfields and uitextview or uiwebview results in unpredictable scroll position either the clicked uitextfield jumps out of view or covered by the keybaord. It only works the very first time when the keyboard appears in the tableviewcell and not working right subsequently.
I had the major break through after reading posts similar to this one: http://alanduncan.net/old/index.php?q=node/13 They did not get it completely right either. The pain is caused by a bug in UIKeyboard events. When the keyboard first appear, it issue an UIKeyboardWillShowNotification and UIKeybaordDidShowNotification. Theree is a bug in iPhone that somehow the first UIKeyboardWillShowNotification differs from the subsequent UIKeyboardWillShowNotification. The solution is to OBSERVE UIKeyboardDidShowNotification. So when your cell will appear, add the following code
NSNotificationCenter*nc=[NSNotificationCenter defaultCenter];
[nc addObserver:self selectorselector(keyboardDidShow name:UIKeyboardDidShowNotification object:self.window];
In the keyboardDidShow function, we need to scroll the TABLEVIEW, not the tableviewcell as suggested in above post. Or you may see various objects go separate way, not scroll together in one piece.
(void)keyboardDidShow:(NSNotification *)notif
{
//1. see which field is calling the keyboard
CGRect frame;
if([textField_no1 isFirstResponder])
frame=textField_no1.frame;
else if([textField_no2 isFirstResponder])
frame=textField_no2.frame;
else if([textField_no3 isFirstResponder])
frame=textField_no3.frame;
else if([textView isFirstResponder])
frame=textView.frame;
else return;
CGRect rect=self.superview.frame;
//2. figure out how many pixles to scroll up or down to the posistion set by theKeyBoardShowUpHorizon.
//remove the complexity when the tableview has an offset
[((UITableView*)[self.superview).setContentOffset:CGPointMake(0,0) animated:YES];
int pixelsToMove=rect.origin.y+ frame.origin.y-theKeyBoardShowUpHorizon;
//3. move the uitableview, not uitableviewcell
[self moveViewUpOrDownByPixels:pixelsToMove];
}
- (void)moveViewUpOrDownByPixels:(int)pixels
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.6];
//find the position of the UITableView, the superView of this tableview cell.
CGRect rect=self.superview.frame;
//moves tableview up (when pixels >0) or down (when pixels <0)
rect.origin.y -= pixels;
rect.size.height += pixels;
self.superview.frame = rect;
[UIView commitAnimations];
}
To restore the tableView back, you need to add observer on UIKeyboardDidHideNotification (not UIKeyboardWillHideNotification as suggested by other posts, to avoid flickering) where you tableviewcell appears every time and put back the tableview to where it was.
[nc addObserver:self selectorselector(keyboarDidHide) name:UIKeyboardDidHideNotification object:nil];
- (void)keyboardDidHideNSNotification*)notif
{
//we have moved the tableview by number of pixels reflected in (self.superview.frame.origin.y). We need to move it back
[self moveViewUpOrDownByPixels:self.superview.frame.origin.y];
}
Do not forget to remove both of the observesr when your cell disappear by [[NSNotificationCenter defaultCenter] removeObserver:...
That is all it takes. I hope Apple iPhone team one day will resolve this issue, maybe in 4.0 in a few months.
I discovered that it's actually pretty easy to do this.
The UITextField delegate method textFieldDidBeginEditing will give you the text field, which you can then map to an indexPath using:
self.currentIndexPath = [self.tableView indexPathForRowAtPoint:textField.frame.origin];
Then you can scroll the cell into view (i.e. in your UIKeyboardDidShowNotification keyboard notification handler):
[self.tableView scrollToRowAtIndexPath:self.currentIndexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
I've found a solution.
Open .xib file in interface builder.
Select the table view
From IB Menu select Tools->Size Inspector
On Scroll View Size Section, modify Inset -> Bottom value to 100, 150 ,250 depending how big is your table view.
Code
-(void) textFieldDidBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *) [[textField superview] superview];
[self.tableView scrollToRowAtIndexPath:[tableView indexPathForCell:cell]
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
didSelectRowAtIndexPath won't be called for UITextField embedded cells; hence, scroll logic needs to be elsewhere.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:indexPath];
[_tableView setContentOffset:CGPointMake(0, cell.frame.size.height) animated:YES];
}
Make sure to wire textField delegate to self
Register for UIKeyboardWillShowNotification and UIKeyboardWillHideNotification, then adjust your view as necessary in the notification handlers. One of the example apps shows how to do this, but I forget which...SQLiteBooks, or maybe EditableDetailView.
I was struggling with this same issue, where I have UITextFields inside of UITableViewCells and couldn't get view to scroll to the field when it was being edited. The core of my solution is below.
The key to this code is the line where the UITextField is created. Instead of hard coding a x and y value in the CGRectMake() function, it uses the x and y from the cell in which its being placed (+/- any offset you want from the edges of the cell as shown below). Hard coding x and y values in the UITextField* gives every cell the same x,y frame position for every UITextField* (it apparently is overridden by the cells frame when its displayed) so when you invoke the 'scrollRectToVisible' code it doesn't seem to have the correct coordinates to which it should scroll.
1) create cell, and add UITextField* to the cell using cell's frame x and y values (I'm including offsets here which are optional
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell;
cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"UITableViewCell"] autorelease];
//this is the critical part: make sure your UITextField* frame is based on the frame of the cell in which it's being placed.
UITextField* txtField = [[UITextField alloc] initWithFrame:CGRectMake(cell.frame.origin.x+20, cell.frame.origin.y+9, 280, 31)];
txtField.delegate = self;
[cell addSubview:txtField];
return cell;
}
2) adjust scroll view in textFieldDidBeginEditing
-(void) textFieldDidBeginEditing:(UITextField *)textField {
CGRect textFieldRect = [textField frame];
[self.tableView scrollRectToVisible:textFieldRect animated:YES];
}
The problem is aggravated by the fact that there is no simple way to find out whether user tapped on text field or it was activated via becomeFirstResponder.
The most elegant solution I could come up with was to implement a hitTest:withEvent: on cell subclass and basically pretend that text field does not exist until cell is selected.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if(view == self.textField && !self.selected) {
return self;
}
return view;
}
tableView:didSelectRowAtIndexPath: then should manually make text field a first responder.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TextFieldCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell.textField becomeFirstResponder]
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
Finally, we have to deselect the row when we finish editing. This can be done via UITextField delegate or via keyboard notification, whatever you prefer.
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.view endEditing:YES];
}
we have one controller called TPKeyboardAvoiding, it handled everything about dynamic auto scrolling for tableview and scrollview.
you can download sample code from below code.
https://github.com/NarayanaRao35/TPKeyboardAvoiding