Make App Pie

Training for Developers and Artists

Swift Swift: Using Dates and the UIDatePicker in Swift

When you set an appointment in the calendar app there is a control used for time measurements called the UIDatePicker. While not one of the most popular controls, it is very handy for working with dates. In this lesson we’ll find out how easy it is to work with. We also will cover some of the important date-related methods and classes you should know when you works with dates and times.

Set Up the Delivery Time app

As a demonstration, we’ll create a delivery time estimator. Create a new project named SwiftDatePickerDemo from the single view template. On the storyboard set up like this:
Screenshot 2014-09-21 17.09.39

Open the assistant editor. From the UIDatePicker, Top label, and upper segmented control, add outlets to your view controller:

//MARK: Outlets and properties
    @IBOutlet weak var myDatePicker: UIDatePicker!
    @IBOutlet weak var deliveryTimeLabel: UILabel!
    @IBOutlet weak var deliveryDelayPreset: UISegmentedControl!

From the date picker and the both segmented controls, add actions.

//MARK: - Target Actions
    @IBAction func myDateView(sender: UIDatePicker) {
    }
    @IBAction func deliveryDelayPreset(sender: UISegmentedControl) {
    }
    @IBAction func deliveryDate(sender: AnyObject) {
    }
 

Configure the Date Picker

Close the assistant editor and go to the viewcontroller.swift file. UIDatePicker has a few properties you can set to control the behavior of the picker. In a delivery application for example, we don’t want to deliver into the past so the first date and time that shows up is the current date and time. The picker will default to the current date and time, but we can set it. We can also set a minimum time on the picker. We will set if the date will show on the picker. For a simple delivery app where we will deliver only the same day, we really don’t need the extra wheels for the date.

Change viewDidLoad to the following:

//MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        myDatePicker.datePickerMode = UIDatePickerMode.Time // 4- use time only
        let currentDate = NSDate()  //5 -  get the current date
        myDatePicker.minimumDate = currentDate  //6- set the current date/time as a minimum
        myDatePicker.date = currentDate //7 - defaults to current time but shows how to use it.
    }

Line 4 uses the datePickerMode property to set the mode to .Time We then get a new instance of a NSDate, which is the current time. We set that time in the minimumDate property and the date property.

Build and Run. While it doesn’t do much yet, you will find you cannot go to a time before the current time.
Screenshot 2014-09-21 18.28.55

Displaying a Date with NSDateFormatter

When we set the date property, we did so as a NSDate Class. What we would like to do is display the time on the label, but for that we need a String. Apparently one of the best kept secrets (AKA poorly documeted) in Xcode is how to do this. There is a special class for this, NSDateFormatter, which is not quite intuitive to use. To get a string of the date, you make a instance of NSDateFormatter,then set the properties for the instance.  You use the instance method stringFromDate to return a string from a date. The reason for all this is localization. NSDateFormatter uses the region settings to display the date properly for that user’s experience.

Above the life cycle MARK, add the following:

//MARK: - Instance Methods
func printDate(date:NSDate){
        let dateFormatter = NSDateFormatter()//3

        var theDateFormat = NSDateFormatterStyle.ShortStyle //5
        let theTimeFormat = NSDateFormatterStyle.ShortStyle//6

        dateFormatter.dateStyle = theDateFormat//8
        dateFormatter.timeStyle = theTimeFormat//9

        deliveryTimeLabel.text = "Delivered at: " + dateFormatter.stringFromDate(date)//11
    }

Line 3 creates a date formatter. Both date and time are in a date, so we need to set how we want to show the date and time. our choices are NoStyle, ShortStyle, MediumStyle and LongStyle. In line 5 and 6 we set this for the time and date. In line 11, we use the stringFromDate method to return a string and directly send it to the label.
There is a shorter way to do this from a class method of NSDateFormatter, but I don’t find it very useful.

let deliveryTime = NSDateFormatter.localizedStringFromDate(date, dateStyle: .ShortStyle, timeStyle: .ShortStyle)
        deliveryTImeLabel.text = "Delivered at: " + deliveryTime

Generally if there is one date on something, there is more than one date or time. Consider this scene from Interval RunCalc, an App I wrote for calculating pace and distance given running intervals.
Photo Sep 21, 6 15 05 PM
There are four times here: two in each table cell, one in the header and one in the footer. This is the common case, and having a date formatter around actually speeds up the process, since you set the style only once and then use the same stringfromDate for all the occurrences.

Now that we can get a date into the label, we need to get the date. Change the myDateView method to:

@IBAction func myDateView(sender: UIDatePicker) {
        printDate(sender.date)
    }

We already set the date property. Here we get the date selected, and call the printDate method to display it on the label.

Build and run, and now we can see the time in the label.

Calculating with Time: Adding a Delay

Of course no one delivers immediately, and the idea of this app is to calculate what time the delivery will be ready from that delay. We have a segmented control with the standard delay times in them. We will add those to the time and display the time plus the delay.

To do that we will need another property. Add this to the properties and outlets:

 var delay:NSTimeInterval = 0.0

Add the following to the instance methods:

func delayTime() -> NSDate{
let pickerTime = myDatePicker.date
return pickerTime.dateByAddingTimeInterval(delay)//3
}

Line three has NSDate‘s method for taking an NSTimeInterval and adding it to an NSDate we get from the picker’s date property. NSTimeIntervals are really of type Double, containing a time interval in number of seconds.

The times on the labels are not numbers, they are strings. We need to get them into type Double to do anything with them.

@IBAction func deliveryDelayPreset(sender: UISegmentedControl) {
            let index = sender.selectedSegmentIndex  //2
            let delayString:NSString = sender.titleForSegmentAtIndex(index)!  //3
            delay = delayString.doubleValue * 60 //4  convert minutes to seconds
            printDate(delayTime()) //5
    }

In lines 2 and 3 we get the title string back from the segment. In line 4 we use the doubleValue method to convert the String to a Double, them multiply by 60 to get seconds. Line 5 prints the date again, this time with the delay.

We will need to change the mydateView method as well so it reflects the delay:

@IBAction func myDateView(sender: UIDatePicker) {
        printDate(delayTime())
    }

Time and Date with NSDateFormatter

When we making deliveries today, we don’t need the date — it’s today’s date. Let’s use the other segmented control to set a boolean variable that will then change the formatting.

Add this to the outlets and properties:

var today = true

Change deliveryDate to this:

@IBAction func deliveryDate(sender: UISegmentedControl) {
        let index = sender.selectedSegmentIndex
        today = index == 0
        if today {
            myDatePicker.datePickerMode = .Time
        }
        else{
            myDatePicker.datePickerMode = .DateAndTime
        }
        printDate(delayTime())
    }

We set a flag today and toggle the picker mode to show and hide the date. In the printDate function change this:

var theDateFormat = NSDateFormatterStyle.NoStyle

to this:

var theDateFormat = NSDateFormatterStyle.NoStyle
        if !today {
            theDateFormat = .ShortStyle
        }

Build and run. Switch the segment from today to future.

Screenshot 2014-09-22 06.11.49

One More Bug

The is still a subtle bug in the application. If you run the app for a long time, you can send pizzas back in time. Run the app, and wait a few minutes. You find you can dial back the date picker to the original initialization time. If you use an minimum time of the current time, you will need to update it frequently. I did it this way:

func delayTime() -> NSDate{
        myDatePicker.minimumDate = NSDate()
        let pickerTime = myDatePicker.date
        return pickerTime.dateByAddingTimeInterval(delay)
    }

Since the date calculation occurs here, By setting the minimum date in the calculation, we prevent and catch all backwards dates, and reset them to the current date in one statement.

The Whole Code

//
//  ViewController.swift
//  SwiftDatePickerDemo
//
//  Created by Steven Lipton on 9/21/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    //MARK: Outlets and properties
    @IBOutlet weak var myDatePicker: UIDatePicker!
    @IBOutlet weak var deliveryTimeLabel: UILabel!
    @IBOutlet weak var deliveryDelayPreset: UISegmentedControl!
    var delay:NSTimeInterval = 0.0
    var today = true

    //MARK: - Target Actions
    @IBAction func myDateView(sender: UIDatePicker) {
        printDate(delayTime())
    }
    @IBAction func deliveryDelayPreset(sender: UISegmentedControl) {
            let index = sender.selectedSegmentIndex
            let delayString:NSString = sender.titleForSegmentAtIndex(index)!
            delay = delayString.doubleValue * 60 //convert minutes to seconds
            printDate(delayTime())
    }
    @IBAction func deliveryDate(sender: UISegmentedControl) {
        let index = sender.selectedSegmentIndex
        today = index == 0
        if today {
            myDatePicker.datePickerMode = .Time
        }
        else{
            myDatePicker.datePickerMode = .DateAndTime
        }
        printDate(delayTime())
    }

    //MARK: - Instance Methods
    func delayTime() -> NSDate{
        myDatePicker.minimumDate = NSDate()
        let pickerTime = myDatePicker.date
        return pickerTime.dateByAddingTimeInterval(delay)
    }

    func printDate(date:NSDate){
        let dateFormatter = NSDateFormatter()

        var theDateFormat = NSDateFormatterStyle.NoStyle
        if !today {
            theDateFormat = .ShortStyle
        }
        let theTimeFormat = NSDateFormatterStyle.ShortStyle

        dateFormatter.dateStyle = theDateFormat
        dateFormatter.timeStyle = theTimeFormat

        deliveryTimeLabel.text = "Delivered at: " + dateFormatter.stringFromDate(date)
    }

    //MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        myDatePicker.datePickerMode = UIDatePickerMode.Time
        let currentDate = NSDate()
        myDatePicker.minimumDate = currentDate
        myDatePicker.date = currentDate //defaults to this but..
    }

}

17 responses to “Swift Swift: Using Dates and the UIDatePicker in Swift”

  1. Just an attempt at a comment.

    Steve Lipton

    The web site for tutorials on Swift, iOS and the Raspberry Pi: Making App Pie Apps now available in the iTunes App Store: http://itunes.com/apps/ *stevenlipton*

  2. Couldn’t find a command printDate(delayTime()) ? Do I have to import smth. before?

    1. Look at lines 41-61 in the Whole Code. Those are both instance methods discussed in the lesson. The method delayTime returns a NSDate where we add the current time to our delay. The method printDate uses NSFormatter to format the date nicely.

      1. Yeah, now everything is clear. Thanks Steven!

  3. Hi , can you please explain today = index == 0

    1. Let’s put it in context.
      We have this as a property

      var today = true

      Which is a Boolean flag we use to say we are delivering today or not. Then we have these three lines:

      @IBAction func deliveryDate(sender: UISegmentedControl) {
              let index = sender.selectedSegmentIndex
              today = index == 0
      

      We are in the action for changing the segmented control. We get the index from the control in the second line, and check if it is zero in the thrid. If so its today and today will be true. You might make this long form like this

      if index == 0 {
          today = true 
      }
      

      But since today is Boolean, and index == 0 returns a Boolean, you can just assign the value of the index == 0 like this

      today = index == 0
      

      Make sense?

      1. Thank you Steven Lipton, it is clear to me now.

      2. No worries. It looks wierd if you haven’t done a lot with boolean values.

  4. Hi thank you for the wonderful lesson,i am new to programming and recently started learning “Swift language” through Make App Pie. After this tutorial i wanted to test my knowledge by making an app like “Alarm Clock”. Initially i thought that i can assign datepicker.date to notification date but soon i realize that datepicker is not giving the exact value. I had no idea how to do it but after a good amount of research on the web i got some understanding about how to implement. I started making the app but now i stuck with a problem. I created a date picker to set the alarm and i used NSDateFormeter function to extract the string from the date. and i made an array with that string and used dateFromComponents to make a date. When i try to print them, the array giving correct numbers but after making date with it the date is coming out different. Please help me to solve this. Thank you very much for help.
    This is my code link.


    import UIKit
    import Foundation
    class ViewController: UIViewController {
    var myArray = [String]()
    @IBOutlet weak var mydatePicker: UIDatePicker!
    override func viewDidLoad() {
    super.viewDidLoad()
    }
    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
    }
    @IBAction func mydatepickerAction(sender: AnyObject) {
    var daFormet = NSDateFormatter()
    //For ex: 2015:01:17:20:05
    daFormet.dateFormat = "YYY:MM:dd:HH:mm"
    //This makes datestring from Datepickersdate
    var dateString:String = daFormet.stringFromDate(mydatePicker.date)
    //Makes an array from the date String
    myArray = dateString.componentsSeparatedByString(":")
    }
    @IBAction func myButton(sender: AnyObject) {
    println(myArray)
    //Creates a date from components
    var newMyDate:NSDate = dateFromComponents(myArray[2] .toInt()!, month: myArray[1] .toInt()!, year: myArray[0] .toInt()!, hour: myArray[3] .toInt()!, minute: myArray[0] .toInt()!, second: 00)
    println(newMyDate)
    var date = NSDate()
    println(date)
    }
    func dateFromComponents(day:Int, month:Int, year:Int, hour:Int, minute:Int, second:Int) -> NSDate {
    var cal = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
    cal.timeZone = NSTimeZone.localTimeZone()
    var comps = NSDateComponents()
    comps.day = day
    comps.year = year
    comps.second = second
    comps.hour = hour
    comps.minute = minute
    comps.month = month
    return cal.dateFromComponents(comps)!
    }
    }

    view raw

    mytest.swift

    hosted with ❤ by GitHub

    1. I am i little overwhelmed right now, so i am afraid i cannot help at this time. I approved your comment and if anyonereding this can help you they certainly are welcome to. My apologies.

  5. […] Swift Swift: Using Dates and the UIDatePicker in Swift […]

  6. […] Swift Swift: Using Dates and the UIDatePicker in Swift […]

  7. […] style time with a default locale. If you are not familiar with date formatters, you might want to check out my post on them. We’re ready to build and run again. Move the sliders a bit and we get […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: