Categories
Programming

How to program an iOS text field that takes only numeric input or specific characters with a maximum length [Updated]

Check out this update (May 24, 2016)!

update

The material in this article is still applicable, but you’ll also want to read a newer one titled A better way to program iOS text fields that have maximum lengths and accept or reject specific characters, which shows you how to make text fields that let you specify the following in Interface Builder or using less code:

  • The maximum number of characters that a text field will accept
  • The only characters that can be entered into a text field
  • The only characters that can’t be entered into a text field

I’m already using that material in a couple of projects, and I think you’ll find it handy, too. Check it out!

 

 

 

And now, the original article…

Constraining text fields

constrained text fields demo app

Click the image to see it at full size.

Update, August 26, 2015: I’ve updated this article so that its code works with Swift 2. It compiles under the latest version of Xcode 7, beta 6.

swift kickA little while back, I published an article that covered constraining text fields so that they accepted only values that evaluated to numeric ones, and limited them to a specified maximum number of characters (don’t bother looking for it; it redirects to this article now). This article expands and improves on it by showing you how to create iOS text fields that:

  • accept only numeric values,
  • accept only characters that appear in a specified string,
  • accept any characters except those that appear in a specified string, and
  • combine any of the features listed above

zip file iconIn order to demonstrate this, I’ve created a quick sample app, ConstrainedTextFieldDemo. You can download it here [90K Xcode project and associated files, zipped]. When you run it, you’ll the screen pictured above. It contains a set of text fields, each one with its own set of constraints:

  1. A text field that accepts only vowel characters (upper- and lowercase), and no more than 6 of them.
  2. A text field that accepts any character except vowels, and no more than 8 of them.
  3. A text field that accepts digits only, and no more than 3 of them.
  4. A text field that accepts only numeric values, as long as they’re 7 characters or fewer in length. Note that this field allows numbers in scientific notation.
  5. A text field that accepts only positive integers up to 5 characters in length.

In this article, I’ll walk you through the app and show you how to create your own constrained text fields in iOS. Better still, I’ll give you the project files so that you can experiment with the app.

Cut to the code (and the storyboard, too)!

Before we get into the explanations, let me cut to the chase and just give you the code.

For the purposes of discussing constrained text fields, we need to consider only two files:

  1. The view controller, ViewController.swift, and
  2. a set of string utility methods contained in StringUtils.swift.

Here’s ViewController.swift:

//
//  ViewController.swift
//

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

  // MARK: Outlets
  
  @IBOutlet weak var vowelsOnlyTextField: UITextField!
  @IBOutlet weak var noVowelsTextField: UITextField!
  @IBOutlet weak var digitsOnlyTextField: UITextField!
  @IBOutlet weak var numericOnlyTextField: UITextField!
  @IBOutlet weak var positiveIntegersOnlyTextField: UITextField!
  
  
  // MARK: View events and related methods
  
  override func viewDidLoad() {
    super.viewDidLoad()
    initializeTextFields()
  }

  // Designate this class as the text fields' delegate
  // and set their keyboards while we're at it.
  func initializeTextFields() {
    vowelsOnlyTextField.delegate = self
    vowelsOnlyTextField.keyboardType = UIKeyboardType.ASCIICapable

    noVowelsTextField.delegate = self
    noVowelsTextField.keyboardType = UIKeyboardType.ASCIICapable
    
    digitsOnlyTextField.delegate = self
    digitsOnlyTextField.keyboardType = UIKeyboardType.NumberPad
    
    numericOnlyTextField.delegate = self
    numericOnlyTextField.keyboardType = UIKeyboardType.NumbersAndPunctuation
    
    positiveIntegersOnlyTextField.delegate = self
    positiveIntegersOnlyTextField.keyboardType = UIKeyboardType.DecimalPad
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }
  
  // Tap outside a text field to dismiss the keyboard
  // ------------------------------------------------
  // By changing the underlying class of the view from UIView to UIControl,
  // the view can respond to events, including Touch Down, which is
  // wired to this method.
  @IBAction func userTappedBackground(sender: AnyObject) {
    view.endEditing(true)
  }
  
  
  // MARK: UITextFieldDelegate events and related methods
  
  func textField(textField: UITextField,
                 shouldChangeCharactersInRange range: NSRange,
                 replacementString string: String)
       -> Bool
  {
    // We ignore any change that doesn't add characters to the text field.
    // These changes are things like character deletions and cuts, as well
    // as moving the insertion point.
    //
    // We still return true to allow the change to take place.
    if string.characters.count == 0 {
      return true
    }
    
    // Check to see if the text field's contents still fit the constraints
    // with the new content added to it.
    // If the contents still fit the constraints, allow the change
    // by returning true; otherwise disallow the change by returning false.
    let currentText = textField.text ?? ""
    let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)
  
    switch textField {
    
      // Allow only upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 6 characters.
      case vowelsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 6

      // Allow any characters EXCEPT upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 8 characters.
      case noVowelsTextField:
        return prospectiveText.doesNotContainCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 8
        
      // Allow only digits in this field, 
      // and limit its contents to a maximum of 3 characters.
      case digitsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("0123456789") &&
               prospectiveText.characters.count <= 3
        
      // Allow only values that evaluate to proper numeric values in this field,
      // and limit its contents to a maximum of 7 characters.
      case numericOnlyTextField:
        return prospectiveText.isNumeric() &&
               prospectiveText.characters.count <= 7
        
      // In this field, allow only values that evalulate to proper numeric values and
      // do not contain the "-" and "e" characters, nor the decimal separator character
      // for the current locale. Limit its contents to a maximum of 5 characters.
      case positiveIntegersOnlyTextField:
        let decimalSeparator = NSLocale.currentLocale().objectForKey(NSLocaleDecimalSeparator) as! String
        return prospectiveText.isNumeric() &&
               prospectiveText.doesNotContainCharactersIn("-e" + decimalSeparator) &&
               prospectiveText.characters.count <= 5
        
      // Do not put constraints on any other text field in this view
      // that uses this class as its delegate.
      default:
        return true
    }
    
  }
  
  // Dismiss the keyboard when the user taps the "Return" key or its equivalent
  // while editing a text field.
  func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true;
  }
  
}

I gave the outlets for the text fields sensible names, but I thought that it might be helpful to show you an annotated storyboard that points out which outlet belongs to which text field:

constrained text fields screenshot

The code in the view controller calls on some string utility methods that I decided to put into their own module: the StringUtils.swift file:

//
//  StringUtils.swift
//

import Foundation

extension String {
  
  // Returns true if the string has at least one character in common with matchCharacters.
  func containsCharactersIn(matchCharacters: String) -> Bool {
    let characterSet = NSCharacterSet(charactersInString: matchCharacters)
    return self.rangeOfCharacterFromSet(characterSet) != nil
  }
  
  // Returns true if the string contains only characters found in matchCharacters.
  func containsOnlyCharactersIn(matchCharacters: String) -> Bool {
    let disallowedCharacterSet = NSCharacterSet(charactersInString: matchCharacters).invertedSet
    return self.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
  }
  
  // Returns true if the string has no characters in common with matchCharacters.
  func doesNotContainCharactersIn(matchCharacters: String) -> Bool {
    let characterSet = NSCharacterSet(charactersInString: matchCharacters)
    return self.rangeOfCharacterFromSet(characterSet) == nil
  }
  
  // Returns true if the string represents a proper numeric value.
  // This method uses the device's current locale setting to determine
  // which decimal separator it will accept.
  func isNumeric() -> Bool
  {
    let scanner = NSScanner(string: self)
    
    // A newly-created scanner has no locale by default.
    // We'll set our scanner's locale to the user's locale
    // so that it recognizes the decimal separator that
    // the user expects (for example, in North America,
    // "." is the decimal separator, while in many parts
    // of Europe, "," is used).
    scanner.locale = NSLocale.currentLocale()
    
    return scanner.scanDecimal(nil) && scanner.atEnd
  }

}

Let’s take a closer look at the code…

The delegate pattern and text fields

The delegate pattern in general

the delegate pattern

The Delegate pattern is a fundamental part of iOS app development. You’ll encounter it often when programming user interfaces, including those times when you want to your program to react to what the user does with text fields.

The delegate pattern involves two categories of object:

  • A delegator, which needs to perform a task, but doesn’t have some needed information, resources, or logic to do so. It gets that needed information, resources, or logic from…
  • A delegate. While it typically can’t do what the delegator does, it has the information, resources, or logic that the delegator needs to perform its task.

My pet analogy for the delegate pattern is pictured above: an airplane and air traffic control. Unlike the driver of a car, who’s controlled only by traffic signals and pretty much free to choose any destination and route s/he pleases, the pilot of a plane has to delegate a lot of those choices to air traffic control. The airplane, which does the actual flying, is the delegator, and air traffic control, which gives clearance for takeoff and landing and tells the plane the heading, speed, and altitude at which it should fly, is the delegate.

The delegate pattern in iOS

If you look at the delegate pattern in Wikipedia, you’ll see that there are a number of ways to implement it. Here’s how it’s done in iOS (and Cocoa), whether you’re doing it in Objective-C or Swift:

delegate pattern in iOS

There are three things in play:

  • The delegator, which keeps a reference to the delegate, which will end up having the task delegated to it,
  • The delegate, which implements the methods and properties used to accomplish the delegated task, and
  • The protocol, which connects the delegator and delegate by:
    • giving the delegator a way to send messages to the delegate, and
    • giving the delegate a way to perform actions on behalf of the delegator.

The delegate pattern with iOS’ text fields

Let’s make the above diagram a little more specific and talk about delegation in terms of iOS’ text fields:

delegation with text fields

iOS text fields — that is, instances of the UITextField class — participate in a delegate pattern as delegators. They’ve got the power to control what happens when the user starts and stops editing their contents and what characters can be typed into them, but they offload the logic that handles those tasks to another object: the delegate.

A specific protocol, the UITextFieldDelegate protocol, connects the text field and its delegate together. A protocol is simply a set of declarations of class members — instance properties, instance methods, type methods, operators, and subscripts. These instance properties, instance methods, type methods, operators, and subscripts are implemented in the delegate (implementing a protocol’s members is called adopting the protocol), and the delegator calls on these implemented members.

The protocol: UITextFieldDelegate

Let’s look at the UITextFieldDelegate protocol. You can actually check it out for yourself; the simplest way is to control-click or right-click on any occurrence of UITextField in your code and then click on Jump to Definition in the contextual menu that appears:

getting to uitextfielddelegate

You’ll be taken to UITextField.h, a header file that allows Swift to connect to the Objective-C code on which UITextField is built. It contains the declarations for all the publicly-accessible parts of UITextField, including the UITextFieldDelegate protocol. You’ll find it near the end of the file. I’ve reproduced it below:

protocol UITextFieldDelegate : NSObjectProtocol {
    
    optional func textFieldShouldBeginEditing(textField: UITextField) -> Bool // return NO to disallow editing.
    optional func textFieldDidBeginEditing(textField: UITextField) // became first responder
    optional func textFieldShouldEndEditing(textField: UITextField) -> Bool // return YES to allow editing to stop and to resign first responder status. NO to disallow the editing session to end
    optional func textFieldDidEndEditing(textField: UITextField) // may be called if forced even if shouldEndEditing returns NO (e.g. view removed from window) or endEditing:YES called
    
    optional func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool // return NO to not change text
    
    optional func textFieldShouldClear(textField: UITextField) -> Bool // called when clear button pressed. return NO to ignore (no notifications)
    optional func textFieldShouldReturn(textField: UITextField) -> Bool // called when 'return' key pressed. return NO to ignore.
}

The delegate: ViewController

In order to become a delegate, a class has to adopt the protocol. If you’re familiar with languages like C# and Java, “adopting a protocol” is similar to “implementing an interface”: we add the protocol to a class’ definition, as if we’re inheriting it. In this case, we’ll have the view controller adopt the protocol:

class ViewController: UIViewController, UITextFieldDelegate {

This says that the ViewController class inherits from the UIViewController class and adopts the UITextFieldDelegate protocol. Having the view controller act as the delegate makes sense: it controls the user interface, and the text fields are part of the user interface.

Just as you have to implement the methods in an inherited interface in C# and Java, you have to implement the methods in an adopted protocol in Swift. There is a difference, however: in Swift, you can choose not to implement methods marked as optional.

You may have noticed that all the methods in the UITextFieldDelegate protocol are optional. This means that a delegate that adopts the protocol can implement as many or as few of its methods as necessary. For the purposes of our app, we’re implementing two of them in ViewController:

  • textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool: The text field calls this whenever the user tries to change the contents of a text field, whether by typing in or deleting a character, or by cutting or pasting. The method should return true if the change is to be accepted, and false to reject the change and keep the contents of the text field the way they are. We’ll use it to limit the types of character that can be entered into the view’s text fields and set a maximum the number of characters that can be entered for each field.
  • textFieldShouldReturn(textField: UITextField) -> Bool: The text field calls this whenever the user taps the Return key or its equivalent on the keyboard. We’ll use it to dismiss the keyboard when the user taps Return.

We’ll talk about the implementation of these methods in the next section. We have to take care of the delegators first.

The delegators: the text fields

We’ve got a protocol, and we’ve got a delegate that adopts it. Now we need to set up the delegators, which in this case, are the text fields — we need to tell them who their delegates are. We do this by setting each text field’s delegate property in the initializeTextFields method of the ViewController class:

  // Designate this class as the text fields' delegate
  // and set their keyboards while we're at it.
  func initializeTextFields() {
    vowelsOnlyTextField.delegate = self
    vowelsOnlyTextField.keyboardType = UIKeyboardType.ASCIICapable

    noVowelsTextField.delegate = self
    noVowelsTextField.keyboardType = UIKeyboardType.ASCIICapable
    
    digitsOnlyTextField.delegate = self
    digitsOnlyTextField.keyboardType = UIKeyboardType.NumberPad
    
    numericOnlyTextField.delegate = self
    numericOnlyTextField.keyboardType = UIKeyboardType.NumbersAndPunctuation
    
    positiveIntegersOnlyTextField.delegate = self
    positiveIntegersOnlyTextField.keyboardType = UIKeyboardType.DecimalPad
  }

By setting all the text fields’ delegate properties to self, we’re saying that this class is their delegate. Any events arising from editing the text fields will be handled by this class.

Constraining the text fields

The magic that constrains a text field so that it’s vowels-only, numbers-only and so on happens inside the protocol method with the very long signature, textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool. This method needs to be coded so that it returns true if we want the user’s changes to be accepted (which then updates the text field), or false if we don’t want the user’s changes to be accepted (which leaves the text field unchanged).

Here’s its code:

  func textField(textField: UITextField,
                 shouldChangeCharactersInRange range: NSRange,
                 replacementString string: String)
       -> Bool
  {
    // We ignore any change that doesn't add characters to the text field.
    // These changes are things like character deletions and cuts, as well
    // as moving the insertion point.
    //
    // We still return true to allow the change to take place.
    if string.characters.count == 0 {
      return true
    }
    
    // Check to see if the text field's contents still fit the constraints
    // with the new content added to it.
    // If the contents still fit the constraints, allow the change
    // by returning true; otherwise disallow the change by returning false.
    let currentText = textField.text ?? ""
    let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)
  
    switch textField {
    
      // Allow only upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 6 characters.
      case vowelsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 6

      // Allow any characters EXCEPT upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 8 characters.
      case noVowelsTextField:
        return prospectiveText.doesNotContainCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 8
        
      // Allow only digits in this field, 
      // and limit its contents to a maximum of 3 characters.
      case digitsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("0123456789") &&
               prospectiveText.characters.count <= 3
        
      // Allow only values that evaluate to proper numeric values in this field,
      // and limit its contents to a maximum of 7 characters.
      case numericOnlyTextField:
        return prospectiveText.isNumeric() &&
               prospectiveText.characters.count <= 7
        
      // In this field, allow only values that evalulate to proper numeric values and
      // do not contain the "-" and "e" characters, nor the decimal separator character
      // for the current locale. Limit its contents to a maximum of 5 characters.
      case positiveIntegersOnlyTextField:
        let decimalSeparator = NSLocale.currentLocale().objectForKey(NSLocaleDecimalSeparator) as! String
        return prospectiveText.isNumeric() &&
               prospectiveText.doesNotContainCharactersIn("-e" + decimalSeparator) &&
               prospectiveText.characters.count <= 5
        
      // Do not put constraints on any other text field in this view
      // that uses this class as its delegate.
      default:
        return true
    }
    
  }

This method takes three parameters:

  • textField: the text field that either had a character added to or removed from it.
  • range: the range of the characters within the text field that are to be replaced.
  • string: the replacement string.

Does the change add characters?

The first thing the method does is see if the change adds characters to the text field:

if string.characters.count == 0 {
  return true
}
  • If the user has typed a character or pasted non-empty text into the text field, string is non-empty and has a length greater than zero. In this case, we’ll want to do more processing.
  • If the user has deleted a character, cut text, or simply moved the insertion point, string is empty and has a length of zero. In this case, we don’t want to do any more processing; removing characters means we don’t have to see if we want to disallow any added character or if the maximum number of characters for the text field has been exceeded. We’ll just exit the method, returning true so that the change still happens, whether it’s a deletion, a cut, or moving the insertion point.

What will the text field look like after the change?

We want to figure out what the text field would contain if the change were allowed. We’ll call that the prospective text, which we’ll assign to a local constant called prospectiveText. We can figure out what the prospective text is by using NSString‘s stringByReplacingCharactersInRange method on the contents of textField.text.

Here’s where we run into a problem:

  • In order to use NSString‘s stringByReplacingCharactersInRange method, we need to convert a Swift String into an NSString.
  • The type of a text field’s text property type isn’t String, but String?. That’s because a text field’s value can either be:
    • a string when it contains at least one character, or
    • nil when it’s empty
  • String can be cast into NSString; String? can’t.

To get around this problem, we’re going to create a String constant called currentText, which we’ll fill as follows:

  • If the text field isn’t empty — that is, if its value isn’t nil — we’ll simply assign currentText the value of textField.text.
  • If the text field is empty — that is, if its value is nil — we’ll assign currenttext the value "", the empty string. There’s a difference between nil (which denotes no value) and the empty string (which is a value, just one that has a length of 0 characters).

Here’s the code:

 let currentText = textField.text ?? ""
 let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)

As we’ll see shortly, having prospectiveText lets us set a maximum number of characters that can be put into a text field.

Taking care of business

Now that we’ve dealt with cases where the change to the text field deletes characters and have created prospectiveText, we can now start constraining text fields. This is handled in the switch statement, which we use to separate the constraining logic for each text field:

switch textField {
    
      // Allow only upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 6 characters.
      case vowelsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 6

      // Allow any characters EXCEPT upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 8 characters.
      case noVowelsTextField:
        return prospectiveText.doesNotContainCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 8
        
      // Allow only digits in this field, 
      // and limit its contents to a maximum of 3 characters.
      case digitsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("0123456789") &&
               prospectiveText.characters.count <= 3
        
      // Allow only values that evaluate to proper numeric values in this field,
      // and limit its contents to a maximum of 7 characters.
      case numericOnlyTextField:
        return prospectiveText.isNumeric() &&
               prospectiveText.characters.count <= 7
        
      // In this field, allow only values that evalulate to proper numeric values and
      // do not contain the "-" and "e" characters, nor the decimal separator character
      // for the current locale. Limit its contents to a maximum of 5 characters.
      case positiveIntegersOnlyTextField:
        let decimalSeparator = NSLocale.currentLocale().objectForKey(NSLocaleDecimalSeparator) as! String
        return prospectiveText.isNumeric() &&
               prospectiveText.doesNotContainCharactersIn("-e" + decimalSeparator) &&
               prospectiveText.characters.count <= 5
        
      // Do not put constraints on any other text field in this view
      // that uses this class as its delegate.
      default:
        return true
    }

The cases for each text field are listed in the order in which they appear onscreen. Let’s look at them one by one:

The “Vowels only” text field

In this text field, we want the user to be able to enter only vowels — the upper- and lower-case versions of the letters a, e, i, o, and u. We also want to limit its contents to a maximum of 6 characters. Here’s the code that does this:

      // Allow only upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 6 characters.
      case vowelsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 6

This code makes use of the String extension method containsOnlyCharactersIn, which I defined in StringUtils.swift. It returns true if the String contains only characters in the provided parameter.

If both conditions in the return statement evaluate to trueprospectiveText contains only vowels and has 6 characters or fewer — the method returns true, the change to the text field is allowed, and the text field is updated. If both conditions don’t evaluate to true, the method returns false, the change to the text field is not allowed, and the text field’s contents remain the same.

The “Anything BUT vowels” text field

In this text field, we want the user to be able to enter any character except vowels and limit its contents to a maximum of 8 characters. Here’s the code that does this:

      // Allow any characters EXCEPT upper- and lower-case vowels in this field,
      // and limit its contents to a maximum of 8 characters.
      case noVowelsTextField:
        return prospectiveText.doesNotContainCharactersIn("aeiouAEIOU") &&
               prospectiveText.characters.count <= 8

This code is similar to the code for the “Vowels only” text field. The major difference is that it makes use of another String extension method defined in StringUtils.swift: doesNotContainCharactersIn, which returns true if the String doesn’t contain any of the characters in the provided parameter.

The “Digits only” text field

In this text field, we want the user to be able to enter only digits, and no more than three of them at most. Here’s the code that does this:

      // Allow only digits in this field, 
      // and limit its contents to a maximum of 3 characters.
      case digitsOnlyTextField:
        return prospectiveText.containsOnlyCharactersIn("0123456789") &&
               prospectiveText.characters.count <= 3

This code is almost the same as the code for the “Vowels only” text field.

The “Numeric values only” text field

Here’s an interesting one: a text field that accepts only user input that evaluates to a proper numeric value. That means it will accept the following characters:

  • The digits 0 through 9
  • The (negative) symbol
  • The decimal separator, which is either . or , depending on the user’s locale settings
  • The letter e, which is used for numbers specified in scientific notation

Even when limiting the user to these characters, it’s possible for non-numeric values to be entered. Here are a couple of example non-numeric values that we don’t want the user to be able to enter:

  • More than one decimal separator, such as 1.2.3 or 1,2,3, depending on the user’s locale settings
  • The unary minus being placed anywhere other than the start, such as 4-5

We also want to limit the text field to a maximum of 7 characters.

Here’s the code:

      // Allow only values that evaluate to proper numeric values in this field,
      // and limit its contents to a maximum of 7 characters.
      case numericOnlyTextField:
        return prospectiveText.isNumeric() &&
               prospectiveText.characters.count <= 7

This code makes use of the String extension method isNumeric, which I defined in StringUtils.swift. It returns true if the String contains a value that evaluates to a numeric value. It’s powered by NSScanner, a class that’s handy for going through strings and extracting useful data from them, and its scanDecimal method, which returns true if it finds a value that can be evaluated as an NSDecimal value.

If both conditions in the return statement evaluate to trueprospectiveText evaluates to a numeric value and has 7 characters or fewer — the method returns true, the change to the text field is allowed, and the text field is updated. If both conditions don’t evaluate to true, the method returns false, the change to the text field is not allowed, and the text field’s contents remain the same.

The “Positive integers only” text field

This is a more strict version of the “Numeric values only” text field. It requires that anything entered into it needs to evaluate as a proper numeric value, but it also requires that the value be a positive integer and not be stated in scientific notation. It has a maximum length of 5 characters. Here’s the code:

      // In this field, allow only values that evalulate to proper numeric values and
      // do not contain the "-" and "e" characters, nor the decimal separator character
      // for the current locale. Limit its contents to a maximum of 5 characters.
      case positiveIntegersOnlyTextField:
        let decimalSeparator = NSLocale.currentLocale().objectForKey(NSLocaleDecimalSeparator) as! String
        return prospectiveText.isNumeric() &&
               prospectiveText.doesNotContainCharactersIn("-e" + decimalSeparator) &&
               prospectiveText.characters.count <= 5

In order to disallow negative numbers, we use the String extension method doesNotContainCharactersIn to block out characters. We disallow scientific notation by using the same method to block out e characters. The tricky part is disallowing the decimal separator, which can be either . or , depending on the user’s locale. We identify it with NSLocale.currentLocale().objectForKey(NSLocaleDecimalSeparator), which we add to the parameter for doesNotContainCharactersIn.

Any other text fields that might be on the screen

Finally, we handle the default case: any other text fields that might be on the screen, which we won’t constrain:

      default:
        return true

Other UI goodies

This app has a couple of UI features that I’ll cover in a subsequent article:

  • specifying the keyboard for a specific text field,
  • dismissing the keyboard when the user taps the Return key or its equivalent, and
  • dismissing the keyboard when the user taps on the view

It’s all in the code, so if you’d like to jump in and learn how it works on your own, go right ahead!

Resources

zip file iconIn case you missed it, here are the zipped project files for the demo project, ConstrainedTextFieldDemo [90K Xcode project and associated files, zipped].

Categories
Uncategorized

“Research journal, day 12: The locals have accepted me as one of their own.”

fitting in

Found via AcidCow. Click the photo to see the source.

Categories
Uncategorized

Drunk Tulsa roommates break beer bottles, stab each other in “iPhone vs. Android” debate

stabby scene

Tulsa-based KTUL’s news team reports that two roommates are in the hospital after a drunken “Which is better, iPhone or Android?” argument escalated into a fight with improvised knives.

Both men smashed their beer bottles action movie-style and stabbed each other. One of them even smashed his bottle on the back of the other’s head (which to my mind sounds like the kind of cheap move a “Fandroid” would pull*).

* I’m kidding, Android fans. I myself own a Moto G (2nd generation). Please don’t stab me.

Some thoughts I had after reading the article:

  • I always thought that smashing a bottle and using it as an improvised weapon was something that happened only in the movies and TV.
  • As a former Windows Phone evangelist, I can’t help but feel a sharp pang of jealousy. Windows Phone will never, ever engender this kind of stabby loyalty. I’d bet that even BlackBerry has a few fans who’d happily shank someone in defense of their phone. (I’m also reminded of this clever Windows Phone ad.)
  • love the last line of the news story: “Police did not respond when our photographer asked which phone is better.”

If you’re the kind of person whose identity is so tied up with a smartphone platform that you can’t have an argument about your favorite one without getting into a murderous brawl, you might want to invest in a case with an integrated blade, like the TaskOne G3, pictured below:

iphone knife case

Thanks to Ethan Henry for the find!

Categories
Uncategorized

BusinessTown: Richard Scarry for the Fast Company / BusinessWeek / TechCrunch set

If you enjoyed Richard Scarry’s “Busytown” books as a kid…

busytown

…and if you work in an environment where phrases like “highly intangible”, “deeply-disruptive”, “data-driven”, and “where’s the value-add?” are the norm, where “lessons” got verbed into “learnings” and “spending” got nouned into “spend”, and everything you do is “agile”, you might enjoy BusinessTown, which takes the Ricard Scarry aesthetic and runs it through the Fast Company/Bloomberg Businessweek/TechCrunch blender:

businesstown

Thanks to Leigh Honeywell for the find!

Categories
Uncategorized

Interesting data from the Pew Research Center’s “US Smartphone Use in 2015” report

share of smartphone users in us

If you need information on smartphone use in America, look no further than the Pew Research Center’s U.S. Smartphone Use in 2015 report. It’s a very thorough, 60-page document based on data from polls and surveys conducted in the October and November 2014. Using a technique called “experience sampling” — in which participants completed two surveys a day over a week — Pew were able to create “a unique and intimate portrait of smartphone usage” in the U.S..

the smartphone-dependent

Some notable items in the report:

  • Nearly two-thirds of Americans own a smartphone. This is double the rate of ownership from just four years ago.
  • There’s a small but significant portion of the population that’s “smartphone-dependent”:  their smartphone is their primary, and sometimes, their only way to the online world. 7% of American smartphone users don’t have a broadband internet access as home, nor do they have easy access to the internet from sources other than their smartphone. About 1 in 5 Americans rely on smartphones to some degree to go online, because their online access options are few.
  • Smartphone ownership is often most tenuous for those who rely on them the most. About half of the smartphone-dependent have had to cancel or suspend their mobile service because it cost too much. The smartphone-dependent are also far more likely to hit their data plan maximum than other users.

cant live without em

If you need to know the state of smartphone use in the U.S. today, be sure to download Pew’s report [1.6 MB PDF]. We’ve taken some of the key points of this report and turned them into the infographics in this article, which you’re free to use in your presentations — all we ask is that you say that you got them from GSG.

canceled on account of cost

this article also appears in the GSG blog

Categories
Current Events Tampa Bay Uncategorized

While I was away (or: Getting married and getting my 2011 MacBook fixed)

Things have been quiet lately here on Global Nerdy. I have a couple of good excuses. Here’s the first one:

Photo: Anitra Pavka and Joey deVilla at their wedding.

Anitra and me, shortly after getting married. Click the photo to see more.

On Saturday, March 7th, the lovely lady for whom I moved from Toronto to Tampa and I got married. We had our ceremony on St. Pete Beach followed by a brunch reception on the penthouse ballroom of the Grand Plaza Hotel, a stunning room that looks like it would’ve made a great hideout for Sean Connery-era Bond villain. As you might expect, this big life event took priority over a great many things, including blogging.

Photo: Joey deVilla's MacBook Pro (early 2011 15-inch model), as seen from his vantage point in an Air Canada window seat. In the background, a movie plays on the seat-back in-flight entertainment system.

“El Guapo”, my trusty early 2011 15″ MacBook Pro, circa 2012.

My other excuse is technological: “El Guapo”, my trusty early 2011 15″ MacBook Pro, was beginning to show some glitchy behavior just before my wedding. (Maybe it had some reservations about leaving the bachelor computing lifestyle.) At first, it started running hotter than usual, and its fans would often be going full bore. Then came the random restarts. Finally, it would fail to boot up. It would start up fine…

Photo: MacBook Pro screen, showing the OS X Yosemite progress bar a little past the halfway mark.

Photo taken from the Stack Exchange Forum Ask Different. Click to see the source.

…but after the progress bar filled up — a process that seemed to take a little longer than usual — it would display a blank gray screen like the one shown below:

Photo: MacBook Pro showing a bank gray screen.

Photo taken from the Stack Exchange Forum Ask Different. Click to see the source.

At this point, the computer would either hang there or spontaneously reboot, which would lead back to the blank gray screen.

The first resort: Resetting the NVRAM, a.k.a. the PRAM

Long-time Mac users know that when a Mac starts exhibiting wonky behavior, resetting the PRAM — that’s short for Parameter RAM — often does the trick. Those same long-time Mac users are also the ones who still call it PRAM; Apple’s moved on from that old name and now calls it NVRAM, which stands for Non-Volatile RAM. No matter what you call it, NVRAM/PRAM is a small store of persistent, writable memory that contains a number of system settings and configurations, including:

  • Which disk is your startup disk
  • Networking and port configurations
  • User input settings such as autokey delay and rate, mouse speed, the blink rate of the “text insert point” cursor, how quickly you have to double-click for it to register as a double-click, and so on…
  • All manner of memory settings, including virtual memory, disk cache, and RAM disk

When your Mac starts acting strangely, it’s often the case that the configuration data inside your NVRAM somehow got corrupted. Luckily, there’s a simple but not-very-obvious way to reset your NVRAM, and it’s all based on a combination of keys that you need to press simultaneously when your Mac boots up. This trick goes back to the days when it was still called PRAM, which explains the keys you have to press:

Headline: The key combo for resetting the PRAM on your Mac when you power up / Photo: Mac keyboard with "command", "option", "P", and "R" keys highlighted.

The steps are simple:

  1. Shut down your Mac. Don’t just log out or put it to sleep, shut it all the way down.
  2. Press the power button to turn your Mac on. Get ready to pounce on the keyboard.
  3. Hold down these keys simultaneously: command, option, P, and R. Make sure that you’re holding down this combination of keys before the gray screen appears. You have to be quick.
  4. Your Mac will reboot. Wait for the startup sound, then let go of the keys and let the machine boot as usual.

While this trick has served me well in the past, it didn’t work in this case. It was time for the next trick: resetting the SMC.

The second resort: Resetting the SMC

The SMC — short for System Management Controller — doesn’t have as long a history as the PRAM, as it was introduced with Intel-based Macs (prior to that. Macs were based on Motorola CPUs). The SMC controls a number of interesting hardware goodies, including indicator lights, the display and keyboard backlights, the cooling fans, power, and more.

The steps for resetting the SMC vary depending on the model of Mac. For my particular machine (once again, it’s an early 2011 15″ MacBook Pro), here are the steps:

  1. Shut down your Mac. Don’t just log out or put it to sleep, shut it all the way down.
  2. Unplug the power cord and all peripherals.
  3. Press and hold the power button for 5 seconds.
  4. Release the power button.
  5. Reconnect the power cord.
  6. Press the power button and let the machine boot as usual.

I’m told this trick fixes a lot of hardware weirdness, but not for me. It was time to take it to the shop, but before I could do that, I wanted to back up some files.

Target disk mode: turning your Mac into a drive that other Macs can use

Target disk mode allows a Mac to function as a drive that other Macs can access. My plan was to hook up the following to my wife’s perfectly-functioning Mac:

  • My Mac, with a Thunderbolt cable, and
  • An external hard drive, with a USB cable.

My plan: to boot my Mac into target disk mode, after which I would copy the files I wanted from my Mac to the external hard drive. I crossed my fingers and booted my Mac into target disk mode using the magic key:

Headline: To boot your Mac in target disk mode, hold the T key while booting up / Photo: Mac keyboard with the "T" key highlighted.

Luckily for me, my Mac was working just well enough to boot into target disk mode. You’ll know when a Mac is in this mode by what it shows on its display: a screensaver-like display of a Thunderbolt or FireWire icon that pops up on random locations on the screen. I used a Thunderbolt cable to connect my Mac to my wife’s (it feels a little odd typing wife rather than girlfriend), and the setup looked something like this:

Headline: Wife's Mac - Normal Mode --- My Mac - Target Disk Mode / Image: Two MacBooks connected via ThunderBolt cable, with one Mac showing a window on its screen and the other Mac showing the Thunderbolt icon on its screen.

With my files backed up so that I could work on them with my backup machine, a Windows/Ubuntu box, it was time to take it to the shop.

Good news, bad news, and a NetBoot at the shop

Photo: The storefront of the PeachMac at Citrus Park Mall in Tampa.

The closest authorized Apple dealer and repair shop to me in the PeachMac at Citrus Park Mall. A number of people I know from the Suncoast iOS Meetup group consider it their go-to store for Macs and Macessories, and they’ve generally done right by me as well. I took my machine to the service desk, where they plugged an ethernet cable into it and performed a netboot in order to run their diagnostics application:

Headline: To boot your Mac from the network, hold the N key while booting up / Photo: Mac keyboard with the "N" key highlighted.

Everything except the graphics card checked out fine. “Yeah, I figured that was the problem — it’s been happening with a lot of 2011 MacBooks.”

“What’s been happening with a lot of 2011 MacBooks?” I asked.

The AMD graphics cards on that line of MacBooks, especially the early 2011 models, have been crapping out. In the beginning, Apple just said that you should zap your PRAM, reset the SMC, or even reinstall the OS. That works — for a little while. Then the graphics card just dies again, and your machine’s hanging in mid-boot with nothing but a gray screen. The real fix is a replacement motherboard.”

“And how much is that going to cost?” I was already wondering if I’d have to drop some cash for a new machine. I was hoping to put off that kind of purchase until next year.

“You’re in luck. Apple’s not calling it a recall, but they’ve got a ‘Repair Extension Program’, and you’ll get a brand new mobo for free…but in your case, there’s a hitch.”

Now what? I thought.

“It’s the replacement battery you put in. In order to qualify for this repair, your machine can’t have any non-standard parts in it. We can’t fix it as it is right now, but if you were to go home and put the original battery back in and bring it back here, we wouldn’t know about it, nudge nudge wink wink.”

Banner: Is your MacBook eligible for a free motherboard replacement? Click here to enter its serial number into Apple's Service and Support Coverage page.

I had a new problem: I’d already recycled the old battery, as it barely held a charge and was just taking up space in my home office as an inert, useless block.

Replacing the battery

Screen capture: eBay page for an Apple A1382 laptop battery

Click the screen shot to visit the eBay page.

eBay to the rescue! I found a dealer selling A1382 batteries — the kind that early 2011 15″ MacBook Pros take — for much cheaper than even the replacement battery I bought through OWC. I didn’t need this battery to be any good; I just needed it to be a genuine Apple one in order to qualify for the free repair.

The battery on the 2011-era MacBooks is technically replaceable, but Apple make it a little difficult by holding it in place with tri-lobe screws, which look like Philips screws, but with one less “wing”:

Photo: Two tri-lobe screws.

Your local hardware store doesn’t typically stock tri-lobe screwdrivers, but they can be ordered online, and the non-Apple replacement battery I got from OWC comes with all the screwdrivers you need to install it. Luckily for me, I’d decided to keep them, which made this operation possible:

Back in action

With a standard Apple battery back in its belly, I brought my MacBook back to PeachMac. They ran the diagnostics again, and this time, the support guy — not the same guy I talked to during my earlier visit — pointed out that I should discharge my battery from time to time. “Don’t leave it plugged in all the time,” he said, not knowing that I’d had the battery for all of one day.

“We’ll call you when we’ve finished swapping out the motherboard,” he said. “It’s pretty quick to do. The slow part is getting it shipped to us.”

With my main machine in the shop, I pressed my backup machine — a Lenovo T430, the quintessential TPS Reports writing machine — into active duty. It has an annoying habit of dropping wifi connections, even with the latest drivers installed.

Photo: Joey deVilla's MacBook Pro, complete with 'Nyan Cat' sticker on the palm rest.

“El Guapo”, my trusty early 2011 15″ MacBook Pro, at the time of this writing (April 2, 2015).

They got the job done in a couple of business days. The new motherboard looks newer, as the markings on the chips don’t look as faded by the heat of regular operation, but the real sign is that it takes a little extra force to insert cables into the USB and Thunderbolt jacks; it feels like breaking in a new pair of shoes. The PeachMac guys even replaced a couple of the rubber feet that had gone missing from the bottom of the machine over the years, as well as one of the screws I lost while upgrading my RAM a little while back, all free of charge.

With my preferred machine back in action, I’ll be able to get back to writing iOS apps, as well as iOS development tutorials here on Global Nerdy. Keep watching this space!