Training and Instructional Design

This week’s lesson covers a topic the I left off on last week’s lesson, but can be used outside that context. I thus wanted to cover it separately. Last week I showed you how to use a `UIPickerView`

to input numbers with a lot less validation. We looked at doubles, fractions and time intervals.

You can go here to read the article on how I set that up. I left the article with one deficiency: the values returned from the picker view are strings, not numbers. In this lesson, I’ll show you how to convert the string to the much more useful `Double`

and its alias `TimeInerval`

. Since that’s something you can use outside the application from last week, I’m presenting it separately. Those who want this in the picker view should be able to cut and paste these functions into the code from last week.

For this Lesson I’m going to use a swift playground. You can use one in Xcode or on your iPad by downloading this file and loading it into playgrounds: . I’ve heavily commented that file to explain what’s going on here.

The easiest of the conversion cases will also be the building block of all the rest: converting a string to a double. In a new playground, add this:

let numberString = "3.1415926" if let number = Double(numberString){ print(number) }

The constructor for type `Double`

has a converter built-in for converting strings to doubles. All you need is `Double("3.14")`

to convert the string to a number – almost. `Double`

returns an optional value of `Double`

if you convert a string, where `nil`

is an invalid string for conversion. for **3.l4l59@6** instead of **3.1415926**. returns `nil`

. Before you use the value, you’ll need to unwrap it. For these conversions, I use either `if let`

or `guard`

to do that, so I can return `nil`

if there is any reason the string is invalid for conversion. Here, I ignore any `nil`

case, but that will change shortly.

`TimeInterval`

is a commonly used type, which is really an alias for `Double`

. `TimeInterval`

is different from the other time type `Date`

you’ll often see in cod . `TimeInterval`

is a measure of seconds, independent of a starting and ending time. `Date`

is a time based on a reference date. Both have their uses. Apple has the `DateFormatter`

classes and its `dateFromString`

method for handling dates, so I’m not concerned with those as much as `TimeInterval`

. Unlike `Date`

which has a lot of localization issues, almost everyone expresses the measures involved in time intervals constantly to hh:mm:ss.ss. The only variations are leaving off the hours or adding days. That means writing a straightforward function is relatively easy. I’ll start even easier and convert a string of minutes and seconds only. Make a new function in the playground of this:

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

We’ll assume that the string `timeString`

looks like** mm:ss.ss**. If that’s the case, then there’s a very handy string function `components(separatedBy:)`

change the function to this:

func minutesSecondsInterval(_ timeString:String)->TimeInterval!{ let time = timeString.components(separatedBy: ":") }

The `components(separatedBy:)`

function creates a string array separated by a colon. if `timeString`

was **“12:34.56” ** `time`

becomes **[“12″,”34.56”]**. I can take the components of that array and after checking for a non-`nil`

value, add them together to find the number of seconds.

func minutesSecondsInterval(_ timeString:String)->TimeInterval!{ var time = timeString.components(separatedBy: ":") if let seconds = Double(time[1]){ if let minutes = Double(time[0]){ return (seconds + (minutes * 60.0)) } } return nil }

If minutes and seconds are true values, I’ll add the seconds to minutes multiplied to 60 seconds. If either are `nil`

, I drop out of the `if`

and return `nil`

.

That’s okay, but not great. If I want hours, I’d need to write a new function. A string with more than one colon gives wrong results. If I put** 1:10:12.5**, the `time`

array would be **[“1″,”10″,”12.5”]**, adding 1 minute time 60 for 60 seconds and 10 seconds together for 70 seconds, which is wrong. This should be a lot more robust.

Think this out. Where hours, minutes and seconds appear in the array changes depending on the string. Reverse the array elements though, and if they exist, the time component is always in the same position in the array. 10:12.15 is [“12.15”,”10] reversed and 1:10:12.15 is [“12.15″,”10″,”1”] reversed. Seconds is always index 0, minutes index 1, hours index 2, if it exists. I multiply the number of seconds in a minute (60) and the number of seconds in an hour (3600) to the component before I add it to the result. If I have a matching array of those component multipliers, I could do that and put the whole thing in a loop that loops only the length of the array. If I have two components only do minute and seconds, If I have three, do all three. If I find five, return nil, because that’s an error. That all becomes this function:

func timeInterval(_ timeString:String)->TimeInterval!{ let timeMultipilers = [1.0,60.0,3600.0] //seconds for unit var time = 0.0 var timeComponents = timeString.components(separatedBy: ":") timeComponents.reverse() if timeComponents.count > timeMultipilers.count{return nil} for index in 0..<timeComponents.count{ guard let timeComponent = Double(timeComponents[index]) else { return nil} time += timeComponent * timeMultipilers[index] } return time }

I have a constant array `timeMultipliers`

with the multiplier value for the component. This could be expanded to days, if I add another element of 86400.00, but I rarely need that for time intervals. I initialize a value `time`

where I’ll add the components together. I break apart the `timeString`

argument into an array then reverse it with the` reverse()`

method of `Array`

. I check the array if there are more components than I have multipliers for. If there is, it’s an invalid string, and I return `nil`

.

There’s a loop from 0 to the last value in the `timeComponents`

array. I use `guard`

to convert the element in `timeComponents`

to a `Double`

, making sure the value is non-`nil`

. If `nil`

, I return `nil`

. If not `nil`

, I multiply by the multiplier, and add that result to `time`

. When the loop is over, I return the `time`

.

This will work with a value in seconds, seconds and minutes, and hours, seconds, and minutes, returning `nil`

for any invalid answer.

In the picker view, I made input for fractions. Fractions have three components: a whole number, a numerator and a denominator. The double value is the whole number added to the numerator divided by the denominator. In the picker view, I picked a format of **w n/d**, so thirty-three and a third is a string **33 1/3**. This has two separators, a space and a slash instead of the single separator of the time interval. The String method you’ve used so far uses a single character. It also can use a character set. Add this function to your code:

func double(fractionString:String)->Double!{ let separators = CharacterSet(charactersIn: " /") let components = fractionString.components(separatedBy: separators) }

Before breaking the string apart to an array, you make a list of separators as a `CharacterSet`

, in our case a space and a slash. This breaks the array into three components` ["w","n","d"]`

. So the string **“33 1/3**” becomes` ["33',"1","3"]`

. This never has a change of format, so I can directly use these values, and assume there are only three components, so check for a `count`

of 3 in the array for validity. Get the components, then do the math to get the double.

func double(fractionString:String)->Double!{ let seperators = CharacterSet(charactersIn: " /") let components = fractionString.components(separatedBy: seperators) if components.count == 3{ if let wholeNumber = Double(components[0]){ if let numerator = Double(components[1]){ if let denominator = Double(components[2]){ return wholeNumber + (numerator/denominator) } } } } return nil //failure case }

Try this out and you’ll get some doubles

However, there’s a problem. Try this one:

double(fractionString: "12 0/5")

You *should* get 12.0 back. You get `nil`

instead.

In cases where we don’t have three components, this doesn’t work. If I had two or one component, I’d like to return just the whole number and ignore whatever is wrong with the fraction. The `if let`

optional chaining presents a problem though. All my calls are local, and make it hard to return just the whole number. This is the beauty of `guard`

. I’ll change this code to use `guard`

, check for the proper number of components and act accordingly.

Make a new function like the first but chage the parameter to `(fraction fractionString:String)`

so we can use it in the playground without duplication complaints from the compiler.

func double(fraction fractionString:String)->Double!{ let separators = CharacterSet(charactersIn: " /") let components = fractionString.components(separatedBy: separators) }

I’m breaking this into two steps instead of one. I’ll check for components to be in the range of 1 to 3. of it isn’t we have an invalid string and will return `nil`

. I’ll use `guard`

to get a constant `number`

. However since this is within the `if`

clause it’s local, so if successful, I’ll assign to a variable `wholeNumber`

the value of `number`

var wholeNumber:Double = 0.0 if components.count <= 3 && components.count > 0 { guard let number = Double(components[0]) else{ return nil //invalid whole number } wholeNumber = number } else { return nil // wrong number of components }

Anything that survives that first `if`

clause is a valid whole number and there are 1, 2, or 3 elements in the array. If I have 3 elements, as I did in the previous example, I have a mixed fraction, and can find the value of the numerator and denominator once again using `guard`

, return the whole number if the value is invalid. Then I can return the value of the fraction, like I did in the last example.

if components.count == 3{ guard let numerator = Double(components[1]) else {return wholeNumber} guard let denominator = Double(components[2]) else {return wholeNumber} if denominator != 0{ return wholeNumber + (numerator/denominator) } else {return wholeNumber} //division by zero will result in zero for the fraction } return wholeNumber

You’ll notice my other paranoid thing I did. I prevented division by zero, returning the whole number if `denominator`

is zero. I also return `wholeNumber`

if I have only one or two components.

Test this out:

double(fraction: "33 1/3") double(fraction: "33 0/3") double(fraction: "33 1/0") double(fraction: "33")

You get an extra added feature. Since the code converts everything to `Double`

, this works:

double(fraction: "33.1234")

And so does this.

double(fraction: "33.1234 1.1/1.1")

Since it doesn’t harm anything and might be useful in a few places where I might be converting just decimals in one string and fractions in another, I’m leaving this the way it is.

The rest of this is for those working through last weeks lesson. If you didn’t, you can skip this. If you worked through last week’s post and are wondering how to use this in that code, copy the `double(fraction fractionString:String)`

and `timeInterval(_ timeString:String)`

into the `ViewController`

class of that project:

func double(fraction fractionString:String)->Double!{ let separators = CharacterSet(charactersIn: " /") let components = fractionString.components(separatedBy: separators) print (components) var wholeNumber:Double = 0.0 if components.count <= 3 && components.count > 0 { guard let number = Double(components[0]) else{ return nil //invalid whole number } wholeNumber = number } else { return nil // wrong number of components } if components.count == 3{ guard let numerator = Double(components[1]) else {return wholeNumber} guard let denominator = Double(components[2]) else {return wholeNumber} if denominator != 0{ return wholeNumber + (numerator/denominator) } else {return wholeNumber} //division by zero will result in zero } return wholeNumber } func timeInterval(_ timeString:String)->TimeInterval!{ let timeMultipiler = [1.0,60.0,3600.0] //seconds for unit var time = 0.0 var timeComponents = timeString.components(separatedBy: ":") if timeComponents.count > timeMultipiler.count{ return nil } timeComponents.reverse() for index in 0..<timeComponents.count{ guard let timeComponent = Double(timeComponents[index]) else { return nil} time += timeComponent * timeMultipiler[index] } return time }

In the `pickerView:didSelectRow:`

delegate, change the display to the label to this:

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { resultString = "" for index in 0..<components.count{ let digit = components[index][pickerView.selectedRow(inComponent: index)] if digit.characters.count > 1 {//add space if more than one character resultString += " " //add space if more than one character } resultString += digit } //--- New Code for displaying doubles ---- // display results as a string and as a double var value:Double! = 0.0 if segmentedControl.selectedSegmentIndex == 2{ value = timeInterval(resultString) //time }else{ value = double(fraction: resultString) //fraction or decimal } displayLabel.text = "\(resultString) is \(value)" }

I got sneaky here. I only needed two conversion functions and not three because of that extra added feature of `double(fraction:)`

. Time calls the `timeInterval`

function, everything else will call the `double(fraction:)`

function. Instead of unwrapping the value I used string interpolation to present the value. The resulting string will tell me this is an optional value. For a real app you’ll be doing some more unwrapping of course.

Build and run. For a decimal value you get this:

Unfortunately, for a string of 3 for the fraction you get this

But it does work in this case.

This is a problem with the picker. In the `numberPickerComponent`

function the first element of the `x`

case, `"0"`

is a simple number character with no delimiter, and the string does not get broken:

case "x": return ["0","1/16","1/8","3/16", "1/4","5/16","3/8","7/16", "1/2","9/16","5/8","11/16", "3/4","13/16","7/8","15/16"]

Changing `"0"`

to `" 0"`

by adding a space in front is a cheap and easy way to solve that problem.

case x: return [" 0","1/16","1/8","3/16", "1/4","5/16","3/8","7/16", "1/2","9/16","5/8","11/16", "3/4","13/16","7/8","15/16"]

Build and run. Now it works.

The process for converting any string into a double is the same. Find the characters that separate components. divide the string, check that the components are valid numbers, then add them to for your final result. As I’ve shown here, that might be different for the format you are using, but the general principles are the same.

Here’s the week’s lesson as a Swift playground, formatted for the iPad playgrounds. Copy and paste into a playground on Xcode or iPad Playgrounds. You can download and unzip the file here as well: numberstringparser-playground

import UIKit //: A playground for converting Strings to Doubles (TimeInterval) /*: #Case 1: A String that looks like number Double converts to an optional, where `nil` is the unconvertable value. */ let numberString = "3.1415926" if let number = Double(numberString){ print(number) } /*: # Case 2: A String that looks like a *mm:ss.ss* time - Break into components, using `components(separatedBy:)` - Unwrap each component, and add together - If anything goes wrong, return `nil` */ func minutesSecondsInterval(_ timeString:String)->TimeInterval!{ var time = timeString.components(separatedBy: ":") if let seconds = Double(time[1]){ if let minutes = Double(time[0]){ return (seconds + (minutes * 60.0)) } } return nil } //: Try this out minutesSecondsInterval("10:13.6") /*: # Case 3: A Flexible TimeInterval converter. - This has a constant array `timeMultiplier` holding a mutiplier for the number of seconds for the component - The function reverses the array with the `reverse()` method so components are alwys in the same position. - The function uses a loop to access the correct component and multiply by `timeMultiplier` before adding together. */ func timeInterval(_ timeString:String)->TimeInterval!{ let timeMultipiler = [1.0,60.0,3600.0] //seconds for unit var time = 0.0 var timeComponents = timeString.components(separatedBy: ":") if timeComponents.count > timeMultipiler.count{ return nil } timeComponents.reverse() for index in 0..<timeComponents.count{ guard let timeComponent = Double(timeComponents[index]) else { return nil} time += timeComponent * timeMultipiler[index] } return time } //: Try it out: timeInterval("1:10:13.6") /*: Case 4: Fractions using a slash. - Fractions are strings like **33 1/3** or **w n/h** - Formula is `wholeNumber + (numerator/denominator)` - There are two separators, a space and a slash. Use the `components(separatedBy: separators)` function for a character set, creating a CharaterSet of the separators by `CharacterSet(charactersIn:)` */ func double(fractionString:String)->Double!{ let separators = CharacterSet(charactersIn: "_/") let components = fractionString.components(separatedBy: separators) if components.count == 3{ if let wholeNumber = Double(components[0]){ if let numerator = Double(components[1]){ if let denominator = Double(components[2]){ return wholeNumber + (numerator/denominator) } else {return wholeNumber}//no or incomplete fraction } else {return wholeNumber} //no or incomplete fraction. } } return nil //failure case } //: Try it out: double(fractionString: "12 0/0") /*: # Case 5: A better fraction converter - Deals with the bug of a fraction of zero case 3 doesn't. - Returns the whole number part if numerator or denominator invalid value. Nil for invalid whole number. - All values are doubles so *22.5 10.2/2.5* will return a correct decimal value of 26.58 */ func double(fraction fractionString:String)->Double!{ let separators = CharacterSet(charactersIn: " /") let components = fractionString.components(separatedBy: separators) var wholeNumber:Double = 0.0 if components.count <= 3 && components.count > 0 { guard let number = Double(components[0]) else{ return nil //invalid whole number } wholeNumber = number } else { return nil // wrong number of components } if components.count == 3{ guard let numerator = Double(components[1]) else {return wholeNumber} guard let denominator = Double(components[2]) else {return wholeNumber} if denominator != 0{ return wholeNumber + (numerator/denominator) } else {return wholeNumber} //division by zero will result in zero } return wholeNumber } double(fraction: "33 1/3") double(fraction: "33 0/3") double(fraction: "33 1/0") double(fraction: "33") double(fraction: "33.1234") double(fraction: "33.1234 1.1/1.1")

%d bloggers like this:

Pingback: Happy holidays, Parse a String to a Double. – A Slice of App Pie

Thank you very much, that will work for me.

One question though.

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

has _ timeString:String

func double(fractionString:String)->Double!{

does not have the _ before fractionString:String

To call the function requires (fractionString ” xx xx/xx”)

If I add the _ before fractionString:String

To call the function only takes (xx xx/xx) like timeInterval

Are you just showing that functions may be done either way?

It’s also context.

`timeInterval`

is clearly something that you’ll put a string in, and doesn’t have one in its own class.`Double`

already has a`Double(_ string:String)-> Double!`

as a factory method. For documentation and prevent confusion,`double(fractionString:string) -> Double!`

tells the developer and anyone reading the code specifically what gets converted to Double.Going back to your 12/12/16 Post

DATA ENTRY WITH UIPICKERVIEW

I changed the first index in the “x” array from “0” to “0/16” or “”

Without a space between the whole number and the fraction it looks like a larger number.

Since the displayLabel starts out at 000

Picking 23 and leaving the fraction at 0 the displayLabel looks like 230

even though the segmented selector says 99x

0/16 inserts a space and shows 23 0/16

“” leaves the third digit out of displayLabel which looks good.

23

23 1/16, etc

BUT

In todays post if a fraction is not entered it returns nil.

If 0/16 is there it works, even if it is kinda ugly

Example

double(fractionString:”24 “) returns nil

double(fractionString:”24 0″) returns nil

double(fractionString:”24 0/16″) returns 24

Also the first number (wholeNumber) can be a decimal

double(fractionString:”23.5678 2/3”) returns 24.23446666666667

In the playground.

A decimal number could not be entered in the picker.

Not sure what you are saying here. I didn’t implement a picker in the playground.

A decimal whole number can be tried in a playground when there is not a picker involved.

This works on any string, yes. that’s the whole point. If you are talking about what I think your are talking about, check the updated post for clarification.

Yes there’s a bug, thought the case of doubles will remain.

Here’s an entirely revamped version that does solve the bug.

Thank you again. I thought the solution would be in the components .count but the couple of things I tried did not work. This is really helping me understand functions better. I told someone before that I was getting dangerous because I was gaining knowledge without understanding. I can write functions, or modify other people’s functions, but I don’t fully understand what I am doing. I am getting more understanding as I get more experience. Practice makes perfect. But your solutions show me what to do without all my trashing in the dark. It is easier to learn the correct way instead of trying a lot of ways that do not work.

Especially when it is very close to exactly what I want to do.

>

Well if there’s learning happening, I’m happy to help.