UITextField Max Length (The Right Way)

Any long time developer of iOS knows that limiting the length of a UITextField is fairly simple. All you have to do is implement  textField:shouldChangeCharactersInRange:replacementString:. The problem is, when compared to other languages, this can seem really tedious and dumb.

In other languages/platforms, all you have to do is set some property on your UITextField , and you’re done. You can do it in the interface layer instead of your application code. Everything is really quite simple.

Recently I set about making setting the max length of a UITextField easier to do on a more global level. We’ve got a mobile device that uploads JSON to a server that then turns around and puts it into an Oracle database. Problem here is that Oracle has limits on the number of characters in a column, but iOS doesn’t have quite the same limitations for UITextFields. We never really considered this problem until we started having customers lose data because of entering way way way too much text into a text field (sometimes I wonder about users)

We have an application that has many screens in it, and is already fully built. So the problem we face is the desire to add limits to the text fields where most of the text fields already have a delegate, and we don’t want to have to go copy and paste code to implement the proper delegate methods.

So here’s the solution I came up with.

Limiting Input

As I mentioned before, limiting the input is a really easy thing to do. Implement the delegate method like this

Most of this should look fairly normal. All this chunk of code is doing is making sure you haven’t entered too many characters. The only thing that’s a little odd is the stuff checking to see if you pressed the return key. That’s because there can be issues with pressing the done key when you’re already at the max length. I’ve never personally seen these issues, but the internet has told me they exist and it’s a good way to cover your butt just in case.

With this chunk of code under your belt, you’re ready to start the complicated stuff.

Delegate Wrapping

I’m sure there’s probably someone out there that’s come up with a real term for this, but I don’t know it. What we’re going to do is create a UITextFieldDelegate that wraps itself around another UITextFieldDelegate . The outer delegate will do some work and then pass things off to the inner delegate. You could also refer to this as Delegate Chaining. Note: After looking up some documentation, I found that Apple actually calls this Surrogate Objects. There’s loads of documentation here.

The magic comes in the Objective-C runtime so make sure you add this import somewhere.

As you may or may not know, Objective-C does not do things quite the same way as other languages. Here’s how I like to think of it: In Objective-C when you need to execute a method, it looks like this

objective-c-methodsWhen you want to execute some method, you ask an object to execute the method. What happens beyond that is up to the object. You don’t get to make any final decisions about what method really executes.

This is made more obvious by the fact that if you tell a nil object to execute a method, you don’t get a null reference exception. The nil object decides to execute the method by doing nothing.

Other languages tend not to do this because it does cause slightly slower performance, but keep in mind, we’re talking nanoseconds. Unless you’re writing a game, it doesn’t matter.

What happens in a language like C++ or C# is when you execute a method, you will look up the address of the method you need to execute and execute the code. There’s a slight difference here. In Objective-C, you tell an object to invoke a method. In C++/C#/Java, you invoke the method. That small difference is whether you invoke it, or you tell something to invoke it.

Why does this matter? You can ask an object to invoke a method that it doesn’t implement. Everyone that still understands this post should know what I’m talking about. Objects will throw exceptions when they are asked to execute methods they don’t implement. But here’s where the magic comes in. You can have a second chance before you throw an exception.

forwardInvocation: is a method where you can have a second chance to execute a method before throwing an unrecognized selector exception. All you have to do is implement it like this

If some other object responds to the selector, you forward the invocation on to that other object. Otherwise, you call super, which will (in the case of NSObject ) throw the unrecognized selector exception.

Along with implementing this, you also need to implement  respondsToSelector: and  methodSignatureForSelector: like this

By implementing these, you can create a UITextFieldDelegate that implements certain methods and allows another UITextFieldDelegate to implement other methods.

The final UITextFieldDelegate wrapper to limit the number of characters looks something like this

Take a moment just to note that  textField:shouldChangeCharactersInRange:replacementString: still provides a way for the delegate being wrapped to implement the method too. This is essential to ensure that we can set a max character limit without limiting delegate functionality.

UITextField Subclass

Now that you know how to create the delegate, you need to create a UITextField subclass. The subclass is responsible for a couple of things:

  • Maintaining a strong reference to the delegate wrapper
  • Providing an easy way to set the max length
  • Interface Builder Integration

The UITextField subclass has to maintain a strong reference to the delegate wrapper, and the delegate wrapper maintains a weak reference to the delegate it’s wrapping. This makes sure the delegate wrapper (which limits the number of characters) has the same lifecycle as the UITextField, and the delegate being wrapped has the lifecycle that it would have had anyway. It might be worth pointing out here that a UITextField can not be it’s own delegate. There’s an infinite recursive loop in UITextField's implementation if UITextField is it’s own delegate.

The UITextField will have a property for the max length that is just going to pass straight through to the TextFieldDelegateWrapper .

Interface Builder integration is extremely easy in Xcode 6. All you have to do is mark the class as  IB_DESIGNABLE and the properties as IBInspectable. I don’t know who the thought that it was a good idea to have two different case conventions for those two clearly related constants, but they’re obviously an idiot.

The whole implementation of the UITextField subclass looks like this

Now in order to use this, you just need to set the class in interface builder from UITextField to  MaxLengthTextField, and then set the value of maxLength directly in Interface Builder

Leave a Reply

Your email address will not be published. Required fields are marked *