I've been trying to implement this toolbar, where only the 'Next' button is enabled when the top textField is the firstResponder and only the 'Previous' button is enabled when the bottom textField is the firstResponder.
It kind of works, but what keeps happening is I need to tap the 'Previous'/'Next' buttons twice each time to enable/disable the opposing button.
Am I missing something in the responder chain that's making this happen?
Here's my code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.topText becomeFirstResponder];
}
- (UIToolbar *)keyboardToolBar {
UIToolbar *toolbar = [[UIToolbar alloc] init];
[toolbar setBarStyle:UIBarStyleBlackTranslucent];
[toolbar sizeToFit];
UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems:@[@"Previous", @"Next"]];
[segControl setSegmentedControlStyle:UISegmentedControlStyleBar];
segControl.momentary = YES;
segControl.highlighted = YES;
[segControl addTarget:self action:@selector(changeRow:) forControlEvents:(UIControlEventValueChanged)];
[segControl setEnabled:NO forSegmentAtIndex:0];
UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] initWithCustomView:segControl];
NSArray *itemsArray = @[nextButton];
[toolbar setItems:itemsArray];
return toolbar;
}
- (void)changeRow:(id)sender {
int idx = [sender selectedSegmentIndex];
if (idx == 1) {
[sender setEnabled:NO forSegmentAtIndex:1];
[sender setEnabled:YES forSegmentAtIndex:0];
self.topText.text = @"Top one";
[self.bottomText becomeFirstResponder];
}
else {
[sender setEnabled:NO forSegmentAtIndex:0];
[sender setEnabled:YES forSegmentAtIndex:1];
self.bottomText.text =@"Bottom one";
[self.topText becomeFirstResponder];
}
}
-(void)textFieldDidBeginEditing:(UITextField *)textField {
if (!textField.inputAccessoryView) {
textField.inputAccessoryView = [self keyboardToolBar];
}
}
Here is a UIViewController
extension that I use whenever I need a group of UITextField
s to provide navigation via input accessory. No need to use UITextField
delegation with this approach, and adding the behavior to multiple forms becomes a single-liner. Also supports the 'Done' button to dismiss.
extension UIViewController {
func addInputAccessoryForTextFields(textFields: [UITextField], dismissable: Bool = true, previousNextable: Bool = false) {
for (index, textField) in textFields.enumerated() {
let toolbar: UIToolbar = UIToolbar()
toolbar.sizeToFit()
var items = [UIBarButtonItem]()
if previousNextable {
let previousButton = UIBarButtonItem(image: UIImage(named: "Backward Arrow"), style: .plain, target: nil, action: nil)
previousButton.width = 30
if textField == textFields.first {
previousButton.isEnabled = false
} else {
previousButton.target = textFields[index - 1]
previousButton.action = #selector(UITextField.becomeFirstResponder)
}
let nextButton = UIBarButtonItem(image: UIImage(named: "Forward Arrow"), style: .plain, target: nil, action: nil)
nextButton.width = 30
if textField == textFields.last {
nextButton.isEnabled = false
} else {
nextButton.target = textFields[index + 1]
nextButton.action = #selector(UITextField.becomeFirstResponder)
}
items.append(contentsOf: [previousButton, nextButton])
}
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: view, action: #selector(UIView.endEditing))
items.append(contentsOf: [spacer, doneButton])
toolbar.setItems(items, animated: false)
textField.inputAccessoryView = toolbar
}
}
}
example:
let field1 = UITextField()
let field2 = UITextField()
addInputAccessoryForTextFields([field1, field2], dismissable: true, previousNextable: true)
Here's a reasonable arrow icon.
Swift:
lazy var inputToolbar: UIToolbar = {
var toolbar = UIToolbar()
toolbar.barStyle = .default
toolbar.translucent = true
toolbar.sizeToFit()
var doneButton = UIBarButtonItem(title: "Done", style: .bordered, target: self, action: "inputToolbarDonePressed")
var flexibleSpaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
var fixedSpaceButton = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
var nextButton = UIBarButtonItem(image: UIImage(named: "keyboardPreviousButton"), style: .bordered, target: self, action: "keyboardNextButton")
nextButton.width = 50.0
var previousButton = UIBarButtonItem(image: UIImage(named: "keyboardNextButton"), style: .Bordered, target: self, action: "keyboardPreviousButton")
toolbar.setItems([fixedSpaceButton, nextButton, fixedSpaceButton, previousButton, flexibleSpaceButton, doneButton], animated: false)
toolbar.userInteractionEnabled = true
return toolbar
}()
In UITextFieldDelegate
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
textField.inputAccessoryView = inputToolbar
return true
}
Okay, after looking at the brilliant BSKeyboardControls, I tried implementing the enabling and disabling of the segmented control in textFieldDidBeginEditing
, instead of where my @selector
was. I also introduced a variable for the segmented control.
It works now.
Here's the amended code snippet:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.topText becomeFirstResponder];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (UIToolbar *)keyboardToolBar {
UIToolbar *toolbar = [[UIToolbar alloc] init];
[toolbar setBarStyle:UIBarStyleBlackTranslucent];
[toolbar sizeToFit];
self.segControl = [[UISegmentedControl alloc] initWithItems:@[@"Previous", @"Next"]];
[self.segControl setSegmentedControlStyle:UISegmentedControlStyleBar];
self.segControl.momentary = YES;
[self.segControl addTarget:self action:@selector(changeRow:) forControlEvents:(UIControlEventValueChanged)];
[self.segControl setEnabled:NO forSegmentAtIndex:0];
UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] initWithCustomView:self.segControl];
NSArray *itemsArray = @[nextButton];
[toolbar setItems:itemsArray];
return toolbar;
}
- (void)changeRow:(id)sender {
int idx = [sender selectedSegmentIndex];
if (idx) {
self.topText.text = @"Top one";
[self.bottomText becomeFirstResponder];
}
else {
self.bottomText.text =@"Bottom one";
[self.topText becomeFirstResponder];
}
}
-(void)textFieldDidBeginEditing:(UITextField *)textField {
if (!textField.inputAccessoryView) {
textField.inputAccessoryView = [self keyboardToolBar];
}
if (textField.tag) {
[self.segControl setEnabled:NO forSegmentAtIndex:1];
[self.segControl setEnabled:YES forSegmentAtIndex:0];
}
}
My suggestion here is "don't reinvent the wheel".
Having Prev
and Next
button over a keyboard for switching between UITextView
s is so common that you can find many good implementations ready to use.
Check out BSKeyboardControl, for instance.
Updated for Swift 3.0
lazy var inputToolbar: UIToolbar = {
var toolbar = UIToolbar()
toolbar.barStyle = .default
toolbar.isTranslucent = true
toolbar.sizeToFit()
var doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(ContactViewController.inputToolbarDonePressed))
var flexibleSpaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
var fixedSpaceButton = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
var nextButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(ContactViewController.keyboardNextButton))
var previousButton = UIBarButtonItem(title: "Previous", style: .plain, target: self, action: #selector(ContactViewController.keyboardPreviousButton))
toolbar.setItems([fixedSpaceButton, previousButton, fixedSpaceButton, nextButton, flexibleSpaceButton, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
return toolbar
}()
And then:
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
textField.inputAccessoryView = inputToolbar
return true
}
Remember to change thange "ContactViewController" to the name of your View Controller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With