CTS – Your Technology Partner

Modify the Default iOS Keyboard

Written by Brian Ponath on April 2, 2014

This article is going to cover how to modify the default iOS keyboard to make it more usable for filling out a form in an app. We’ll be using Visual Studio 2013 with the Xamarin.iOS libraries. There are plenty of examples out there showing how to do things in objective-C. Finding the examples that show how to do the same things in Xamarin is a little more difficult. The motivation for this article was born out of difficulty in finding good, detailed examples.

If you are not already familiar with Xamarin, it is a tool that allows you to develop mobile apps across iOS, Android, and Windows mobile devices. Code is written in C#.NET and back-end code, such as business and data layers, is shared across the platforms. You can read more about it at Xamarin.com.

The standard iOS keyboard for UITextFields and UITextViews does not offer much in the way of easily adding commonly used functionality by default. There’s not an easy way to add Prev or Next buttons as is commonly seen in apps where the user fills out a form. 

I’m going to walk through how I modified the keyboard that I think offers a much better user experience. First things first, we need some text fields for our form. In our UIViewController class, we’ve added some UITextFields. We also have some constant integers specifying some default values for the UITextFields.

public class FormController : UIViewController
{
   private const int fieldX = 20;
   private const int fieldWidth = 280;
   private const int fieldHeight = 40;
   private UILabel header;
   private UITextField firstName;
   private UITextField lastName;
   private UITextField address1;
   private UITextField address2;
   private UITextField city;
   private UITextField state;
   private UITextField zip;

Next, we initialize our controls in the FormController constructor with help from InitializeTextField.

public FormController()
{
   header = new UILabel(new RectangleF(75, 50, 100, 40));
   header.Text = “CTS Form”;
   InitializeTextField(out firstName, “First Name”);
   InitializeTextField(out lastName, “Last Name”, y: 200);
   InitializeTextField(out address1, “Address 1″, y: 250);
   InitializeTextField(out address2, “Address 2″, y: 300);
   InitializeTextField(out city, “City”, y: 350);
   InitializeTextField(out state, “State”, y: 400, width: 100);
   InitializeTextField(out zip, “Zip”, x: 130, y: 400, width: 170);
}

private void InitializeTextField(out UITextField field, string
   placeholder, int x = fieldX, int y = 150, int width =
   fieldWidth, int height = fieldHeight)
{
   field = new UITextField()
{
      Frame = new RectangleF(x, y, width, height),
      Placeholder = placeholder,
      BackgroundColor = UIColor.White
};
   field.Layer.BorderColor = new CGColor(0.8f, 0.8f, 0.8f);
   field.Layer.BorderWidth = 1.0f;
   field.Layer.CornerRadius = 5.0f;
   //These two lines set the left padding
   field.LeftView = new UIView(new RectangleF(0, 0, 10, 40));
   field.LeftViewMode = UITextFieldViewMode.Always;
}

Finally we override the ViewDidLoad method and add our controls to the View.

public override void ViewDidLoad()
{
   base.ViewDidLoad();
   View.Frame = UIScreen.MainScreen.Bounds;
   View.BackgroundColor = UIColor.White;
   View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth |
      UIViewAutoresizing.FlexibleHeight;
   View.AddSubview(header);
   View.AddSubview(firstName);
   View.AddSubview(lastName);
   View.AddSubview(address1);
   View.AddSubview(address2);
   View.AddSubview(city);
   View.AddSubview(state);
   View.AddSubview(zip);
}

Now we can run this and see what our form looks like.

image

 

It’s just a basic form with a label. It’s not much to look at, but it’s something to work with. If we tap on a field, the keyboard slides up allowing us to enter a value. Here’s where we find our first problem. The keyboard covers up all the fields below Last Name and doesn’t allow us to enter values for those fields. We also can’t tap out of the keyboard. Tapping Return doesn’t even make the keyboard disappear. Nothing we do makes the keyboard go away. 

 

image

 

This needs some work. First we’ll fix the issue with being able to tap out of the keyboard. We need to add a UITapGestureRecognizer to our UIViewController. This will detect when you tap on the screen, and send the keyboard away via the ResignFirstResponder method. We add the code to the end of the ViewDidLoad method.


var g = new UITapGestureRecognizer(() =>
{
   firstName.ResignFirstResponder();
   lastName.ResignFirstResponder();
   address1.ResignFirstResponder();
   address2.ResignFirstResponder();
   city.ResignFirstResponder();
   state.ResignFirstResponder();
   zip.ResignFirstResponder();
});
View.AddGestureRecognizer(g);

Now when we run the app, we can tap on a field, enter some text, and then tap out of the keyboard to send the keyboard away. We still have to deal with the issue of the keyboard covering up the text fields. But first we’re going to modify the keyboard. (I’ll explain at the end why we’re modifying the keyboard first.) We’re going to add a toolbar to the top of the keyboard that includes a Previous button, a Next button, and a Done button. This will allow us to navigate through the different fields quickly and easily without having to tap out of the keyboard or tap on the fields themselves. 

We create an EnhancedKeyboard class that extends the UIToolbar class. This class contains the three UITextFields which will be the UITextFields passed in via the constructor. The constructor accepts the UITextField objects for specifying which is the current, previous, and next UITextFields on the form. 

public class EnhancedToolbar : UIToolbar
{
   public UIView prevTextFieldOrView { get; set; }
   public UIView currentTextFieldOrView { get; set; }
   public UIView nextTextFieldOrView { get; set; }
   public EnhancedToolbar() : base() { }
   public EnhancedToolbar(UIView current, UIView previous,
      UIView next)
{
      this.currentTextFieldOrView = current;
      this.prevTextFieldOrView = previous;
      this.nextTextFieldOrView = next;
      SetupToolbar();
}
   void SetupToolbar()
   {
      Frame = new RectangleF(0.0f, 0.0f, 320, 44.0f);
      TintColor = UIColor.DarkGray;
      Translucent = false;
      Items = new UIBarButtonItem[]
{
         new UIBarButtonItem(“Prev”,
            UIBarButtonItemStyle.Bordered, delegate
{
prevTextFieldOrView.BecomeFirstResponder();
}) { Enabled = prevTextFieldOrView != null },
      new UIBarButtonItem(“Next”,
          UIBarButtonItemStyle.Bordered, delegate
{
nextTextFieldOrView.BecomeFirstResponder();
         }) { Enabled = nextTextFieldOrView != null },
    new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace),
   new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate
  {
       currentTextFieldOrView.ResignFirstResponder();
     })
     };
   }
}

In the SetupToolbar method, we create three UIBarButtonItems and associate our UITextField objects to their corresponding UIBarButtonItem.

Next we need to update the ViewDidLoad method and assign each of the UITextField’s InputAccessoryView to an instance of EnhancedToolbar


firstName.InputAccessoryView = new EnhancedToolbar(firstName,
   null, lastName);
lastName.InputAccessoryView = new EnhancedToolbar(lastName,
   firstName, address1);
address1.InputAccessoryView = new EnhancedToolbar(address1,
   lastName, address2);
address2.InputAccessoryView = new EnhancedToolbar(address2,
   address1, city);
city.InputAccessoryView = new EnhancedToolbar(city, address2,
   state);
state.InputAccessoryView = new EnhancedToolbar(state, city,
   zip);
zip.InputAccessoryView = new EnhancedToolbar(zip, state, null);
zip.KeyboardType = UIKeyboardType.NumberPad;

Notice how for the firstName and zip UITextFields, we pass in null for the previous and next UITextFields, respectively. We also specified the KeyboardType for the zip field. Now when the zip field is the active field, the keyboard will show the number pad instead of the letters.

image

Now we can run this and see our new enhanced toolbar above the keyboard. If we tap on First Name, the keyboard pops up with the toolbar on top. The Prev button is disabled since we passed in null for the Previous field for First Name.

image

Tap the Next button twice and we can enter in a value for the Address 1 field. Tap the Done button to see our entered value. We still need to resolve the issue of the keyboard hiding the fields. 

image

We need some way to recognize when the keyboard goes up and down and scroll our view appropriately to show the selected UITextField if it’s hidden by the keyboard. We create a new class called KeyboardHandler.

public class KeyboardHandler
{
   private UIView _activeview; // Controller that activated the
                              // keyboard
   private float _scrollamount = 0; // amount to scroll
   private float _scrolledamount = 0; // how much we’ve
                                     // scrolled already
   private float _bottom = 0.0f; // bottom point
   private const float Offset = 10.0f; // extra offset
   public UIView View { get; set; } // The UIView for the
                                   // keyboard handler
   public void KeyboardUpNotification(NSNotification
      notification)
   {
      // get the keyboard size
      var val = new
NSValue(notification.UserInfo.ValueForKey(UIKeyboard.FrameBeginUserInfoKey).Handle);
      RectangleF keyboardFrame = val.RectangleFValue;
      // Find what opened the keyboard
     foreach (UIView view in this.View.Subviews)
     {
          if (view.IsFirstResponder)
                   _activeview = view;
          }
      // Determine if we need to scroll up or down.
      // Bottom of the controller = initial position + height
      // + offset
      _bottom = (_activeview.Frame.Y +
                 _activeview.Frame.Height + Offset);
      // Calculate how far we need to scroll
      _scrollamount = (keyboardFrame.Height –
                       (View.Frame.Size.Height — _bottom));
//Move view up
if (_scrollamount > 0)
{
//Subtract the scrolledamount. We can’t do this
//subtraction
//above because the calculations won’t work
//correctly.
_bottom -= _scrolledamount;
_scrollamount = (keyboardFrame.Height —
(View.Frame.Size.Height —_bottom));
_scrolledamount += _scrollamount;
ScrollTheView(false);
}
//Reset the view.
else
{
ScrollTheView(true);
}
}
public void KeyboardDownNotification(NSNotification
notification)
{
ScrollTheView(true);
}
private void ScrollTheView(bool reset)
{
// scroll the view up or down
UIView.BeginAnimations(string.Empty,
System.IntPtr.Zero);
UIView.SetAnimationDuration(0.3);
RectangleF frame = View.Frame;
if (reset)
{
frame.Y = frame.Y + _scrolledamount;
_scrollamount = 0;
_scrolledamount = 0;
}
else
{
frame.Y -= _scrollamount;
}
View.Frame = frame;
UIView.CommitAnimations();
}
}

There’s some calculations in here that determine how much to scroll based on the heights of the iPhone screen, the keyboard, and the active UITextField and how much we’ve already scrolled. The details of these calculations are outside the scope of this article but feel free to step through it to see how it works. A couple of the key points of this class are the KeyboardUpNotification and the KeyboardDownNotification methods. These methods will be added to the NSNotificationCenter in the KeyboardView class. 

We need to add a few things to the KeyboardView class. We need a KeyboardHandler instance. And we also need a couple of NSObjects for the NSNotificationCenter observers. 


private readonly KeyboardHandler kbHandler;
private NSObject keyboardUp;
private NSObject keyboardDown;

In order to recognize when the keyboard goes up or down, we need to add a couple of observers. We’ll do this in the overridden ViewWillAppear and ViewWillDisappear methods. 

public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
kbHandler.View = this.View;
keyboardUp = NSNotificationCenter
.DefaultCenter
.AddObserver(UIKeyboard.DidShowNotification,
kbHandler.KeyboardUpNotification);
keyboardDown = NSNotificationCenter
.DefaultCenter
.AddObserver(UIKeyboard.WillHideNotification,
kbHandler.KeyboardDownNotification);
}

public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
if (keyboardUp != null && keyboardDown != null)
{
NSNotificationCenter
.DefaultCenter
.RemoveObserver(keyboardUp);
NSNotificationCenter
.DefaultCenter
.RemoveObserver(keyboardDown);
}
}

Note: Since we only have one screen in this app, we could have added the observers in the ViewDidLoad method and not worried about ever removing them. However, adding the observers in ViewWillAppear and removing them in ViewWillDisappear is good practice and will alleviate any headaches later when more screens are added that may use these observers. If we don’t explicitly remove the observers, the notify Actions can be called multiple times and not be real intuitive as to why. Also, there are examples showing how to do this that don’t assign the AddObserver call to a variable. These examples just call the method as follows…

//Don’t do this.
//Assign it to a variable if you plan on removing it.
NSNotificationCenter
.DefaultCenter
.AddObserver(UIKeyboard.DidShowNotification,
kbHandler.KeyboardUpNotification);

This won’t allow proper removal when you go to remove the observer, which will lead to more frustration when you can’t figure out why the notify Actions are still being called even though you thought you removed the observers.

Finally we need to instantiate our KeyboardHandler in the constructor.

public KeyboardView()
{
kbHandler = new KeyboardHandler();

Now when we run the app, we can tap on a field, hit the Next, Prev, and Done buttons on the keyboard toolbar, and the screen will scroll appropriately.

Comments

comments