How can I enable/disable a UITextField's inputAccessoryView buttons? - ios

I've created a UIToolBar as an inputAccessoryView with a next and previous buttons that will cycle through the textFields in my view controller.
I've created a category on UITextField based on the third SO answer on this page which adds a property to the textField that points to the next/previous textField.
I can get it to cycle through the textFields both forward and backward, but only once, and then my buttons are permanently disabled. Also, when the last textField is in focus, I still need to tap the next button one extra time (4 taps for 3 textFields) to have it disable the next button — same with the previous button, I have to tap back once when I'm in the first textField.
// ViewController.h
#interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, UIPopoverControllerDelegate> {
__weak IBOutlet UITextField *valueField;
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *serialNumberField;
}
#property (nonatomic, strong) UITextField *currentTextField;
- (IBAction)nextTextField:(id)sender;
- (IBAction)prevTextField:(id)sender;
// ViewController.m
- (void)viewDidLoad {
//...
nameField.delegate = self;
nameField.nextTextField = serialNumberField;
nameField.prevTextField = nil;
serialNumberField.delegate = self;
serialNumberField.nextTextField = valueField;
serialNumberField.prevTextField = nameField;
valueField.delegate = self;
valueField.prevTextField = serialNumberField;
valueField.nextTextField = nil;
//...
}
- (void)viewWillAppear:(BOOL)animated {
//UIToolBar for inputAccessoryView
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
UIBarButtonItem *nextField = [[UIBarButtonItem alloc] initWithTitle:#"\U00003009"
style:UIBarButtonItemStylePlain
target:self
action:#selector(nextTextField:)];
UIBarButtonItem *prevField = [[UIBarButtonItem alloc] initWithTitle:#"\U00003008"
style:UIBarButtonItemStylePlain
target:self
action:#selector(prevTextField:)];
UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(backgroundTapped:)];
NSArray *toolBarButtons = #[prevField, nextField, space, done];
toolBar.items = toolBarButtons;
nameField.inputAccessoryView = toolBar;
valueField.inputAccessoryView = toolBar;
serialNumberField.inputAccessoryView = toolBar;
}
- (IBAction)nextTextField:(id)sender {
UITextField *next = self.currentTextField.nextTextField;
if (!next) {
[sender setEnabled:NO];
} else {
[sender setEnabled:YES];
[next becomeFirstResponder];
}
}
- (IBAction)prevTextField:(id)sender {
UITextField *prev = self.currentTextField.prevTextField;
if (!prev) {
[sender setEnabled:NO];
} else {
[sender setEnabled:YES];
[prev becomeFirstResponder];
}
}

I think part of the problem is that you are handling the enabling/disabling of the bar buttons in a method that is only called once one of the bar buttons has been tapped. It would be better to set the barbuttonitems as properties of your view controller (so you can enable/disable them when you want to), and then handle the enabling/disabling of the bar button items within the UITextField's 'textFieldShouldBeginEditing' delegate method.
So, something like this:
- (void)viewWillAppear:(BOOL)animated {
//UIToolBar for inputAccessoryView
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
self.moveToNextFieldButton = [[UIBarButtonItem alloc] initWithTitle:#"\U00003009"
style:UIBarButtonItemStylePlain
target:self
action:#selector(nextTextField:)];
self.moveToPrevFieldButton = [[UIBarButtonItem alloc] initWithTitle:#"\U00003008"
style:UIBarButtonItemStylePlain
target:self
action:#selector(prevTextField:)];
UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(backgroundTapped:)];
NSArray *toolBarButtons = #[self.moveToPrevFieldButton, self.moveToNextFieldButton, space, done];
toolBar.items = toolBarButtons;
nameField.inputAccessoryView = toolBar;
valueField.inputAccessoryView = toolBar;
serialNumberField.inputAccessoryView = toolBar;
}
-(BOOL)textFieldShouldBeginEditing:(UITextField*)textField{
UITextField *next = textField.nextTextField;
UITextField *prev = textField.prevTextField;
self.moveToNextFieldButton.enabled = next != nil;
self.moveToPrevFieldButton.enabled = prev != nil;
return YES;
}
- (IBAction)nextTextField:(id)sender {
UITextField *next = self.currentTextField.nextTextField;
if (next) {
[next becomeFirstResponder];
}
}
- (IBAction)prevTextField:(id)sender {
UITextField *prev = self.currentTextField.prevTextField;
if (prev) {
[prev becomeFirstResponder];
}
}

Related

UIBarButtonSystemItemSave width unequal to UIBarButtonSystemItemCancel

I have seven UIBarButtonItems in a UIToolbar.
UIBarButtonSystemItemFixedSpace
UIBarButtonSystemItemCancel
UIBarButtonSystemItemFlexibleSpace
UIBarButton with custom text
UIBarButtonSystemItemFlexibleSpace
UIBarButtonSystemItemSave
UIBarButtonSystemItemFixedSpace
Everything is laying out as I would normally expect it to except for the UIBarButtonSystemItemSave item. It has an extra space next to it.
I added borders to each subview inside of the toolbar to try to figure out what was happening but still couldn't quite figure it out.
Maybe there was something happening with the order of the buttons so I switched the items around in order, but still couldn't figure it out.
Just in case it was something about the way I created the button, I changed the init option to UIBarButtonSystemItemCancel for both Save and Cancel.
Here's the code that I've been using.
- (UIToolbar *)headerToolbar {
if (!_headerToolbar) {
_headerToolbar = [[UIToolbar alloc] initWithFrame:self.headerToolbarFrame];
_headerToolbar.barTintColor = [[OVThemeManager shared] blueColor];
if (UIApplication.sharedApplication.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) {
_headerToolbar.items = #[[self fixedSpace:buttonBufferWidth],
self.cancelButton,
self.flexibleSpace,
self.titleButton,
self.flexibleSpace,
self.saveButton,
[self fixedSpace:buttonBufferWidth]];
} else {
_headerToolbar.items = #[[self fixedSpace:buttonBufferWidth],
self.saveButton,
self.flexibleSpace,
self.titleButton,
self.flexibleSpace,
self.cancelButton,
[self fixedSpace:buttonBufferWidth]];
}
[self borderSubviews:self.headerToolbar];
}
return _headerToolbar;
}
- (void)borderSubviews:(UIView *)superview {
superview.layer.borderWidth = 1.0f;
superview.layer.borderColor = [UIColor colorWithRed:0.001f *(float)(arc4random()%1000)
green:0.001f *(float)(arc4random()%1000)
blue:0.001f *(float)(arc4random()%1000)
alpha:1.0f].CGColor;
for (UIView *subview in superview.subviews) {
[self borderSubviews:subview];
}
}
- (CGRect)headerToolbarFrame {
CGRect frame = self.view.bounds;
frame.size.height = headerToolbarHeight;
return frame;
}
- (UIBarButtonItem *)saveButton {
if (!_saveButton) {
_saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(saveButtonTouched:)];
_saveButton.tintColor = [[OVThemeManager shared] blueColor];
}
return _saveButton;
}
- (UIBarButtonItem *)cancelButton {
if (!_cancelButton) {
_cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancelButtonTouched:)];
_cancelButton.tintColor = [[OVThemeManager shared] blueColor];
}
return _cancelButton;
}
- (UIBarButtonItem *)titleButton {
if (!_titleButton) {
_titleButton = [[UIBarButtonItem alloc] initWithTitle:#"Other button".localized
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_titleButton.tintColor = [[OVThemeManager shared] blueColor];
}
return _titleButton;
}
- (UIBarButtonItem *)flexibleSpace {
return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
}
- (UIBarButtonItem *)fixedSpace:(CGFloat)width {
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil
action:nil];
fixedSpace.width = width;
return fixedSpace;
}

UIPickerView 2nd component populates blank the second time the picker view is shown

I have a UIPickerView with 2 components. Selecting 1st components populates 2nd component. Looks like this:
Test Case:
Select any option in Component 0 and then pick any episode in Component 1 of picker and click Fitler which hides the picker.
Show the Picker again to select something else. The first component loads properly. The second component is blank like this screenshot:
Here's the code:
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view{
UILabel* tView = (UILabel*)view;
if (!tView){
tView = [[UILabel alloc] init];
tView.textAlignment = NSTextAlignmentCenter;
// Setup label properties - frame, font, colors etc
//...
//adjustsFontSizeToFitWidth property to YES
if(pickerView == self.seasonEpiPickerView && component == 1){
tView.font= [UIFont systemFontOfSize:12];
tView.adjustsFontSizeToFitWidth = YES;
}
}
// Fill the label text here
if (pickerView == self.seasonEpiPickerView)
{
if (component == 0) {
if (row == 0) {
item = #"All";
} else {
//NSLog(#"c1:%ld", (long)row);
item = [NSString stringWithFormat:#"S%#", ((Season *)[self.media.seasonManager.seasonArray objectAtIndex:row-1]).seasonNr];
}
} else if (component == 1){
if(self.media.seasonManager && self.media.seasonManager.seasonArray && [self.media.seasonManager.seasonArray count]>0 ) {
Season* tempSeason = self.media.seasonManager.seasonArray[[self.seasonEpiPickerView selectedRowInComponent:0]-1];
item = [NSString stringWithFormat:#"%#.%# \"%#\"", tempSeason.seasonNr, [NSString stringWithFormat: #"%02d", [((Episode *)[tempSeason.episodeManager.episodeArray objectAtIndex:row]).episodeNr intValue]], ((Episode *)[tempSeason.episodeManager.episodeArray objectAtIndex:row]).title];
NSLog(#"c2:%ld:%#", (long)row, item);
}
}
}
tView.text = item;
return tView;
}
Edit:
Here's how I create the Picker in viewDidLoad
self.seasonPickerViewTextField = [[UITextField alloc] initWithFrame:CGRectZero];
[self.view addSubview:self.seasonPickerViewTextField];
self.seasonEpiPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
self.seasonEpiPickerView.showsSelectionIndicator = YES;
self.seasonEpiPickerView.dataSource = self;
self.seasonEpiPickerView.delegate = self;
//add the toolbar for the season picker
UIToolbar *toolBar= [[UIToolbar alloc] initWithFrame:CGRectMake(0,0,screenWidth,44)];
[toolBar setBarStyle:UIBarStyleBlack];
UIBarButtonItem *barButtonDone = [[UIBarButtonItem alloc] initWithTitle:#"Filter " style:UIBarButtonItemStylePlain target:self action:#selector(filterByEpisode:)];
UIBarButtonItem *flexible = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
UIBarButtonItem *barCancelButton = [[UIBarButtonItem alloc] initWithTitle:#" Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(cancelPicker:)];
toolBar.items = [NSArray arrayWithObjects: barCancelButton, flexible, barButtonDone, nil];
self.seasonPickerViewTextField.inputAccessoryView = toolBar;
// set change the inputView (default is keyboard) to UIPickerView
self.seasonPickerViewTextField.inputView = self.seasonEpiPickerView;
EDIT 2
doing a delayed reload when the picker view is presented the second time reloads the second component correctly. This seems very hawkish.
- (IBAction)pickEpisodeButton:(id)sender
{
[self.seasonPickerViewTextField becomeFirstResponder];
/* None of the following commented lines of code have any effect*/
//[self.seasonEpiPickerView selectRow:1 inComponent:0 animated:NO];
//[self.seasonEpiPickerView selectRow:selectedSeason inComponent:0 animated:YES];
//[self.seasonEpiPickerView selectRow:selectedEpisode inComponent:1 animated:YES];
//[self.seasonEpiPickerView reloadComponent:1];
//set a 1.5 sec timer to go to the next screen
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(loadSecondComponent) userInfo:nil repeats:NO];
}
-(void)loadSecondComponent {
[self.seasonEpiPickerView reloadComponent:1];
}

Change items in navigation bar on device rotation

I have custom UIBarButton items added to UINavigationBar as rightbarbuttonitems. I add two more items in UIBarButton when device goes to landscape mode. I am getting rotation call and I am correctly removing items from UIBarbuttons array based on device orientation, but my navigationbar item set never gets updates.
If i start with portrait mode, it shows what it suppose to. When I rotate to landscape new items are not added to navigation bar and vice versa extra item won't go when device go to portrait.
I am not sure how I can change navigationbaritems on device rotation. Per me I am doing all things correct.
-(void)setUpOnRotation:(BOOL)toPortrait{
_searchMessage = [[UILabel alloc] initWithFrame:CGRectMake(3.0, 0, 110, 20)];
_searchMessage.numberOfLines = 2;
_searchMessage.textColor = [UIColor blackColor];
_searchMessage.font = [UIFont systemFontOfSize:8.0];
_searchMessage.text = #"";
_searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 15, 130, 25)];
_searchBar.delegate = self;
_searchBar.showsCancelButton = NO;
UIBarButtonItem* doneButton = [BarButtons doneButtonWithTarget:self action:#selector(cancel)];
UIBarButtonItem *searchActivityItem = [[UIBarButtonItem alloc] initWithCustomView:_searchMessage];
UIBarButtonItem *searchItem = [[UIBarButtonItem alloc] initWithCustomView:_searchBar];
UIBarButtonItem *in = [BarButtons nativeButtonNamed:#"In"
title:#"In"
target:self
action:#selector(In)];
UIBarButtonItem *out = [BarButtons nativeButtonNamed:#"Out"
title:#"Out"
target:self
action:#selector(Out)];
NSMutableArray *itemsSet;
itemsSet = [NSMutableArray arrayWithObjects:in,
out,
searchItem,
searchActivityItem,
doneButton, nil];
if (toPortrait) {
if ([itemsSet containsObject:searchItem] || [itemsSet containsObject:searchActivityItem]) {
[itemsSet removeObject:searchActivityItem];
[itemsSet removeObject:searchItem];
}
}
[_searchBar release];
[searchItem release];
[_searchActivity release];
[_searchMessage release];
[searchActivityView release];
self.navigationItem.rightBarButtonItems = itemsSet;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self setUpOnRotation:UIInterfaceOrientationIsPortrait(toInterfaceOrientation)];
} -(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:animated];
[self setUpOnRotation:UIInterfaceOrientationIsPortrait(self.interfaceOrientation)];}
Note: setUpOnRotaion method is called from willAnimateRotationToInterfaceOrientation and viewWillAppear with UIInterfaceOrientationIsPortrait(self.intefaceOrientation) and it does give me correct result. I have debugged.
In willAnimateRotationToInterfaceOrientation change cehck for orientation
if([uidevice orientaiton]statusbarorientation]){
self.naviogationite.rightbarbuttonitems = #[btn1,btn2,btn3];
}
else{
self.naviogationite.rightbarbuttonitems = #[abtn1,abtn2,abnt3];
}
u can do like this
can u do like this
NSMutableArray *itemsSet;
if(!toPortrait){
itemsSet = [NSMutableArray arrayWithObjects:in,
out,
searchItem,
searchActivityItem,
doneButton, nil];
}
if (toPortrait) {
itemsSet = [NSMutableArray arrayWithObjects:in,
out,
doneButton, nil];
}

Configure "Done" button for multiple picker views

I have a scene in which I want to use picker views to get the input for multiple text boxes. I want each picker view to have a toolbar with a "Done" button that dismisses the picker view. So far I have:
//toolbar with "Done" button for picker views
UIToolbar *pickerToolBar= [[UIToolbar alloc] initWithFrame:CGRectMake(0,0,320,44)];
[pickerToolBar setBarStyle:UIBarStyleBlackOpaque];
UIBarButtonItem *barButtonDone = [[UIBarButtonItem alloc] initWithTitle:#"Done"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(doneWithPicker:)];
pickerToolBar.items = [[NSArray alloc] initWithObjects:barButtonDone,nil];
barButtonDone.tintColor=[UIColor blackColor];
//set up picker views
_pickerOne = [[UIPickerView alloc] init];
_pickerOne.dataSource = self;
_pickerOne.delegate = self;
self.textFieldOne.inputView = self.pickerOne;
self.textFieldOne.inputAccessoryView = pickerToolBar;
What I need is some way for the doneWithPicker method to figure out which text field is currently being edited and call resignFirstResponder.
Use the text field delegate to identify which field is active. (Make sure to set yourself up as the delegate)
#implementation WhateverViewController
{
UITextField *activeTextField;
}
...
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
activeTextField = textField;
}
Then, in the method that is called when your done button is tapped:
[activeTextField resignFirstResponder];

toolbar previous and next button logic

I'm using the following code to move to the next field using the UITextField delegate and also I'm adding a toolbar to the keyboard with the previous, next and ok buttons. The code is working fine.
Like you see the keyboard return button logic is pretty generic, using the UITextField tags, and that's good because I'm gonna use the piece of code all around. Now I will need to write the previous and next buttons logic, and I'm lost. Any ideas?
UPDATE (complete code, with some modifications, thanks to #8vius that spent some time with me in the chat to make it work):
//
// SigninViewController.m
//
#import "SigninViewController.h"
#implementation SigninViewController
#synthesize firstResponder = _firstResponder;
#synthesize toolbar;
#synthesize email;
#synthesize password;
- (void)move:(UIBarButtonItem*)sender {
NSInteger tag = self.firstResponder.tag;
if ([sender.title isEqualToString:#"Anterior"]) {
tag -= 1;
} else if ([sender.title isEqualToString:#"Próximo"]) {
tag += 1;
}
UITextField *nextTextField = (UITextField*)[self.view viewWithTag:tag];
if (nextTextField && tag > 0) {
[nextTextField becomeFirstResponder];
} else {
[self.firstResponder resignFirstResponder];
self.firstResponder = nil;
}
}
- (void)ok:(id)sender {
[self.view endEditing:YES];
self.firstResponder = nil;
}
- (void)textFieldDidBeginEditing:(UITextField*)textField {
self.firstResponder = textField;
}
- (BOOL)textFieldShouldReturn:(UITextField*)textField {
NSInteger tag = textField.tag + 1;
UITextField *nextTextField = (UITextField*)[self.view viewWithTag:tag];
if (nextTextField) {
[nextTextField becomeFirstResponder];
} else {
[textField resignFirstResponder];
self.firstResponder = nil;
}
return NO;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.email.delegate = self;
self.password.delegate = self;
if (self.toolbar == nil)
{
self.toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
UIBarButtonItem* previous = [[UIBarButtonItem alloc] initWithTitle:#"Anterior" style:UIBarButtonItemStyleBordered target:self action:#selector(move:)];
UIBarButtonItem* next = [[UIBarButtonItem alloc] initWithTitle:#"Próximo" style:UIBarButtonItemStyleBordered target:self action:#selector(move:)];
UIBarButtonItem* space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:(UIBarButtonSystemItemFlexibleSpace) target:nil action:nil];
UIBarButtonItem* ok = [[UIBarButtonItem alloc] initWithTitle:#"Ok" style:UIBarButtonItemStyleBordered target:self action:#selector(ok:)];
[self.toolbar setItems:[[NSArray alloc] initWithObjects:previous, next, space, ok, nil]];
[self.toolbar setTranslucent:YES];
[self.toolbar setTintColor:[UIColor blackColor]];
}
for (UIView* view in self.view.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
[(UITextField*)view setInputAccessoryView:toolbar];
}
}
}
- (void)viewDidUnload {
self.email = nil;
self.password = nil;
[super viewDidUnload];
}
#end
It's quite simple, when you load your view you set the tag property on your text fields depending on the order you want them in, then you have to just traverse the tag element on the fields:
- (void)toggleTextfield:(UIBarButtonItem *)sender {
NSInteger nextTag = self.firstResponder.tag;
if ([sender.title isEqualToString:#"Previous"] && nextTag > 1) {
nextTag -= 1
} else if ([sender.title isEqualToString:#"Next"]) {
nextTag += 1;
}
UITextField *nextTextField = (UITextField *)[self.view viewWithTag:nextTag];
if (nextTextField) {
[nextTextField becomeFirstResponder];
}
}
And keep track of who is the first responder:
-(void)textFieldDidBeginEditing:(UITextField *)textField {
self.firstResponder = textField;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
self.firstResponder = nil;
return YES;
}
And when you load your view, you bind the buttons to the toggle action:
UIBarButtonItem *previousButton = [[UIBarButtonItem alloc] initWithTitle:#"Previous"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(toggleTextfield:)];
UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] initWithTitle:#"Next"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(toggleTextfield:)];
In my case, for instance, I set up my text fields inside a table view, so in my cellForRowAtIndexPath method I set the tag property to be the row of the indexPath.
EDIT: You have to set the firstResponder property for it to work.
In your .h file:
#property UIView *firstResponder
In your .m file:
#synthesize firstResponder = _firstResponder;

Resources