22

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];
    }
}
BillySangster
  • 408
  • 1
  • 3
  • 16

7 Answers7

34

Here is a UIViewController extension that I use whenever I need a group of UITextFields 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.

Richard Topchii
  • 7,075
  • 8
  • 48
  • 115
Ben Packard
  • 26,102
  • 25
  • 102
  • 183
17

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
}

Sahil Kapoor
  • 11,183
  • 13
  • 64
  • 87
11

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];
    }
}
BillySangster
  • 408
  • 1
  • 3
  • 16
7

My suggestion here is "don't reinvent the wheel".

Having Prev and Next button over a keyboard for switching between UITextViews is so common that you can find many good implementations ready to use.

Check out BSKeyboardControl, for instance.

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • Thanks for that, Gabriele. I thought I'd be able to do it simply using these few methods instead of importing an (admittedly small) implementation. Still can't see what I've done wrong though, and it's annoying me. If nobody turns up with a correction (or I suss it out looking at BSKeyboardControl), I'll mark you as the accepted solution. – BillySangster Jan 04 '13 at 11:46
3

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.

Rivers
  • 2,102
  • 1
  • 16
  • 27
2

You can also try the pod UITextField-Navigation:

pod 'UITextField-Navigation'

It adds two lazy-loaded properties nextTextField and previousTextField to any UITextField. All you need is to specify the nextTextField either on the interface builder or programmatically, and the toolbar is added to the keyboard for you.

Programmatically:

import UITextField_Navigation

let textField1 = UITextField()
let textField2 = UITextField()
textField1.nextTextField = textField2

assert(textField2 == textField1.nextTextField)
assert(textField1 == textField2.previousTextField)

On the Interface Builder:

connect nextTextField in Interface Builder enter image description here

Thanh Pham
  • 2,021
  • 21
  • 30
  • Hi, How to customize toolbar – Nikhil Pandey Feb 20 '17 at 14:09
  • @Nikhil Pandey: please check out the readme https://github.com/T-Pham/UITextField-Navigation/blob/master/README.md#ui-customization – Thanh Pham Feb 20 '17 at 17:37
  • Hi I believe you have updated the library but not the documentation page on GitHub and the example project. – Nikhil Pandey Feb 20 '17 at 23:53
  • Hi I checked the library, the documentation appears old , e.g., if i use the function : -func navigationFieldDidTapDoneButton(_ navigationField: NavigationField) { navigationField.resignFirstResponder(). it says there is nothing like Navigation Field. The current syntax I believe is - func textFieldNavigationDidTapDoneButton(_ textField: UITextField). – Nikhil Pandey Feb 20 '17 at 23:54
  • Yours is an awesome library, please update the GitHub page and may I request you to make fresh documentation and update example project again. – Nikhil Pandey Feb 20 '17 at 23:55
  • @NikhilPandey: it appears that you are using version 2 of the library. Please update to version 3. The one with `navigationField` is version 3. In case you wish to keep using version 2, here is the documentation for version 2: http://cocoadocs.org/docsets/UITextField-Navigation/2.0.0/ – Thanh Pham Feb 21 '17 at 02:23
  • Yeah just got my pod file updated. Many thanks and best wishes to you. Keep it up:). – Nikhil Pandey Feb 21 '17 at 06:05
  • Hi Pham,While using the latest version I am getting following in my Xcode debugger, can you look into this as I believe there is a print statement in your file navigationFieldDidTapNextButton: ; layer = > – Nikhil Pandey Feb 22 '17 at 03:33
  • Ah, you must be using the example app. The print statement there is to demonstrate how to handle the previous, next and done buttons yourself with your app. – Thanh Pham Feb 22 '17 at 16:23
  • @ Pham : No, I am getting this message while I am using the framework in my Xcode project and not the example app. – Nikhil Pandey Feb 23 '17 at 06:27
  • That's strange since I don't leave any print in my lib source. – Thanh Pham Feb 23 '17 at 09:04
  • That's why asking you to check, there may be one left . – Nikhil Pandey Feb 23 '17 at 12:37
  • Strange, still getting those messages. – Nikhil Pandey Feb 25 '17 at 16:52
  • Whenever two text fields are connected via nextNavigationField following types of log messages occur Reading from private effective user settings. navigationFieldDidTapNextButton: ; layer = > Though no problem with running of app is found but why this log message appears should be investigated. – Nikhil Pandey Mar 21 '17 at 06:41
  • @ThanhPham You've got an awesome little library there. I used it with my Swift 3 project and faced no issues at all. Thanks. – Anjan Biswas Jul 07 '17 at 21:41
1

Try this :-

- (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];

int idx = [sender selectedSegmentIndex];

if (idx == 1) {
    [sender setEnabled:NO forSegmentAtIndex:1];
}
else {
    [sender setEnabled:NO forSegmentAtIndex:0];
}

return toolbar;
}

- (void)changeRow:(id)sender {


int idx = [sender selectedSegmentIndex];

if (idx == 1) {
    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];
}
}

you dont need to enable other item as they reinitialise every time when keyboard changes ....

dev_m
  • 796
  • 6
  • 12