Categories
Uncategorized

Swift 3 text field magic, part 3: Creating text fields that ban a specific set of characters

creating-text-fields-that-ban-specified-characters

So far, in this series of articles, we’ve created classes that extend the capabilities of iOS’ default text fields so that they can:

These limits can be set in both Interface Builder and code.

In this article, we’ll create a new text field: one that doesn’t allow a specified set of characters to be entered into it. You may want to read the first two articles in this series, as this article builds their material.

The MaxLengthTextField class, in review

In the first article in this series, we created a subclass of UITextField called MaxLengthTextField. MaxLengthTextField added one additional ability to UITextField: the ability to limit the number of characters that could be entered into it. Here’s its code:

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
    }
  }

  // 5  
  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    // 6    
    guard string.characters.count > 0 else {
      return true
    }
    
    // 7
    let currentText = textField.text ?? ""
    // 8
    let prospectiveText = (currentText as NSString).replacingCharacters(in: range, with: string)
    // 9
    return allowedIntoTextField(text: prospectiveText)
  }
  
  func allowedIntoTextField(text: String) -> Bool {
    return text.characters.count <= maxLength
  }
  
}

Here’s what’s happening in MaxLengthTextField — 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 thatMaxLengthTextField 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 themaxLength property.
  3. In the initializer, we specify that MaxLengthTextField will define its ownUITextFieldDelegate 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.
  5. 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 itsBool 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.
  6. 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.
  7. 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.
  8. Now that we have the current text, we’ll determine the prospective text — the text that would result if the changes were accepted.
  9. Finally, we use the the length of the prospective text to decide whether to allow the changes or not. We’ve factored this logic into its own method because we expect to create subclasses to override it.

The AllowedCharsTextField class, in review

In the second article in this series, we built of MaxLengthTextField and subclassed it to create AllowedCharsTextField. AllowedCharsTextField adds the ability to limit the characters that can be entered into it to only those in a specified string. Here’s its code:

import UIKit
import Foundation


class AllowedCharsTextField: MaxLengthTextField {
  
  // 1
  @IBInspectable var allowedChars: String = ""
  
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    delegate = self
    // 2
    autocorrectionType = .no
  }
  
  // 3
  override func allowedIntoTextField(text: String) -> Bool {
    return super.allowedIntoTextField(text: text) &&
           text.containsOnlyCharactersIn(matchCharacters: allowedChars)
  }
  
}


// 4
private extension String {
  
  // Returns true if the string contains only characters found in matchCharacters.
  func containsOnlyCharactersIn(matchCharacters: String) -> Bool {
    let disallowedCharacterSet = CharacterSet(charactersIn: matchCharacters).inverted
    return self.rangeOfCharacter(from: disallowedCharacterSet) == nil
  }

}

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

  1. The instance variable allowedChars contains a string specifying the characters that will be allowed into the text field. If a character does not appear within this string, the user will not be able to enter it into the text field. This instance variable is marked with the @IBInspectable attribute, which means that its value can be read and set from within Interface Builder.
  2. We disable autocorrect for the text field, because it may try to suggest words that contain characters that we won’t allow into it.
  3. Overriding MaxLengthTextField‘s allowedIntoTextField method lets us add additional criteria to our new text field type. This method limits the text field to a set maximum number of characters and to characters specified inallowedChars.
  4. We extend the String class to include a new method,containsOnlyCharactersIn(_:), which returns true if the string contains only characters within the given reference string. We use the private keyword to limit this access to this new String method to this file for the time being.

Let’s make BannedCharsTextField

BannedCharsTextField is a slight variation on AllowedCharsTextField. Here’s its code:

import UIKit


class BannedCharsTextField: MaxLengthTextField {
  
  @IBInspectable var bannedChars: String = ""
  
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    delegate = self
    autocorrectionType = .no
  }

  override func allowedIntoTextField(text: String) -> Bool {
    return super.allowedIntoTextField(text: text) &&
           text.doesNotContainCharactersIn(matchCharacters: bannedChars)
  }
  
}


private extension String {
  
  // Returns true if the string contains no characters in common with matchCharacters.
  func doesNotContainCharactersIn(matchCharacters: String) -> Bool {
    let disallowedCharacterSet = CharacterSet(charactersIn: matchCharacters)
    return self.rangeOfCharacter(from: disallowedCharacterSet) == nil
  }
  
}

The differences between BannedCharsTextField and AllowedCharsTextField are:

  • We store a string containing the characters that we want to ban from being entered into the text field in an instance variable called bannedChars.
  • The allowedIntoTextField method calls a String extension method called doesNotContainCharactersIn, which returns true if the string doesn’t any of the banned characters.

Using BannedCharsTextField

Using BannedCharsTextField within storyboards is pretty simple. First, use a standard text field and place it on the view. Once you’ve done that, you can change it into an BannedCharsTextField by changing its class in the Identity Inspector (the inspector with the identity-inspector icon):

bannedcharstextfield-custom-class

If you switch to the Attributes Inspector (the inspector with the attributes inspector icon icon), you’ll be able to edit the text field’s Allowed Chars and Max Length properties:

bannedcharstextfield-attributes-inspector

If you prefer, you can also set BannedCharsTextField‘s properties in code:

// myNextTextField is an instance of BannedCharsTextField
myNewTextField.bannedChars = "AEIOUaeiou" // No vowels allowed!
myNewTextField.maxLength = 7

A sample project showing MaxLengthTextFieldAllowedCharsTextField, and BannedCharsTextField in action

app

If you’d like to try out the code from this article, I’ve created a project named Swift 3 Text Field Magic 3 (pictured above), which shows both MaxLengthTextField andAllowedCharsTextField in action. It’s a simple app that presents 3 text fields:

  1. A MaxLengthTextField with a 10-character limit.
  2. An AllowedCharsTextField text field with a 5-character maximum length that accepts only upper- and lower-case vowels. Its Max Length and Allowed Chars properties were set in Interface Builder.
  3. BannedCharsTextField text field with a 7-character maximum length that doesn’t allow vowels to be entered into it. Its Max Length and Allowed Chars properties were set in Interface Builder.
  4. BannedCharsTextField text field with a 7-character maximum length that doesn’t allow digits to be entered into it. Its maxLength and allowedChars properties were set in code.

Try it out, see how it works, and use the code in your own projects!

xcode download

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

Coming up next in this series…

In the next installment in this series, we’ll build a text field that accepts only valid numerical values.

5 replies on “Swift 3 text field magic, part 3: Creating text fields that ban a specific set of characters”

This was great till you have delicate the text field in order to get the to close the keyboard. the delicate over rides the class. :/

To make AllowedCharsTextField to work, you need to remove the delegate from the uitextfield. This means that you can’t resign the first responder by tapping the Return button.
Great code, has helped me with the iPad keyboard showing special characters.

Ok. Did some more playing and breaking of the build. And worked it out.
Inserted the following into the class MaxLengthTextField before the final }.
The key is that the MaxLengthTextField has the UITextFieldDelegate protocol. So this function is called when Return is tapped.

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard.
textField.resignFirstResponder()
return true
}

Comments are closed.