Categories
Uncategorized

Swift 3 text field magic, part 1: Creating text fields with maximum lengths [Updated]

text field max lengths

Update, September 8, 2016: My original implementation of this solution had a memory leak problem. This new implementation doesn’t!

In this series of articles, we’ll look at making iOS text fields even better using Swift 3. We’ll start by adding an important capability that iOS text fields have been missing for far too long: the ability to limit themselves to a maximum number of characters.

.NET has a maxLength property for text fields; why can’t Cocoa?

In Microsoft’s .NET framework, TextBoxes — the .NET equivalent of Cocoa’sUITextFields —  have a MaxLength property that you can set, either visually or in code, that specifies the maximum number of characters that can be entered into them:

easy-in-visual-studio

In the setup shown above, the TextBox‘s MaxLength property is set to 3, which ensures that the maximum number of characters that can be entered into it is 3, whether the characters are inserted by the user typing or pasting them in, or by code.

Cocoa’s UIKit doesn’t provide an analog to MaxLength for its UITextFields, but there’s no reason we can’t create it ourselves. By the end of this article, we’ll haveUITextFields that feature a maxLength property that can be set either in code or Interface Builder, as shown below:

wouldnt-it-be-nice

Creating a new class that subclasses UITextField and features a maxLength property that can be edited in Interface Builder

Start a new project by doing the standard File → New → Project… dance to create a new Single View Application.

Open Main.storyboard, place a single text field on the view, select the text field and switch to the Attributes Inspector view (the inspector panel with the attributes inspector icon icon):

plain-text-field-and-attributes-inspector

The Attributes Inspector lets you edit all sorts of text field properties, but not the maximum length…yet.

Use File → New → File… to create a new Swift File

new-swift-file

…and give it the name MaxLengthTextField.swift. We’ll use it for the code of a new class, MaxLengthTextField, which will contain the necessary properties and behaviors for a text field where you can specify a maximum length.

Change the contents ofMaxLengthTextField.swift to the code below:

import UIKit

// 1
class MaxLengthTextField: UITextField, UITextFieldDelegate {
  
  // 2
  private var characterLimit: Int?
  
  
  // 3
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    delegate = self
  }
  
  // 4
  @IBInspectable var maxLength: Int {
    get {
      guard let length = characterLimit else {
        return Int.max
      }
      return length
    }
    set {
      characterLimit = newValue
    }
  }
  
}

Here’s what’s happening in the code above — these notes go with the numbered comments:

  1. In order to create a text field with the ability to set a maximum length, we’re defining a new class — MaxLengthTextField — as a subclass of UITextField, which gives it all the properties and behaviors of a UITextField. We also specify that MaxLengthTextField adopts the UITextFieldDelegate protocol, which allows us to manage changes to the content of our new text fields. We’ll need this in order to set a limit on how much text will be allowed inside the text field.
  2. characterLimit will hold the maximum number of characters that can be entered into the text field. It’s defined as an Int? since its value may or may not be defined, and defined as private since its value will be get and set using the maxLength property.
  3. In the initializer, we specify that MaxLengthTextField will define its own UITextFieldDelegate protocol methods. We’ll make use of one of these protocol methods later on to ensure that the text field doesn’t accept any more than the specified maximum number of characters.
  4. The @IBInspectable attribute makes the maxLength property editable from within Interface Builder’s Attribute Inspector.

Trying out our new MaxLengthTextField class

With the starter code for MaxLengthTextField done, let’s try it out. Switch to Main.storyboard and put a text field on the view. With the text field still selected, select the Identity Inspector (the one with the identity-inspector icon). Within the Identity Inspector’s Custom Class section, use the Class drop-down to change the text field’s class from UITextField to MaxLengthTextField:

custom-class

With this change, the text field is no longer an instance of UITextField; it’s now an instance of MaxLengthTextField. This becomes obvious when you switch to the Attributes Inspector (the inspector with the attributes inspector icon icon). Near the top of the Attributes Inspector, you’ll see that the text field has a new section of properties, Max Length Text Field, and within it, a new property called Max Length:

attributes-inspector

Note that Interface Builder did a couple of things in response to our marking the maxLength property with the @IBInspectable attribute:

  • It created a new section of the Attributes Inspector titled Max Length Text Field, deriving its name from the MaxLengthTextField class, where you can edit its @IBInspectable properties.
  • It created a new property field titled Max Length, which lets you edit MaxLengthTextField‘s maxLength property.

You’ll want to keep the way Xcode names Attribute Inspector fields in mind when naming classes and properties that you want to be editable within Interface Builder.

You can set the Max Length property to any valid integer value. It won’t have any apparent effect if you run the app; right now, all it does is get and set the value of MaxLengthTextField‘s private characterLimit variable. Let’s make Max Length actually do something.

Making the maxLength property limit the number of characters allowed in a text field

Update the code in MaxLengthTextField.swift so that it contains the following:

import UIKit

class MaxLengthTextField: UITextField, UITextFieldDelegate {

  private var characterLimit: Int?
  
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    delegate = self
  }
  
  @IBInspectable var maxLength: Int {
    get {
      guard let length = characterLimit else {
        return Int.max
      }
      return length
    }
    set {
      characterLimit = newValue
    }
  }
  
  // 1
  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  
    // 2
    guard string.characters.count > 0 else {
      return true
    }
    
    // 3
    let currentText = textField.text ?? ""
    // 4
    let prospectiveText = (currentText as NSString).replacingCharacters(in: range, with: string)
    // 5
    return prospectiveText.characters.count <= maxLength
  }
  
}

Here’s what’s happening in the code above — these notes go with the numbered comments:

  1. The actual functionality of MaxLengthTextField is contained within textField(_:shouldChangeCharactersIn:replacementString:), one of the methods made available by adopting the UITextFieldDelegate protocol. This method is called whenever user actions change the text field’s content, and its Bool return value specifies if the text change should actually change place. We’ll use this method to limit the number of characters that can be entered into the text field — if user changes would cause the number of characters in the text field to exceed characterLimit, it returns false; otherwise, it returns true.
  2. Get to know and love the guard statement and the “early return” style of programming; you’re going to see a lot of it in a lot of Swift coding. Here, we’re using guard to filter out cases where the user’s changes are of zero length, which means that characters aren’t being added, but deleted. In this case, we don’t have to see if the user’s changes will cause the text field to exceed its set maximum number of characters. We do want the method to return true in order to allow the user’s deletions to take place.
  3. If we’ve reached this point of the method, it means that the user has made additions to the text field. We should see if these changes would cause the text field to contain more than characterLimit characters. The first step in this process is getting the text currently in the text field. We use the nil coalescing operator?? — to assign the contents of the text field if they are not nil, or the empty string if they are nil.
  4. Now that we have the current text, we’ll determine the prospective text — the text that would result if the changes were accepted.
  5. Finally, we use the the length of the prospective text to decide whether to allow the changes or not.

If you include MaxLengthTextField.swift in any of your iOS projects, it’s pretty simple to turn ordinary text fields into ones with maxLength properties that you can set either GUI builder-style in Interface Builder or in code, using myTextField.maxLength = n syntax, just like the .NET people do.

Happy text field coding!

A sample project showing Visual Studio-style text field maximum lengths in action

text field max lengths

If you’d like to try out the code from this article, I’ve created a project named Swift 3 Text Field Magic 1 (pictured above), which shows our new MaxLengthTextField subclass in action. It’s a quick-and-dirty single-view app that presents 4 text fields:

  1. A text field without a set maximum length.
  2. A text field with a 1-character maximum length, with the Max Length property set in Interface Builder.
  3. A text field with a 5-character maximum length, with the maxLength property set in code (in the view controller’s viewDidLoad method).
  4. A text field with a 10-character maximum length, with the Max Length property set in Interface Builder.

Give it a try, learn what makes it tick, and use it as a jumping-off point for your own projects!

xcode download

You can download the project files for this article (61KB zipped) here.

Coming up next in this series…

In the next installment in this series, we’ll build on what we’ve made so far to create a text field that accepts only a defined set of characters.

10 replies on “Swift 3 text field magic, part 1: Creating text fields with maximum lengths [Updated]”

When will elements be removed from the map. Won’t this leak references to all created text boxes?

Alex Valee: Yes it will. I’ve changed the solution so that it’s based on subclassing UITextField rather than extensions. By doing so, I’m storing the maximum length of each text field within the text field instance, which gets rid of the leak problem.

Thanks for the heads-up!

[…] In the previous article in this series, we added a long-missing feature to iOS text fields: the ability to limit them to a maximum number of characters, specifying this limit using either Interface Builder or in code. In this article, we’ll add an additional capability to our improved text field: the ability to allow only specified characters to be entered into them. […]

Excuse me, according with your “Swift 3 text field magic, part 1: Creating text fields with maximum lengths [Updated]” article. I have a question, if I want to instantiate an AllowedCharsTextField as below

let myTextField = AllowedCharsTextField.init to add it manually in runtime to a UIView

I don’t know how to instantiate it using your init with aDecoder

Can you help me please?

So, I have long loved this, but suddenly my Storyboards that use this died.

Been a while since I worked on this but XCode 9.4 required me to add this :
required override init(frame: CGRect) {
super.init(frame: frame)
}

To my subclass. Swift requires you to implement required inits.

Comments are closed.