Time Picker Part 2: Strings to Time Intervals

In the last tip,  I showed you how to use UIPickerControllers to make a time interval input. What I didn’t show you is how to output that. In this tip I’ll show you how to read and convert data from a picker that has a separate delegate. 

If you download the exercise file, you’ll have a copy of the project as I left it last week. If you go to TimeComponents, you can see the first problem: this is a delegate of a picker. It does not communicate directly with a view controller. I’m going to set a delegate for this that has two parameters: the time in a string and a time interval. 

protocol TimeComponentsDelegate{
    func didSelect(timeString:String,seconds:TimeInterval)
}

Then assign the delegate to TimeComponents

var delegate:TimeComponentsDelegate! = nil

In pickerView( didSelectRow:), I’ll add this to update the values whenever we change a component.

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
       delegate.didSelect(timeString: resultString,seconds: timeInterval(resultString))

    }

How do I get the String and the TimeInterval? It is easier to get the string first and convert it to seconds. I’m going to make a property resultString

Every selection,  I’ll clear it. 

resultString = ""

Then loop through the components

for index in 0..<componentMax.count{
}

Picker views have a method selectedRow(inComponent) I’ll grab the selected digit in each component as I loop through 

let digit = pickerView.selectedRow(inComponent: index)

If the component is a separator, I’ll add the separator to the string. Otherwise, I’ll add the digit.

if componentMax[index] == 0 {
  resultString += separator
} else {
  resultString += String(format:"%i",digit)
}

That gets me a string.  I’ll use another method for converting the string to a timeInterval. I will separate out the components by the separator, then add the value of the hours minutes and seconds together. 

Under the didSelectRow, add this 

func timeInterval(_ timeString:String)-> TimeInterval!{
        
    }

I’ll add an array of multipliers in seconds for hours (3600) minutes(60) and seconds.  I’ll also initialize time to 0.0

let multipliers = [3600.0,60.0,1.0]
var time = 0.0

I’ll split the string into an array using the separator to split it up. 

var timeComponents = timeString.components(separatedBy: separator)

I’ll check to make sure the components array is the same length as the multipliers.  

if timeComponents.count > multipliers.count {return nil}

.If so, I’ll loop through the components. I’ll use a guard while casting them to TimeInterval That will also check for a valid value before multiplying by the multiplier.  If invalid I’ll return nil.

for index in 0..<timeComponents.count{
            guard let timeComponent = TimeInterval(timeComponents[index]) else {return nil}
            time += timeComponent * multipliers[index]
        }

I’ll return the TimeInterval after the loop. 

return time

With all that in place. I’ll set up the delegates in the view controllers.  I’ll get a message about the required method. Use the Fix there to load the required method. I’ll set it up to display timeString

class ViewController: UIViewController,TimeComponentsDelegate {
    func didSelect(timeString: String,seconds:TimeInterval ) {
        self.title = timeString
    }

Don’t forget to point the delegate correctly. 

timeComponents.delegate = self

Then I’ll do the same to the SecondViewController, this time displaying the time interval:

class SecondViewController: UIViewController,TimeComponentsDelegate {

func didSelect(timeString: String, seconds: TimeInterval) {
title = String(format: "%6.0f seconds", seconds)
}

Note I’m living dangerously here, and not worrying about having nil values. Don’t forget to point the delegate correctly. 

timeComponents.delegate = self

Build and run. On the first view controller your time shows up on top. 

Click the button, and try 1:01:01 on the second, and you get  3661 seconds. 

Using the techniques from this and the last tip, you can make a powerful picker for selecting times. 

The Whole Code

Here you’ll find the code for this project. You can also download it from GitHub here:

TimeComponents.swift

//
//  TimeComponenets.swift
//  ProtocolPickerDemo
//
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


import UIKit
protocol TimeComponentsDelegate{
    func didSelect(timeString:String,seconds:TimeInterval)
}
class TimeComponents: NSObject,UIPickerViewDataSource, UIPickerViewDelegate {
    let componentMax = [9,9,0,5,9,0,5,9] // 0 = separator
    var separator = ":"
    var delegate:TimeComponentsDelegate! = nil
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return componentMax.count
    }
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return componentMax[component] + 1
    }
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if componentMax[component] == 0{
            return separator
        }
        return String(format:"%i",row)
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        var resultsString = ""
        for index in 0..<componentMax.count{
            let digit = pickerView.selectedRow(inComponent: index)
            if componentMax[index] == 0 {
                resultsString += separator
            } else {
                resultsString += String(format:"%i",digit)
            }
        }
        
        
        delegate.didSelect(timeString: resultsString, seconds: timeInterval(resultsString))
        }
    func timeInterval(_ timeString:String)-> TimeInterval!{
        let multipliers = [3600.0,60.0,1.0]
        var time = 0.0
        var timeComponents = timeString.components(separatedBy: separator)
        if timeComponents.count > multipliers.count {return nil}
        for index in 0..<timeComponents.count{
            guard let timeComponent = TimeInterval(timeComponents[index]) else {return nil}
            time += timeComponent * multipliers[index]
        }
        return time
    }


}

ViewController.swift

//
//  ViewController.swift
//  ProtocolPickerDemo
//
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


import UIKit


class ViewController: UIViewController, TimeComponentsDelegate {
    func didSelect(timeString: String, seconds: TimeInterval) {
        self.title = timeString
    }
    
    
    
    @IBOutlet weak var picker: UIPickerView!
   let timeComponents = TimeComponents()
    override func viewDidLoad() {
        super.viewDidLoad()
       picker.delegate = timeComponents
        picker.dataSource = timeComponents
        timeComponents.delegate = self
    }




}



SecondViewController.swift

//
//  SecondViewController.swift
//  ProtocolPickerDemo
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//


import UIKit


class SecondViewController: UIViewController, TimeComponentsDelegate{
    func didSelect(timeString: String, seconds: TimeInterval) {
        self.title = String(format:"%6.0f seconds",seconds)
    }
    


    @IBOutlet weak var picker: UIPickerView!
    
    let timeComponents = TimeComponents()
    override func viewDidLoad() {
        super.viewDidLoad()
        picker.delegate = timeComponents
        picker.dataSource = timeComponents
        timeComponents.delegate = self
    }
    


}

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 )

Google photo

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

Twitter picture

You are commenting using your Twitter 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: