Categories
Swift Kick

A better way to program iOS text fields that have maximum lengths and accept or reject specific characters

In my last article on iOS programming in Swift, I showed you how to add some code to your project that would allow you to add a “Max Length” property to all its text fields, which you could set in code, or better yet, in Xcode’s Interface Builder, as shown below:

max chars text field

For more details, you should take a look at that article, iOS programming trick: How to use Xcode to set a text field’s maximum length, Visual Studio-style.

A key part of what made it work was the @IBInspectable keyword. When applied to a property of an object that can be displayed in Interface Builder, it makes that property available to the Attributes Inspector, which in turn provides a user interface for viewing and changing that property.

That got me thinking: could I apply @IBInspectable to the text field tricks I covered in my April 2015 article, How to program an iOS text field that takes only numeric input or specific characters with a maximum length?

In that article, I covered how to program text fields that accepted only specified characters or accepted all characters except a specified set, and it required making the containing view controller adopt the UITextFieldDelegate protocol and use itself as its own delegate, and then implementing the textField(_:shouldChangeCharactersInRange:replacementString:) method.

Wouldn’t it be nice if it were simpler? Consider the illustration below of an “Allowed Chars Text Field”. It lets me specify the following in Interface Builder:

  1. That characters that are allowed to be entered into the text field, and
  2. The maximum number of characters that are allowed to be entered into the text field.

allowed chars text field

This works if the set of characters that I want to allow into the text field is small. How about the case where I want to do the opposite: prevent the user from entering a small set of specified characters? That would be a “Banned Chars Text Field”, and it would allow me to specify the following in Interface Builder:

  1. That characters that are not allowed to be entered into the text field, and
  2. The maximum number of characters that are allowed to be entered into the text field.

banned chars text field

Let’s make these happen!

Limiting the number of characters that can be put into a text field

In the article iOS programming trick: How to use Xcode to set a text field’s maximum length, Visual Studio-style, we put the following code into a file named TextFieldMaxLengths.swift:

import UIKit

// 1
private var maxLengths = [UITextField: Int]()

// 2
extension UITextField {
  
  // 3
  @IBInspectable var maxLength: Int {
    get {
      // 4
      guard let length = maxLengths[self] else {
        return Int.max
      }
      return length
    }
    set {
      maxLengths[self] = newValue
      // 5
      addTarget(
        self,
        action: #selector(limitLength),
        forControlEvents: UIControlEvents.EditingChanged
      )
    }
  }
  
  func limitLength(textField: UITextField) {
    // 6
    guard let prospectiveText = textField.text
      where prospectiveText.characters.count > maxLength else {
        return
    }
    
    let selection = selectedTextRange
    // 7
    text = prospectiveText.substringWithRange(
      Range<String.Index>(prospectiveText.startIndex ..< prospectiveText.startIndex.advancedBy(maxLength))
    )
    selectedTextRange = selection
  }
  
}

Once again, here are the annotations for the numbered comments in the code:

  1. There are two big things going on in this single line of code, which declares and initializes maxLengths, a dictionary that stores the maximum lengths of text fields:
    • First, there’s the private declaration. In many programming languages, private means “accessible only inside the class”, but in Swift, private means “accessible only inside the source file where they’re defined”. Any code inside TextFieldMaxLengths.swift has access to maxLengths, and any code outside TextFieldMaxLengths.swift does not. By putting maxLengths in the same file as our UITextField extension, we get a place where we can store the maximum lengths of text fields (remember: extensions can only add methods, not properties), and by making it private, we keep other code from messing with it.
    • Then there’s the matter of what to use as the key for the maxLengths dictionary. Swift lets you use anything that conforms to the Hashable protocol as a dictionary key, and UITextField does just that. It makes sense to use the text fields themselves as the keys to the values for their maximum lengths.
  2. Swift extensions let you add new functionality to existing classes, structs, enumerations, and protocols. We’re using an extension to UITextField to add two things:
    • maxLength, a property that lets the programmer set and get the maximum length of a text field, and
    • limitLength, a method called whenever the contents of a text field are changed, and limits the number of characters in that text field.
  3. By marking the maxLength property with @IBInspectable, we make it available to Interface Builder, which then provides an editor for its value in the Attributes Inspector.
  4. 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 no maximum length has been defined for the text field, in which case, we simply return the theoretical maximum string size.
  5. We use addTarget in maxLength‘s setter to ensure that if a text field is assigned a maximum length, the limitLength method is called whenever the text field’s contents change.
  6. Another guard statement. Any case that gets past it is one where the text about to go into the text field is longer than the maximum length.
  7.  Cocoa sometimes likes to make things complicated. This line is the Cocoa way of saying “put the first maxLength characters of prospectiveText into text“. If you’re going to be playing with substrings, you need to get comfortable with Ranges and intervals.

max length text field

Any Xcode project containing the code in TextFieldMaxLengths.swift will have text fields featuring “maximum length” properties that can be set in either Interface Builder or in code.

text2 screenshot

If you’d like to see these “max length” text fields in action, download the Text2 project (the running app is pictured above) and try them out!

AllowedCharsTextField: A text field that allows only specific characters

Let’s build on our new, improved text fields by creating one that accepts only characters from a defined set. You might need a text field that accepts only digits, or punctuation, or the letters A, C, G, and T. We’ll make setting up such a text field easy by creating a subclass of UITextField, which we’ll call AllowedCharsTextField. Since it’s a subclass of UITextField, it inherits all its capabilities, which includes any extensions, including the “max length” one we created.

Use File → New → File… to create a new Swift File. Give it the name AllowedCharsTextField.swift, and once you’ve created it, enter the following code into it:

import UIKit


// 1
class AllowedCharsTextField: UITextField, UITextFieldDelegate {
  
  // 2
  @IBInspectable var allowedChars: String = ""
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    // 3
    delegate = self
    // 4
    autocorrectionType = .No
  }

  // 5  
  func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {  
    // 6
    guard string.characters.count > 0 else {
      return true
    }
    
    // 7
    let currentText = textField.text ?? ""
    let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)
    return prospectiveText.containsOnlyCharactersIn(allowedChars)
  }
  
}


// 8
extension String {

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

}

Here’s what’s happening just after the numbered comments in the code:

  1. Our class inherits from UITextField to get the capabilities of a text field, and it adopts the UITextFieldDelegate protocol to be able to respond to the textField(_:shouldChangeCharactersInRange:replacementString:) method in order to intercept changes to the text field just after they’ve been input by the user, but before they’re committed to the text field.
  2. allowedChars stores the characters that we’ll allow in the text field, and @IBInspectable makes its value editable in Interface Builder. Interface Builder takes the code-cased allowedChars property name and displays it as Allowed Chars in the Attributes Inspector.
  3. This indicates that this class is the UITextFieldDelegate and will implement at least one UITextFieldDelegate method — in this case, textField(_:shouldChangeCharactersInRange:replacementString:).
  4. We disable autocorrection because it has a tendency to bypass the the textField(_:shouldChangeCharactersInRange:replacementString:) method that we use to detect allowed characters. I’ll admit it; this is a quick a dirty fix. That being said, you probably don’t want “autocucumber” in a text field like this anyway. I’m looking into ways to allow autocorrection and still have this text field work as designed.
  5. This method gets called after the user has made changes to the text field, but before they’re committed. It gives us a chance to cancel those changes. The method should return true if we want to accept the changes, and false otherwise.
  6. We’re only concerned about cases where characters are added, so if this string has a length of 0, exit early.
  7. Here’s where the real work is done:
    • ?? is Swift’s nil coalescing operator. It returns the left operand if it’s not nil, otherwise it returns the right operation (e.g.: x ?? y returns x if it isn’t nil, otherwise it returns y).
    • We create prospectiveText, which is the text that would result if we accept the user’s changes.
    • If prospectiveText contains only the allowed characters, we return true; otherwise, we return false.
  8. We’re using an extension of String to give it a containsOnlyCharactersIn function, which returns true if the string contains only the characters in the given string, false otherwise.

That’s all the code we need to create AllowedCharsTextField. To use it, drag a plain ol’ text field onto a view in the storyboard and do the following:

allowed chars 1

1. Select the Identity Inspector.
2. Select the text field.
3. Change its class to AllowedCharsTextField.

By default, the underlying class for a text field in the Storyboard is UITextField. However, the Identity Inspector lets you change the classes of things on the storyboard to other classes related by inheritance. If you got to the Custom Class  menu in the Identity Inspector and expand the Class drop-down menu, you’ll see that you now have two choices: AllowedCharsTextField and UITextField. Changing this value to AllowedCharsTextField marks the text field as an instance of our new AllowedCharsTextField instance instead of a regular UITextField.

You’ll know it’s working in the next couple of steps:

allowed chars 2

4. Select the Attributes Inspector.
5. You can now define allowed characters and maximum length in Interface Builder.

You can also define allowed characters and maximum length in code:

// myTextField is an instance of AllowedCharsTextField.
// It should accept up to 7 characters,
// and they MUST be vowels.
myTextField.allowedChars = "aeiou"
myTextField.maxLength = 7

BannedCharsTextField: A text field that bans only specific characters

AllowedCharsTextField works well for text fields where the set of characters that we want to allow into it is relatively small. There may be times when you want the opposite: a text field that accepts all characters, except for a certain few specific ones. Let’s build one in the same way we built AllowedCharsTextField and call it BannedCharsTextField.

Use File → New → File… to create a new Swift File. Give it the name BannedCharsTextField.swift, and once you’ve created it, enter the following code into it:

import UIKit


// 1
class BannedCharsTextField: UITextField, UITextFieldDelegate {
  

  // 2
  @IBInspectable var bannedChars: String = ""
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    // 3
    delegate = self
    // 4
    autocorrectionType = .No
  }
  
  // 5
  func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    // 6
    guard string.characters.count > 0 else {
      return true
    }
    
    // 7
    let currentText = textField.text ?? ""
    let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)
    return prospectiveText.doesNotContainCharactersIn(bannedChars)
  }
  
}


// 8
extension String {
  
  // 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
  }
  
}

Here’s what’s happening just after the numbered comments in the code:

  1. Our class inherits from UITextField to get the capabilities of a text field, and it adopts the UITextFieldDelegate protocol to be able to respond to the textField(_:shouldChangeCharactersInRange:replacementString:) method in order to intercept changes to the text field just after they’ve been input by the user, but before they’re committed to the text field.
  2. bannedChars stores the characters that we’ll allow in the text field, and @IBInspectable makes its value editable in Interface Builder. Interface Builder takes the code-cased bannedChars property name and displays it as Banned Chars in the Attributes Inspector.
  3. This indicates that this class is the UITextFieldDelegate and will implement at least one UITextFieldDelegate method — in this case, textField(_:shouldChangeCharactersInRange:replacementString:).
  4. We disable autocorrection because it has a tendency to bypass the the textField(_:shouldChangeCharactersInRange:replacementString:) method that we use to detect banned characters. I’ll admit it; this is a quick a dirty fix. That being said, you probably don’t want “autocucumber” in a text field like this anyway. I’m looking into ways to allow autocorrection and still have this text field work as designed.
  5. This method gets called after the user has made changes to the text field, but before they’re committed. It gives us a chance to cancel those changes. The method should return true if we want to accept the changes, and false otherwise.
  6. We’re only concerned about cases where characters are added, so if this string has a length of 0, exit early.
  7. Here’s where the real work is done:
    • ?? is Swift’s nil coalescing operator. It returns the left operand if it’s not nil, otherwise it returns the right operation (e.g.: x ?? y returns x if it isn’t nil, otherwise it returns y).
    • We create prospectiveText, which is the text that would result if we accept the user’s changes.
    • If prospectiveText contains only the allowed characters, we return true; otherwise, we return false.
  8. We’re using an extension of String to give it a doesNotContainCharactersIn function, which returns true if the string doesn’t contain any of the characters in the given string, false otherwise.

That’s all the code we need to create BannedCharsTextField. To use it, drag a plain ol’ text field onto a view in the storyboard and do the following:

banned chars 1

1. Select the Identity Inspector.
2. Select the text field.
3. Change its class to BannedCharsTextField.

By default, the underlying class for a text field in the Storyboard is UITextField. However, the Identity Inspector lets you change the classes of things on the storyboard to other classes related by inheritance. If you got to the Custom Class  menu in the Identity Inspector and expand the Class drop-down menu, you’ll see that you now have three choices: AllowedCharsTextFieldBannedCharsTextField, and UITextField. Changing this value to BannedCharsTextField marks the text field as an instance of our new BannedCharsTextField instance instead of a regular UITextField.

You’ll know it’s working in the next couple of steps:

banned chars 2

4. Select the Attributes Inspector.
5. You can now define allowed characters and maximum length in Interface Builder.

You can also define allowed characters and maximum length in code:

// myTextField is an instance of BannedCharsTextField.
// It should accept up to 7 characters,
// and they CANNOT be vowels.
myTextField.bannedChars = "aeiou"
myTextField.maxLength = 7

“Allowed + Banned Chars Text Fields”: A sample project showing text fields with maximum lengths, AllowedCharsTextField, and BannedCharsTextField in action

allowed - banned chars app screenshot

You’re probably raring to try out the code from this article. Here’s a project you can play with that presents a quick-and-dirty single view app with 4 text fields:

  1. A text field a maximum length of 6 characters.
  2. A vowels-only text field with a 5-character maximum length, with the Max Length and Allowed Chars properties set in Interface Builder.
  3. A no-vowels-allowed text field with a 7-character maximum length, with the Max Length and Banned Chars properties set in Interface Builder.
  4. A text field with a 10-character maximum length that accepts only the characters from the word freaky. Its maxLength and allowedChars properties were set in code in the view controller’s viewDidLoad method.

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 (37KB zipped) here.

Categories
Swift Kick

How to build an iOS weather app in Swift, part 2: A little more explanation, and turning OpenWeatherMap’s JSON into a Swift dictionary

smartphone weather

Welcome to part two in a series of articles on building your own iOS weather app in Swift!

The previous article in this series showed you how to:

  • Get a key for OpenWeatherMap’s API for current weather data
  • Make a manual API call using your browser
  • Create a basic app to get the data from OpenWeatherMap
  • Tweak the app

In this article, we’ll explain the code that was presented in the previous article and make the next major step in writing our weather app: extracting the data from the JSON returned by OpenWeatherMap.

A deeper look at the code that gets the weather data

In the last installment, I gave you enough code to connect to OpenWeatherMap and retrieve the weather data, but never explained how it works. Let’s fix that oversight.

Our simple weather app makes use of the NSURLSession class along with some classes that go along with it. Together, classes in the NSURLSession family of classes give us an API for downloading and uploading data from the internet, in a number of different ways.

The diagram below shows the classes that our weather app works with, either directly or indirectly:

nsurlsession diagram

Here’s a slightly more in-depth explanation of the classes in the diagram above:

NSURLSession class Instances of NSURLSession are containers — collections of other objects — that provide an API for sending data to and receiving data from a given URL. You use the NSURLSession API to create one or more sessions, which are objects that coordinate one or more tasks, which are objects that actually transfer the data.
Shared session object The shared session object is a pre-made singleton instance of NSURLSession. It’s not as configurable as other NSURLSession instances that you can instantiate yourself, but for simple requests, such as the kind we’re making in our bare-bones weather app, it’s good enough. You access the shared session with NSURLSession‘s sharedSession class method.
NSURLSessionTask class As mentioned in the explanation of NSURLSession above, tasks are the objects within an NSURLSession instance that actually transfer the data. NSURLSessionTask is the base class for tasks.
NSURLSessionDataTask class

You typically don’t instantiate an NSURLSessionTask object, but one of its subclasses, and you do so by calling one of NSURLSession‘s task creation methods. NSURLSessionDataTask is the class for data tasks, which are used to download data from a given server into memory. We’re using an NSURLSessionDataTask instance to request and collect data from OpenWeatherMap.

In addition to data tasks, there are also upload tasks, which are used to upload data to a server, and download tasks, which are used for downloading data from a server into a file (as opposed to memory, which is where data tasks download their data to).

That’s the “big picture” view. Keep it in mind as we look at the code that does the downloading, contained in the getWeatherInfo function in the WeatherGetter class:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "06db44f389d2172e9b1096cdce7b051c"
  
  func getWeatherInfo(city: String) {
    
    // *** 1 ***
    let session = NSURLSession.sharedSession()

    // *** 2 ***
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // *** 3 ***
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      // *** 4 ***
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      // *** 5 ***
      else {
        // Case 2: Success
        // We got a response from the server!
        print("Raw data:\n\(data!)\n")
        let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
        print("Human-readable data:\n\(dataString!)")
      }
    }

    // *** 6 ***
    dataTask.resume()
  }
  
}

I’ve added number comments to the code, which correspond to the explanations below:

  1. We use NSURLSession‘s class method sharedSession to get a reference to the shared session object, and assign that reference to the local variable session.
  2. We construct an URL using the URL for OpenWeatherMap’s current weather API, filling in the blanks with the name of the city that we want the weather for, and our OpenWeatherMap API key.
  3. We want to use a data task to request and retrieve the data from OpenWeatherMap. As mentioned earlier, tasks are created by using one of NSURLSession‘s task-creation methods. In this case, we’re using dataTaskWithURL, which we provide with 2 arguments:
    • The URL created in step 2, and
    • A completion handler that executes once the task is done requesting and retrieving the data. The completion handler will receive three arguments, which we can work with:
      1. data: If the request was successfully made and the data was successfully received, this will contain that received data.
      2. response: The response from the server.
      3. error: If the request was not made successfully or the data was not received successfully, this will contain information about the error.
  4. If the error argument isn’t nil, we assign its value to a local variable also named error, and then display its contents.
  5. If the error argument is nil, it means that no error occurred, and we display both the server response and the data received, in both raw and human-readable formats.
  6. Up until this point, we’ve only defined the data task. This statement activates it.

Turning the weather data into a Swift dictionary

Right now, if the weather request was successfully made and the data was successfully received, we take that data, convert it into a human-readable string, and output it to the debug console…

// Case 2: Success
// We got a response from the server!
print("Raw data:\n\(data!)\n")
let dataString = String(data: data!, encoding: NSUTF8StringEncoding)
print("Human-readable data:\n\(dataString!)")

…and that human-readable string looks something like this:

{"coord":{"lon":-82.46,"lat":27.95},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"cmc stations","main":{"temp":295.49,"pressure":1015,"humidity":64,"temp_min":294.15,"temp_max":296.15},"wind":{"speed":2.1,"deg":280},"clouds":{"all":1},"dt":1460163717,"sys":{"type":1,"id":728,"message":0.004,"country":"US","sunrise":1460200241,"sunset":1460245929},"id":4174757,"name":"Tampa","cod":200}

We could simply perform all sorts of string operations to extract the data we need, but why should we? The data is already in JSON format, which maps nicely to data structures in most programming languages, including Swift. There should be a simple way to take that incoming data and turn it into a nice Swift dictionary.

Enter the NSJSONSerialization class, which can convert JSON into Foundation objects (such as arrays and dictionaries), and vice versa. It has a method called JSONObjectWithData that takes two arguments:

  • data: An NSData object containing JSON data, which we happen to have as one of the parameters of our completion handler, and
  • options: Options that specify how the JSON data should be read and how the corresponding Foundation objects should be created.

Let’s change the code so that we no longer turn the data into a string, but instead using NSJSONSerialization‘s JSONObjectWithData method to turn it into a dictionary:

// Case 2: Success
// We got a response from the server!
do {
  // Try to convert that data into a Swift dictionary
  let weather = try NSJSONSerialization.JSONObjectWithData(
    data!,
    options: .MutableContainers) as! [String: AnyObject]

  // If we made it to this point, we've successfully converted the
  // JSON-formatted weather data into a Swift dictionary.
  // Let's print its contents to the debug console.
  print("Date and time: \(weather["dt"]!)")
  print("City: \(weather["name"]!)")
  
  print("Longitude: \(weather["coord"]!["lon"]!!)")
  print("Latitude: \(weather["coord"]!["lat"]!!)")

  print("Weather ID: \(weather["weather"]![0]!["id"]!!)")
  print("Weather main: \(weather["weather"]![0]!["main"]!!)")
  print("Weather description: \(weather["weather"]![0]!["description"]!!)")
  print("Weather icon ID: \(weather["weather"]![0]!["icon"]!!)")

  print("Temperature: \(weather["main"]!["temp"]!!)")
  print("Humidity: \(weather["main"]!["humidity"]!!)")
  print("Pressure: \(weather["main"]!["pressure"]!!)")

  print("Cloud cover: \(weather["clouds"]!["all"]!!)")

  print("Wind direction: \(weather["wind"]!["deg"]!!) degrees")
  print("Wind speed: \(weather["wind"]!["speed"]!!)")

  print("Country: \(weather["sys"]!["country"]!!)")
  print("Sunrise: \(weather["sys"]!["sunrise"]!!)")
  print("Sunset: \(weather["sys"]!["sunset"]!!)")
}
catch let jsonError as NSError {
  // An error occurred while trying to convert the data into a Swift dictionary.
  print("JSON error description: \(jsonError.description)")
}

If you run the app now, you’ll see that when put into dictionary form, it’s easy to extract weather data:

Date and time: 1462277829
City: Tampa
Longitude: -82.45999999999999
Latitude: 27.95
Weather ID: 800
Weather main: Clear
Weather description: clear sky
Weather icon ID: 02d
Temperature: 294.659
Humidity: 95
Pressure: 1025.47
Cloud cover: 8
Wind direction: 168.003 degrees
Wind speed: 2.11
Country: US
Sunrise: 1462272456
Sunset: 1462320359

Here’s what the complete WeatherGetter.swift should look like now:

import Foundation

class WeatherGetter {
  
  private let openWeatherMapBaseURL = "http://api.openweathermap.org/data/2.5/weather"
  private let openWeatherMapAPIKey = "YOUR API KEY HERE"
  
  func getWeather(city: String) {
    
    // This is a pretty simple networking task, so the shared session will do.
    let session = NSURLSession.sharedSession()
    
    let weatherRequestURL = NSURL(string: "\(openWeatherMapBaseURL)?APPID=\(openWeatherMapAPIKey)&q=\(city)")!
    
    // The data task retrieves the data.
    let dataTask = session.dataTaskWithURL(weatherRequestURL) {
      (data: NSData?, response: NSURLResponse?, error: NSError?) in
      if let error = error {
        // Case 1: Error
        // We got some kind of error while trying to get data from the server.
        print("Error:\n\(error)")
      }
      else {
        // Case 2: Success
        // We got a response from the server!
        do {
          // Try to convert that data into a Swift dictionary
          let weather = try NSJSONSerialization.JSONObjectWithData(
            data!,
            options: .MutableContainers) as! [String: AnyObject]

          // If we made it to this point, we've successfully converted the
          // JSON-formatted weather data into a Swift dictionary.
          // Let's print its contents to the debug console.
          print("Date and time: \(weather["dt"]!)")
          print("City: \(weather["name"]!)")
          
          print("Longitude: \(weather["coord"]!["lon"]!!)")
          print("Latitude: \(weather["coord"]!["lat"]!!)")

          print("Weather ID: \(weather["weather"]![0]!["id"]!!)")
          print("Weather main: \(weather["weather"]![0]!["main"]!!)")
          print("Weather description: \(weather["weather"]![0]!["description"]!!)")
          print("Weather icon ID: \(weather["weather"]![0]!["icon"]!!)")

          print("Temperature: \(weather["main"]!["temp"]!!)")
          print("Humidity: \(weather["main"]!["humidity"]!!)")
          print("Pressure: \(weather["main"]!["pressure"]!!)")

          print("Cloud cover: \(weather["clouds"]!["all"]!!)")

          print("Wind direction: \(weather["wind"]!["deg"]!!) degrees")
          print("Wind speed: \(weather["wind"]!["speed"]!!)")

          print("Country: \(weather["sys"]!["country"]!!)")
          print("Sunrise: \(weather["sys"]!["sunrise"]!!)")
          print("Sunset: \(weather["sys"]!["sunset"]!!)")
        }
        catch let jsonError as NSError {
          // An error occurred while trying to convert the data into a Swift dictionary.
          print("JSON error description: \(jsonError.description)")
        }
      }
    }
    
    // The data task is set up...launch it!
    dataTask.resume()
  }
  
}

At this point, the WeatherGetter class doesn’t just get the weather data from OpenWeatherMap; it also puts that data into a form that we can process: a Dictionary. We’re still displaying the information in the debug console — we’re still not showing it to anyone who’s not running the app with Xcode.

In the next installment in this series, we’ll take the weather data, now in dictionary form, and make it visible to the user. We’ll also make it possible for the user to enter a city to get the weather for, rather than hard-wire it into the app.

xcode download

You can download the project files for this aricle (41KB zipped) here.

Categories
Florida Swift Kick Tampa Bay

Share your Swift tips and tricks at this week’s Tampa iOS meetup: Wednesday, November 18!

swift tips and tricks meetup

Hey, Tampa Bay iOS developers — here’s your chance to shine! At the next Tampa iOS Meetup (Wednesday, November 18th at 7:00 p.m.), a monthly gathering run by me and my friend Angela, we’re having a “Swift Tips and Tricks” night, where we’ll take turns sharing tips an tricks that we’ve either discovered on our own or found through others while programming in Swift.

Have you ever wanted to present something at an iOS meetup, but it was on a topic or technique that could easily be covered in ten or even five minutes? Well, this meetup is your chance to be an iOS rock star, as short presentations is what it’s all about! Whether you’ve been building apps since the Objective-C days or picked up Swift a couple of weeks ago, you’ve got knowledge to share with your fellow developers, who in turn have knowledge to share with you! Join us for an evening of demos, information exchange, and that buzz that you get when you’re in a room of smart, interesting people, one of whom is you!

Me and Angela at BarCamp Tampa Bay 2015.

In order to help kick off the event, I’ll start by presenting some tips and tricks that I’ve picked up while working on my own apps, and I’m sure Angela will be doing the same. After that, it’s everyone else! We invite discussions and questions throughout the meetup, as it’s the best way to learn.

Here are the event details:

  • What: Tampa iOS meetup, a new gathering in the area that complements the Suncoast iOS Meetup and Tampa Bay Cocoaheads, both worthwhile gatherings. We want to make sure that if you can’t make one local iOS event, there’ll always be another one in the near future!
  • When: Wednesday, November 18th, from 7:00 p.m. to 9:00 p.m.
  • Where: Energy Sense Finance, located at 3825 Henderson Blvd., Suite 300 (just west of South Dale Mabry)
  • If you have a tip or trick that you’d like to present, let us know! Drop us a line in the comments section at the bottom of our Meetup page or email me at joey@joeydevilla.com. We’ll provide a projector, and if you need one, a Mac to present on.
  • We’ll have provide some snacks and drinks. No idea what they’ll be, but I’ll post details as I found out.
Categories
Florida Swift Kick

Swift programming tip: How to execute a block of code after a specified delay

burning fuse
swift kick

Sometimes, you want some code to execute after a specified delay. For me, this happens often in user interfaces; there are many cases where I want some notification or other interface element to appear and then disappear after a couple of seconds. I used to use an NSTimer to make it happen, but nowadays, I call on a simple method called delay().

Consider the simple “Magic 8-Ball” app design shown below:

magic 8-ball app

The two functional interface items are the Tap me button and a label that displays a random “yes/no/maybe” answer in response to a button tap. The button should be disabled and the answer should remain onscreen for three seconds, after which the app should revert to its initial state, with the button enabled and the answer label blank.

Here’s the action method that responds to the Touch Up Inside event on the “Tap me” button:

@IBAction func buttonClicked(sender: UIButton) { 
  tapMeButton.enabled = false
  predictionLabel.text = randomAnswer()
  delay(3.0) {
    self.tapMeButton.enabled = true
    self.predictionLabel.text = ""
  }
}

Don’t worry too much about the randomAnswer() method; it simply returns a randomly-selected string from an array of possible answers. The really interesting method is delay(), which takes two parameters:

  • A number of seconds that the system should wait before executing a block of code. In this particular case, we want a 3-second delay.
  • The block of code to be executed after the delay. In our block, we want to blank the label and enable the button.

The block of code that we’re passing to delay() is a closure, which means it will be executed outside the current ViewController object, which in turns means that we’ve got to be explicit when capturing variables in the current scope. We can’t just refer to the button and label as tapMeButton and predictionLabel, but by their fully-qualified names, self.tapMeButton and self.predictionLabel.

Here’s the code for delay():

func delay(delay: Double, closure: ()->()) { 
  dispatch_after(
    dispatch_time(
      DISPATCH_TIME_NOW,
      Int64(delay * Double(NSEC_PER_SEC))
    ),
    dispatch_get_main_queue(),
    closure
  )
}

delay() is just a wrapper for dispatch_after(), one of the functions in Grand Central Dispatch, Apple’s library for running concurrent code on multicore processors on iOS and OS X. dispatch_after() takes three parameters:

  • How long the delay should be before executing the block of code should be,
  • the queue on which the block of code should be run, and
  • the block of code to run.

We could’ve simply used dispatch_after(), but it exposes a lot of complexity that we don’t need to deal with. Matt Neuburg, the author of the O’Reilly book iOS 9 Programming Fundamentals with Swift, found that he was using dispatch_after() so often that he wrote delay() as a wrapper to simplify his code. Which would you rather read — this…

dispatch_after(
  dispatch_time(
    DISPATCH_TIME_NOW,
    Int64(3.0 * Double(NSEC_PER_SEC))
  ),
  dispatch_get_main_queue(),
  {
    self.tapMeButton.enabled = true
    self.predictionLabel.text = ""
  }
)

…or this?

delay(3.0) {
  self.tapMeButton.enabled = true
  self.predictionLabel.text = ""
}

Here’s the code for the example “Magic 8-Ball” app, which I’ve put entirely in the view controller for simplicity’s sake:

//
// ViewController.swift
//

import UIKit


class ViewController: UIViewController {

  @IBOutlet weak var tapMeButton: UIButton!
  @IBOutlet weak var predictionLabel: UILabel!
  
  let predictions = [
    "Yes.",
    "Sure thing.",
    "But of course!",
    "I'd bet on it.",
    "AWWW YISSS!",
    "No.",
    "Nuh-uh.",
    "Absolutely not!",
    "I wouldn't bet on it.",
    "HELL NO.",
    "Maybe.",
    "Possibly...",
    "Ask again later.",
    "I can't be certain.",
    "Reply hazy. Try again later."
  ]
  
  override func viewDidLoad() {
    super.viewDidLoad()
    predictionLabel.text = ""
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }

  @IBAction func buttonClicked(sender: UIButton) {
    
    // When the "Tap me" button tapped,
    // we want to:
    // 1. Disable the button
    // 2. Display a random magic 8-ball answer
    // 3. Wait 3 seconds, and then:
    //    a) Enable the button
    //    b) Clear the displayed answer
    
    tapMeButton.enabled = false
    predictionLabel.text = randomAnswer()
    delay(3.0) {
      self.tapMeButton.enabled = true
      self.predictionLabel.text = ""
    }
  }
  
  func randomAnswer() -> String {
    return predictions[randomIntUpToButNotIncluding(predictions.count)]
  }
  
}


// MARK: Utility functions

func delay(delay: Double, closure: ()->()) {
  
  // A handy bit of code created by Matt Neuburg, author of a lot of books including
  // iOS Programming Fundamentals with Swift (O'Reilly 2015).
  // See his reply in Stack Overflow for details:
  // http://stackoverflow.com/questions/24034544/dispatch-after-gcd-in-swift/24318861#24318861
  //
  // The secret sauce is Grand Central Dispatch's (GCD) dispatch_after() function.
  // Ray Wenderlich has a good tutorial on GCD at:
  // http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1
  
  dispatch_after(
    dispatch_time(
      DISPATCH_TIME_NOW,
      Int64(delay * Double(NSEC_PER_SEC))
    ),
    dispatch_get_main_queue(),
    closure
  )
}

func randomIntUpToButNotIncluding(count: Int) -> Int {
  return Int(arc4random_uniform(UInt32(count)))
}

Resources

zip file iconYou can see this code in action by downloading the zipped project files for the demo project, DelayDemo [220K Xcode 7 / Swift 2 project and associated files, zipped].

If you’d like to learn more about coding for concurrency with Grand Central Dispatch, a good starting place is the tutorial on Ray Wenderlich’s site. It’s a two parter; here’s part 1, and here’s part 2.

I found Matt Neuburg’s delay() method in his answer to this Stack Overflow question.

Categories
Swift Kick

My “How to constrain text fields” and “How to work with dates and times in Swift” articles, updated for Swift 2

xcode

Of all the new features that come with the upcoming “2.0” version of Apple’s Swift programming language, there’s an annoying one: it breaks a lot of earlier code. I’ve been going through my more popular Swift articles and updating them for Swift 2, and now they’re ready:

How to program an iOS text field that takes only numeric input or specific characters with a maximum length: One of the most popular tutorials on this blog, this covers a way to constraint text fields so that they accept only numeric input or specific characters, and limits the number of characters they will accept.

How to work with dates and times in Swift, part one: An introduction of Cocoa’s date and time classes, and how they work together. This article covers UTC (Coordinated Universal Time), and the key classes: NSDate, NSCalendar, NSDateComponents.

How to work with dates and times in Swift, part two: Calculations with dates: Now that we’ve got the basics, it’s time to do some date arithmetic: comparing two dates to see which one is the earlier and later one, finding out how far apart two dates are, and adding and subtracting from dates.

How to work with dates and times in Swift, part three: Making date arithmetic more Swift-like: Cocoa’s date and time classes have an Objective-C heritage, which in the Swift context, feel kind of clunky. In this article, I look at ways — and by ways, I mean helper functions and class extensions — to make date calculations feel more like Swift.

How to work with dates and times in Swift, part four: A more Swift-like way to get the time interval between two dates: This quick article shows you how to make an operator overload that makes getting the time interval between two dates more like subtraction.

Categories
Swift Kick

How to dismiss the iOS keyboard when the user taps the background in Swift

tapping iphone

A quick recap

A couple of weeks ago, I posted an article that showed how to program an iOS text field that takes only numeric input or specific characters with a maximum length in Swift.

While that article focused on constraining what the user could enter into a text field, its example app has a couple of UI features that you’ll find in many apps:

  • It dismisses the keyboard when the user taps the Return key, and
  • it dismisses the keyboard when the user taps on the background.

Last week, I covered implementing the first of these two features in the article titled How to dismiss the iOS keyboard when the user taps the “Return” key in Swift. The one major takeaway from that article was that the way to dismiss the iOS keyboard after the user is editing a text field is to make the text field resign its role as first responder.

In that article, we dismissed the keyboard when the user tapped the Return key by doing the following:

  • We made the view controller adopt the UITextFieldDelegate protocol,
  • We made that same view controller the delegate for all text fields contained within its view, and
  • We implemented the delegate method textFieldShouldReturn(textField: UITextField), which is called when the Return key is tapped, and provides us a reference to the text field that was being edited when user tapped it:
// 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;
}

In this article, we’ll look at how to dismiss the keyboard when the user touches the view in the background while editing a text field.

Dismissing the keyboard when you don’t know which text field is the first responder, but know which view the text field is in

The textFieldShouldReturn(textField: UITextField) method can be implemented to resign the first responder for a specific text field because a reference to that specific text field is passed to it. Unfortunately, we don’t get that information when the user taps on the view, and there’s no method or property in UIView that tells us which of the text field contained within it is the first responder.

In the Apress book Beginning iPhone Development with Swift, they take the “brute force” approach and implement a method that looks like this:

@IBAction func userTappedBackground(sender: AnyObject) {
  firstTextField.resignFirstResponder()
  secondTextField.resignFirstResponder()

  // ...more of the same...

  lastTextField.resignFirstResponder()
}

It works, but you need to update the method whenever you add a text field to or remove a text field from the view.

A more flexible approach is to loop through the contents of the view’s subviews property (an array of AnyObject), calling the resignFirstResponder() method for any text fields encountered along the way. I’ve seen code similar to this:

@IBAction func userTappedBackground(sender: AnyObject) {
  for view in self.view.subviews as! [UIView] {
    if let textField = view as? UITextField {
      textField.resignFirstResponder()
    }
  }
}

It works — as long as all the text fields in your view are immediate subviews of the view. If your view hierarchy runs deeper, you’ll have to write more code to deal with subviews or subviews. I’ll leave that as an exercise for the reader.

It turns out that there’s a simple and effective way to dismiss the keyboard for a view without knowing which text field is the first responder: call the view’s endEditing(_:) method. This method scours the view’s subview hierarchy, find the text field that is currently the first responder and asks it to resign that status. If its parameter is set to true, it forgoes the pleasantry of asking the text field to resign as first responder and simply makes it resign. If we use this method, our userTappedBackground method becomes very short and sweet:

@IBAction func userTappedBackground(sender: AnyObject) {
  view.endEditing(true)
}

Now that we have a method that dismisses the keyboard no matter which text field is the first responder, we need to hook it up to an event that gets fired whenever the user taps on the view.

Responding to taps on the view

These next steps will show you how to use Interface Builder to change a view so that it can respond to events, and then connect that view to the userTappedBackground method we just created.

01

1. Go to the storyboard and select the view.

2. Switch to the Identity Inspector. You can do this by either:

  • Clicking on the Identity Inspector icon (shown beside the number 2 in the screenshot above; I think the icon is meant to look like photo ID) in the Inspector Bar at the top of Xcode’s Utilities Area, or
  • Pressing command-option-3 on your keyboard.

3. In the Identity Inspector’s Custom Class section, you’ll find the Class drop-down menu. It indicates the underlying class of any object in the storyboard, and it also lets you change it. If you have the view selected, the Class drop-down menu will display UIView. We’ll change this in a moment, but let’s take a quick detour first.

02

4. With the view still selected, switch to the Connections Inspector. You can do this by either:

  • Clicking on the Connections Inspector icon (indicated by the number 4 in the screenshot above) in the Inspector Bar at the top of Xcode’s Utilities Area, or
  • Pressing command-option-6 on your keyboard.

5. Take a look at the available connections for the view. You can make a couple of different of kinds of connections, but none of them are for events. We need to make event connections available to the view, which we’ll do in the next few steps.

03

6. Switch back to the Identity Inspector.

7. Make sure that the view is still selected.

8. Change the value in the Class drop-down from UIView to UIControl. This changes the view’s underlying class, as you may have guessed, from UIView to UIControl. We’ll see the effect of this change in a moment…

04

9. With the view still selected, switch to the Connections Inspector.

10. Make a note of the available connections for the view. Since the underlying class for the view is now UIControl, it has UIControl‘s connections available to it, which include events.

We want to dismiss the keyboard as soon as the view gets a Touch Down event. We use this event rather than Touch Up Inside (the one we typically use for buttons) because we want to dismiss the keyboard as soon as the user taps the view without first waiting for him/her to release or move his/her finger.

5

11. Drag from the Touch Down event to the View Controller icon above the view.

12. A list of action methods in the view controller (any method prefaced with @IBAction is an action method) will appear. Select userTappedBackground:.

6

13. If you look at the Identity Inspector now, you’ll see that the Touch Down event is now connected to the view controller’s userTappedBackground method, and when you run the app, tapping on the view dismisses the keyboard.

Bonus: Dismissing the keyboard when you don’t even know which view the text field is in

There may come a time when you need to dismiss the keyboard from code that:

  • doesn’t have a reference to the text field that’s currently the first responder and
  • doesn’t even have a reference to the view that contains that text field.

In such a case, use this line of code:

UIApplication.sharedApplication().sendAction(
  "resignFirstResponder", to:nil, from:nil, forEvent:nil)

Here’s what happens in this line:

  • UIApplication.sharedApplication() gets a reference to the instance of the app itself.
  • sendAction(_:to:from:forEvent:) sends an action message to a target object within the app. If the target (the to: parameter) is nil, the message — resignFirstResponder in this case — gets sent to whatever object in the app is currently the first responder.

My guess is that this approach is more computationally heavy that calling a view’s endEditing(_:) method, so use that if you have a reference to the view, and use the approach above when you don’t.

Resources

zip file iconYou can see this code in action by downloading the zipped project files for the demo project, ConstrainedTextFieldDemo [83K Xcode project and associated files, zipped].

Categories
Swift Kick

How to dismiss the iOS keyboard when the user taps the “Return” key in Swift

dismissing ios keyboard with return key

A quick recap

A couple of weeks ago, I posted an article that showed how to program an iOS text field that takes only numeric input or specific characters with a maximum length in Swift.

It covered the Delegate pattern (something you’ll use often in iOS development), introduced the UITextFieldDelegate protocol, and showed you how to use one of its methods, textField(_:shouldChangeCharactersInRange: replacementString:), to intercept the characters that the user inputs into text fields.

zip file iconThe article included the Xcode project files for the featured app, ConstrainedTextFieldDemo. You can download it here [83K Xcode project and associated files, zipped]. When you run it, you’ll the screen pictured above, with text fields constrained so that they’d accept only characters from a specified set, or reject characters from a specified set.

Undocumented feature : Dismissing the keyboard when the user taps the “Return” key

One of the features that was included in the app but discussed only in passing was the dismissal of the keyboard when the user tapped the Return key. While this is convenient for the user, it’s not iOS’ default behavior. You have to watch for the event where the user taps the Return key, and then respond accordingly.

The UITextFieldDelegate protocol’s textFieldShouldReturn(textField: UITextField) method is called whenever the user taps the Return key while editing a text field. It accepts a single parameter, textField, which is a reference to the text field the user was editing when s/he tapped the Return key. It returns a bool — true if the text field should implement its default behavior for the Return key; otherwise, false otherwise. It’s where you should place code to intercept the Return key event.

What should that code be? If you remember only one thing from this article, let it be this:

The way to dismiss the iOS keyboard after the user is editing a text field is to make the text field resign its role as first responder.

What’s the first responder, you ask?

  • The short answer is that the UI object that currently has the first responder role in iOS is roughly analogous to the UI object that currently has the focus in other systems (such as Android, HTML, and .NET).
  • The long answer is that the first responder is the first view in the responder chain to receive many events. Most of the time, it’s the view in a window that an app deems best suited for handling an event. You can find out more by reading these Apple documents: Event Delivery: The Responder Chain and their Responder object documentation.

Take a look at code in the example app in my earlier article, particularly ViewController.swift.

Combining the textFieldShouldReturn(textField: UITextField) method and The One Thing You Should Remember From This Article, we get this method definition:

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

After all that preamble, the method itself seems pretty short and anticlimactic. This method lives in the ViewController class, which has adopted the UITextViewDelegate protocol…

class ViewController: UIViewController, UITextFieldDelegate {

…and in its viewDidLoad() method, it declared itself as the delegate of every text field in the corresponding view:

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

All this setup means that whenever the user edits any of the text fields in the view — whether it’s vowelsOnlyTextFieldnoVowelsTextFielddigitsOnlyTextFieldnumericOnlyTextField, or positiveIntegersOnlyTextField — and then hits the Return key, the textFieldShouldReturn(textField: UITextField) method gets called.

And when the textFieldShouldReturn(textField: UITextField) method, it does just two things:

  • It tells the text field to resign its first responder role (by invoking its resignFirstResponder() method). In this application, we want the keyboard to disappear when the user taps the Return key regardless of the text field, so we don’t bother trying to identify the text field; we simple call textField.resignFirstResponder().
  • Aside from dismissing the keyboard, we want the Return key to function as it normally would, so we have the method return true, which states that text field should implement its default behavior.

Resources

zip file iconOnce again, here are the zipped project files for the demo project, ConstrainedTextFieldDemo [83K Xcode project and associated files, zipped].