I am trying to implement "previous," "next," and "done" buttons for a series of UITextFields, each of which is contained in a UITableViewCell in a grouped UITableView. I hold on to the UITextFields in an NSMutableArray, and keep an integer pointing to the UITextField that is currently active. Here are the two selectors that get fired when the Previous and Next buttons are tapped, respectively.
-(IBAction)didSelectPreviousButton:(id)sender
{
if ((textFieldIndex - 1) >= 0) {
UITextField *currentField = [self.testTextFields objectAtIndex:(textFieldIndex)];
UITextField *nextTextField = [self.testTextFields objectAtIndex:(--textFieldIndex)];
BOOL result = [nextTextField becomeFirstResponder];
NSLog([NSString stringWithFormat:#"currentField's window: %#", currentField.window]);
NSLog([NSString stringWithFormat:#"nextTextField's window: %#", nextTextField.window]);
} else {
[self dismissKeyboard:sender];
}
}
-(IBAction)didSelectNextButton:(id)sender
{
if ((textFieldIndex + 1) < [self.inspectionItemSpec.numberOfTests intValue]) {
UITextField *currentField = [self.testTextFields objectAtIndex:(textFieldIndex)];
UITextField *nextTextField = [self.testTextFields objectAtIndex:(++textFieldIndex)];
BOOL result = [nextTextField becomeFirstResponder];
NSLog([NSString stringWithFormat:#"currentField's window: %#", currentField.window]);
NSLog([NSString stringWithFormat:#"nextTextField's window: %#", nextTextField.window]);
} else {
[self dismissKeyboard:sender];
}
}
As you can see, I am logging the window property of the current & next text field, and in the didSelectNextButton, everything is correct. However, in didSelectPreviousButton, nextTextField.window is always nil. Why would this be happening?
(Note that the previous button is enabled only after the user has tapped the next button once.)
This may be because each UITextField is in a UITableViewCell while also being referenced in self.testTextFields. Because of the way cells are re-used by tables in iOS, you could (and probably will) end up in a situation where the next text field in your array is not the text field in the next visible row in the table.
If you post your tableView:cellForRowAtIndexPath: code, that may make the problem apparent.
The window is the root of the view hierarchy. If the window property is nil, the view hasn't been added to the view hierarchy via something like
[someViewInTheViewHierarchy addSubview:yourView].
Related
To clarify my question, my program has three lightbulb on the screen (Customized UIButton)
when any lightbulb is pressed, I programatically generate a UIView with a switch on it
when I turn on the switch, corresponding lightbulb will light up (change its background image)
However, I have trouble accessing this UISwitch since I can't declare it publicly
My code goes something like this:
#property buttonA;
#synthesize buttonA;//all three buttons have their background image set to 'off.png'
- (IBAction)lightBulbPressed:(UIButton *)sender
{
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(1,1, 64, 64)];
UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(0,0,64,64)];
[mySwitch addTarget:self action:#selector(onOrOff) forControlEvents:UIControlEventValueChanged];
[myView addSubview:mySwitch]
[self.view addSubview:myView];
}
So what troubles me is how to program the selector onOrOff, so that it knows which switch is being touched and change the background image of corresponding button accordingly.
Think about your method:
- (IBAction)lightBulbPressed:(UIButton *)sender {
// your method
}
You already know who called it. This piece of information is stored in sender.
So you can save it and use later in onOrOff
By the way, if you are using UISwitch you have to check
UIControlEventValueChanged
and not UIControlEventTouchUpInside.
EDIT: To pass your sender you can store its value to a NSString *buttonTapped declared in your .h file
- (IBAction)lightBulbPressed:(UIButton *)sender {
if (sender == bttOne) {
buttonTapped = #"ButtonOneTapped";
} else if (sender == bttTwo) {
buttonTapped = #"ButtonTwoTapped";
} else if (sender == bttThree) {
buttonTapped = #"ButtonThreeTapped";
}
// your method
}
- (void)onOrOff {
if ([buttonTapped isEqualToString:#"ButtonOneTapped"]) {
// Button One
} else if ([buttonTapped isEqualToString:#"ButtonTwoTapped"]) {
// Button Two
} else if ([buttonTapped isEqualToString:#"ButtonThreeTapped"]) {
// Button Three
}
}
One way to do so, is taht you give them distinct tag numbers in IB, and in - (IBAction)lightBulbPressed:(UIButton *)sender method, get their tag. e.g. NSInteger pressedButtonTag = [sender tag];, and go from there.
Also, instead of alloc/init myView every time user presses a button, you can add that view in IB, add the switch to it, put in the hierarchy of the owner but not the view, and set an outlet to it in .h. Call it whenever you need it, and again, access the switch by tag e.g. ( UISwitch *mySwitch = (UISwitch *)[myView viewWithTag:kSwitchTag]; ) and do whatever you want to do (on or off), add it to the subview and remove it later. This is more efficient.
I have this delegate method below that is setting a few fields in my tableview. What happens is when one of the tableview cells is pressed it loads a subview with a bunch more tableview cells when one is selected is pops the view from the viewcontrollerand loads the value that was selected into the parentviews cell that was initially selected.
This main cell effects what I am able to set in the second cell. so when it is clicked it shows data related to the first selection.
I am enabling my user to go back to any of the cells to change their selection.. or maybe they might go back thinking they want to change but don't.
In which case for the second tableviewcell of my parentview will either need to change if the first cell changes or stay the same if the value of the first cell dosnt change.
I have this delegate that is used with the first cell, and it is where I am trying to control the value of the secondcell, as shown below.
- (void) setManufactureSearchFields:(NSArray *)arrayValues withIndexPath:(NSIndexPath *)myIndexPath
{
manufactureSearchObjectString = [[arrayValues valueForKey:#"MANUFACTURER"] objectAtIndex:0];
manufactureIdString = [[arrayValues valueForKey:#"MANUFACTURERID"] objectAtIndex:0]; //Restricts Models dataset
manufactureResultIndexPath = myIndexPath;
[self.tableView reloadData]; //reloads the tabels so you can see the value in the tableViewCell.
//need some sort of if statment here so that if the back button is pressed modelSearchObjectString is not changed..
if (oldManufactureSearchObjectString != manufactureSearchObjectString) {
modelResultIndexPath = NULL;
modelSearchObjectString = #"empty";
oldManufactureSearchObjectString = manufactureSearchObjectString;
}
}
The thing being is that it enters the if statment every time even if the same cell is selected for the first cell.. (in which case is should not enter the if statement.
I thought I could do this by checking oldMan vrs man if != then go through and set the second cell stuff and then pass man to oldMan so next time you use the first cell it has a value to comapre against. but obviously this dosn't seem to be working. Is my logic bad or is it something in my code.
this is how I set these values in .h they are all #synthesised
//...
NSString *manufactureSearchObjectString;
NSString *oldManufactureSearchObjectString;
NSString *manufactureIdString;
NSIndexPath *manufactureResultIndexPath;
//...
#property (copy) NSString *manufactureSearchObjectString;
#property (copy) NSString *oldManufactureSearchObjectString;
#property (copy) NSString *manufactureIdString;
#property (copy) NSIndexPath *manufactureResultIndexPath;
//...
You have to compare the value of the NSStrings as below:
BOOL isEqual = [aString isEqualToString:bString]; // provide aString and bString
if (!isEqual)
{
// code
}
What you've done is to compare the pointers, which can be different for strings of the same value.
I have noticed, in one of my views in an iPad app I am building the next button on the keyboard goes through all the UITextFields from left to right down the screen.
Is it possible somehow to make it go top to bottom then right, top to bottom?
So say I have to two long columns of text fields, I wan to go top to bottom not left to right, make sense?
Any help appreciated, thanks.
I don't think there is a way through IB, but you can do this way in code. You're not actually tabbing, you'd be using the return key.
Put this in your UITextField's delegate:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
BOOL shouldChangeText = YES;
if ([text isEqualToString:#"\n"]) {
// Find the next entry field
BOOL isLastField = YES;
for (UIView *view in [self entryFields]) {
if (view.tag == (textView.tag + 1)) {
[view becomeFirstResponder];
isLastField = NO;
break;
}
}
if (isLastField) {
[textView resignFirstResponder];
}
shouldChangeText = NO;
}
return shouldChangeText;
}
Found here: http://iphoneincubator.com/blog/tag/uitextfield
You'll want to implement UITextFieldDelegate's - (BOOL)textFieldShouldReturn:(UITextField *)textField method. An example of how to use this method to control the order is in this question.
I would like to elaborate on #sprocket's answer addressing the same issue. Just because something works out of the box doesn't mean you should stop thinking about a better way -- or even the right way -- of doing something. As he noticed the behavior is undocumented but fits our needs most of the time.
This wasn't enough for me though. Think of a RTL language and tabs would still tab left-to-right, not to mention the behavior is entirely different from simulator to device (device doesn't focus the first input upon tab). Most importantly though, Apple's undocumented implementation seems to only consider views currently installed in the view hierarchy.
Think of a form in form of (no pun intended) a table view. Each cell holds a single control, hence not all form elements may be visible at the same time. Apple would just cycle back up once you reached the bottommost (on screen!) control, instead of scrolling further down. This behavior is most definitely not what we desire.
So here's what I've come up with. Your form should be managed by a view controller, and view controllers are part of the responder chain. So you're perfectly free to implement the following methods:
#pragma mark - Key Commands
- (NSArray *)keyCommands
{
static NSArray *commands;
static dispatch_once_t once;
dispatch_once(&once, ^{
UIKeyCommand *const forward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:0 action:#selector(tabForward:)];
UIKeyCommand *const backward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:UIKeyModifierShift action:#selector(tabBackward:)];
commands = #[forward, backward];
});
return commands;
}
- (void)tabForward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.firstObject becomeFirstResponder];
}
- (void)tabBackward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls.reverseObjectEnumerator) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.lastObject becomeFirstResponder];
}
Additional logic for scrolling offscreen responders visible beforehand may apply.
Another advantage of this approach is that you don't need to subclass all kinds of controls you may want to display (like UITextFields) but can instead manage the logic at controller level, where, let's be honest, is the right place to do so.
In the Contacts app's add/edit view, if you hit the 'Return' key in a field, you cycle through each of the UITextFields. These fields seem to be inside a UITableViewCell. What's a good way to do this?
When I have a series of UITextFields not inside a Table, I can invoke
[self.view viewWithTag:tag] to get a list of the views and the use that to cycle through them. But within a table, I only get one view and I'm not sure how to convert this over.
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
// Find the next entry field
for (UIView *view in [self entryFields]) {
if (view.tag == (textField.tag + 1)) {
[view becomeFirstResponder];
break;
}
}
return NO;
}
/*
Returns an array of all data entry fields in the view.
Fields are ordered by tag, and only fields with tag > 0 are included.
Returned fields are guaranteed to be a subclass of UIResponder.
From: http://iphoneincubator.com/blog/tag/uitextfield
*/
- (NSArray *)entryFields {
if (!entryFields) {
self.entryFields = [[NSMutableArray alloc] init];
NSInteger tag = 1;
UIView *aView;
while (aView = [self.view viewWithTag:tag]) {
if (aView && [[aView class] isSubclassOfClass:[UIResponder class]]) {
[entryFields addObject:aView];
}
tag++;
}
}
return entryFields;
}
Just discovered this while browsing for something myself - sorry for the delay.
If you haven't resolved the issue, you could create an array of uitextfield objects in the same order presented on screen. As you create the table, set the .tag property of the text field. Within the text field delegate method - read the tag, increment by 1 (to move to the next field), and issue the becomefirstresponder call.
You could also look at the textFieldDidEndEditing delegate method.
I have a UITableView that has a disclosure button on every row. When the table is put into edit mode and the the Deletion control is pressed ("-" sign), the Delete Button shows, however the disclosure button is not replaced, but instead just slides to the left of the delete button.
The apple UITableView guide explains the delegates for everything it seems except for the delegate that is called when the "-" sign is pressed, but before the delete button is displayed.
I would just like to suppress the disclosure indicator while the delete button is shown.
I'm guessing that I am missing something... I have set the setHidesAccessoryWhenEditing:NO
on the cells of the table so that the indicator is displayed to indicate to the user that if they select the row, they can edit it...
The behavior I am trying to copy is done in the contacts app when a contact is edited. Any help would be greatly appreciated...
Thanks, Greg
The standard way to do this is to use cell.hidesAccessoryWhenEditing = YES, and that editing is a modal action in which navigation is typically disabled.
The Contacts application actually uses custom table cells, and I wouldn't be surprised if it didn't so much use an accessory as have an image located on the cell's right edge, judging by its behaviour.
If you want to know when the delete button appears, I'd suggest that you try installing a Key-Value observer on the cell's showingDeleteConfirmation property, like so:
[cell addObserver: self forKeyPath: #"showingDeleteConfirmation"
options: NSKeyValueObservingOptionNew context: NULL];
Then you implement the observer callback method:
- (void)observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context
{
if ( [keyPath isEqualToString: #"showingDeleteConfirmation"] )
{
UITableViewCell * cell = (UITableViewCell *) object;
BOOL isShowing = [[change objectForKey: NSKeyValueChangeNewKey] boolValue];
if ( isShowing == NO )
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
else
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
I should note that I'm not sure whether the UITableViewCell class is KVO-compliant for this property, but it's got to be worth a try…
Or when you are creating your cell you can define the accessory type: cell.editingAccessoryType = UITableViewCellAccessoryNone;
The disclosure indicator is managed by tableView:accessoryTypeForRowWithIndexPath: so maybe you could change the accessory type while in editing mode.
I believe there's a tableView:accessoryButtonTappedForRowWithIndexPath:, maybe there you can note that you're going to edit mode and then change what the tableView:accessoryTypeForRowWithIndexPath: returns for each row - no accessory when in edit mode.
I would give it a try.
Here is a calling sequence (not sure if that helps) description/tutorial/examples from Apple reference about how to go about Inserting and Deleting Rows in Editing Mode.
My comment to Jim's solution didn't come across very well... Here is the version that solved the problem for me... Thanks again Jim!
- (void)observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context
{
UITableViewCell * cell = object;
if ( [keyPath isEqualToString: #"showingDeleteConfirmation"] )
{
BOOL isShowing = [[change objectForKey: NSKeyValueChangeNewKey] boolValue];
if ( !isShowing )
{
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
}