Tag Archives: Swift playgrounds

This Old App: 2013 Code, Meet Xcode 8

A few months ago my app Interval RunCalc was removed from the App Store for being too old and not updated. In this series of articles I’ll document what I did on rebuilding and improving the app. In our first article I analyzed the changes necessary. In the second, I built the model in a Swift playground. Now it is time to place that model into the old app, and make a temporary view and controller to test it.

Open the App in Xcode

I made a copy of the original app development files, then opened them in Xcode. The issue navigator started complaining right away:

Clicking on the issue, I find several updates Xcode wanted to make.

I select Perform Changes resolving the issues. I find in the target settings for the project, there’s more issues: provisioning profiles for debug and release.

>

Above the issue, I’ll find a checkbox Automatically Manage Signing. I check that and enable the automatic signing.

I find provisioning one of the most irritating parts of working with Xcode. Anything to get me away from it is a good thing.

Of course that’s not the end of the issues. I get another error message.

If I manage singing automatically, I have to pick the team. I only have me so I pick myself from the drop down. The target is set up, and Xcode is happy again.

Configuring the New Version

This is a major version update for the app. I want to change the version number to version 2.0. Originally I wanted the app to be called runner’s calc, but that name was taken. I settled on the mouthful Interval RunCalc. That’s too long for an icon, I want to change the Display Name to a shorter name, Run Calc to show better on an icon.

The next change I’ll make is the deployment info. Currently, the deployment is iOS 8.0 and only on iPhone in portrait.

Which iOS version to set to is a good question. In most cases, you want to deploy a version or two back for compatibility. Here’s where getting user data comes in handy. I’ll use iTunes Connect’s analytics, and look at the analytics for 2016.

Two active devices per month means there’s almost no one but me using this. Usage data strongly hints me that the biggest user is me. The large peaks occur at races I either ran or was a spectator (marathons are my 2018 goal). While a second user might be at the same races, I find it unlikely.

I’m looking at a sample that’s less than a third of total apps out there. Still, upping the version to 10.3 is not tragic since it looks like nobody but me uses this. I’ll bump it up all the way for the revision. I’m also going to make this a Universal app instead of a iPhone app. My new settings look like this:

Later, I’ll set the iPad and iPhone interface to the same initial storyboard.

The Old Structure

Opening the project navigator, You can see how I put together this app:

You’ll see the root files are the RunnersCalc files. The top level contains the initial view controller and the help controller. There was a help controller, though no one knows how to get to it — I unfortunately hid it too well. If you got to it, it was just written instructions, and very badly written. It’s another thing to go on the list of changes.

The folders under this root level has one folder for the model, and three folders for the view controllers: Intervals, Input controls and Splits.

For those from a completely Swift background, those .h and .m files are Objective-C files. The .h files contain a header which indicates what properties and methods are sharable to the rest of the app. Your code is in the .m files.

Opening up the Model folder and clicking the PCPace.h file, I find the declarations for the model, the properties of the model were:

//properties of the object

@property NSTimeInterval pace; // seconds per mile

@property float speed; // miles per hour

@property float distance; //miles

@property NSTimeInterval time; //seconds of the run or split

@property NSTimeInterval splitPace; //seconds per mile or km

@property int splitCount; //total number of splits

@property int splitNumber; //current split -- necessary?

@property int fullSplitCount; //number of splits for the full mile/kilometer

@property NSTimeInterval elapsedTime; //elapsed time in a split

@property float totalDistance; //totalDistance to this point

enum PCVariable {

  kcPCSpeed,kcPCDistance,kcPCPace,kcPCTime

};

@property enum PCVariable finding;


Add the Playground Code to the App

I’m not too worried about any of this code, since I’m about to replace it all. I open up the RunStats playground so I can copy my code. I have the code stored in four files:

In the App project, I’ll make four new files in the model folder. I’ll select the Model Folder first to keep the group correct as I add files. I’ll hit Command-N to make a new file. There’s several ways I can make these files, since I’m cutting and pasting everything into the file. I’ll select Swift File for the template, though I could have just as easily picked a Cocoa Touch Class subclassing NSObject.

I’ll be asked to save with a file name. I’ll call this one RunStats.

On the bottom, I’ll make sure the target is correct and the Group is Model

We click Create, and get a new Message:

I usually create the bridging header. As the message says the bridging header opens up the classes, methods and properties in the Objective-C code to the the newer Swift code if you want to use it. In this application I won’t use it. However, it is not a lot of overhead. I’m planning to scrap all the Objective-C, but I never know in an intermediate step if I’ll need it, so I’ll create it anyway.

I’ll repeat this three more times for the classes DistanceStat, PaceStat and TimeStat. I have two classes in the RunStats playground page. I’ll add one more for RunStatsIntervals, so every class has its own file.

I’ll organize this a little better. I’ll select my Objective-C files PCPace.h, PCPace.m, and the bridging header. I’ll right click and make New Group From Selection, naming the group Objective-C. I’ll do the same for the Swift code, selecting it all and making a Swift group for it.

I’ll select RunStats, and see this:

//

// RunStats.swift

// RunnersCalc

//

// Created by Steven Lipton on 4/6/17.

// Copyright © 2017 Steven Lipton. All rights reserved.

//



import Foundation


I’ll delete the import foundation, and then copy and paste my RunStats class from the playground to the App, leaving the intervals class and the test data in the playground. I’ll make sure I copy the import UIKit when I do. It will of course have lots of errors for the moment. We have the three classes it refers to yet to add. I’ll do the same copy and paste to the other four files, adding the classes and import UIKit in each case.

Once I’m done with this there are seven issues to solve. When transferring a playground, you’ll probably get these errors.

If you click on the first error, it will show you this in the editor:

The time.seconds() and the distance.meters() lines Xcode wants to assign to something. I used them to get a quicklook for debugging the playground. All the errors are like this. The simple solution is delete that code. The fatal error is the same thing. it’s on the line

 self.pace

It was to get a quicklook for the object in the playground. I deleted it to clear the error.

A New View and Controller

The new model is in the app, and I’d like to test it. Right now, this code is ignored by the app. What I’ll do is set up a new storyboard to check the model. In the Storyboards group, I have a blank iPad storyboard and the Main_iPhone.storyboard file, which has the current storyboard for the app:

This is a big mess. This is one of those times when doing an update project like this you just shake your head and wonder what you were thinking back then. With a little investigation, you’ll find some of those answers. I iterated this model with a inadequate model to start, and I needed a lot of UI to cover up my mistakes. The major reason this app never had an iPad version was I’d have to replicate this storyboard on an iPad.

The new app had a completly new Storyboard. I’m using auto layout to need one storyboard for all devices. I Press Command-N, selecting a Storyboard object and name it Main, making sure my Group is Storyboards.

The storyboard I get is completely blank. I’ll drag a view controller onto the storyboard

I’ll change the attribute to give this a name and set this view controller to Is Initial view controller.

I’ll need a ViewController file for this scene. I’ll press Command-N again, click a Cocoa Touch Class for the template and make the Class RootViewController, subclassing UIViewController and Swift as the language.

When I save the file I’ll save it to the RunnersCalc root folder, where the root and help view controllers are. Going back to the main storyboard, I’ll set the Custom Class to RootViewController. Now I’m ready to add some controls.

This first layout is only for testing purposes. No auto layout just yet, just drag and drop. I’ll add a label for a result and a button to calculate the result. I’ll use text fields for input, so I’ll need room for the keyboard.  I’ll set the button at the center of the iPhone screen, giving the keyboard I hope plenty of room, and the result at the top. I’ll make the label read Result and the button Calculate

I Drop two text fields between the label and the button, which I’ll call variable 1 and 2. That will be for decimal values. For time values I’ll add three textfields under those two text fields. The layout looks like this, using placeholder text for titles.

Next, I’ll wire up my view controller. I’ll open the assistant editor. All but the button are outlets, So I’ll do those first, control dragging into the view controller.

@IBOutlet weak var result: UILabel!

  @IBOutlet weak var variable1: UITextField!

  @IBOutlet weak var variable2: UITextField!

  @IBOutlet weak var hours: UITextField!

  @IBOutlet weak var minutes: UITextField!

  @IBOutlet weak var seconds: UITextField!

I’m keeping this very generic for a reason. This is a test bed. I’ll use these same fields to check different parts of the model. I’ll calculate the values in code in the calculate button, which I now wire up as an action.

@IBAction func calculate(_ sender: UIButton) { 

  }

There’s a function I’ll need in the view controller. It will take a TextField and convert the text in it to a Double. I can close up the storyboard and assistant editor, and then add the function to RootViewController

  func double(textField:UITextField!)->Double{

    guard let textField = textField 

       else {return 0.0} //uwrap textfield

    guard let value = Double(textField.text!) 

       else {return 0.0} //find double

    return value

  }

This will give me a value of 0 for anything that isn’t a true Double, and return a Double if it is. There’s three optionals, and I deal with all the unwrapping here giving me a value or a zero. This is not something I’ll need in the finished code. The input system (which we cover next time) will restrict the user to only valid values.

With the structure of the test bed done, I’ll add the model to the code.

 let runStats = RunStats()

I’ll test the function the IBAction calculate. I’ll start with a simple test to make sure everything works, converting kilometers to miles.

@IBAction func calculate(_ sender: UIButton) {

    let distance = double(textField: variable1)

    runStats.distance.distance(kilometers: distance)

    let resultText = String(format: "%02.2d", runStats.distance.miles())

    result.text = resultText

  }


I use the double function to turn the variable1 text field into a double, then add it to the model with a kilometer measure. I change the distance to miles in a string, placing the result in result.

I’m almost ready to run this code, but there’s one more thing I need to do: Change the initial storyboard. In the General tab of the Target Properties, you’ll find this:

When run, the app goes to the old Main_iPHone storyboard, not the new Main. I also restricted the layout to portrait in the old version, and I’ll use all orientations for the new version. I set both the iPad and iPhone to the same storyboard Main:

Setting my simulator to an iPhone 7 I run the app. Two things happen. I get 44 warnings, but the app runs. I try putting in a value. I find I didn’t leave enough room for the keyboard

I’ll stop the app and move the calculate button up. I try running again. I’ll type in 5 when the calculator appears, and get rewarded with a value of…Zero instead of 3.11.

I first check to see if the model is working right with a few print statements

@IBAction func calculate(_ sender: UIButton) {

    let distance = double(textField: variable1)

    print("distance in km \(distance)")

    runStats.distance.distance(kilometers: distance)

    print("distance in km \(runStats.distance.kilometers())")

    print("distance in km \(runStats.distance.miles())")

    let resultText = String(format: "%02.2d", runStats.distance.miles())

    result.text = resultText

  }

When run, I get output on the console:

distance in km 5.0

distance in km 5.0

distance in km 3.10685596118667

The model is working fine. My bug is a silly one of setting the format wrong in the string. I change the format to this:

let resultText = String(format: "%6.2f", runStats.distance.miles())

When I run again, the test works.

I go back to the storyboard. That alphanumeric keyboard is annoying. For all the TextFields, I set the keyboard type to decimal pad.

I’m ready to try another test. Let’s take distance in miles and a speed in miles per hour and calculate time. I’ll convert distance and speed to Doubles, then set them to the runstats model. I’ll try this code.

@IBAction func calculate(_ sender: UIButton) {

    let distance = double(textField: variable1)

    let speed = double(textField: variable2)

    runStats.distance.distance(miles: distance)

    let pace = PaceStat(milesPerHour: speed)

    runStats.time(changedBy: pace)

    result.text = runStats.time.hoursMinutesSeconds()

  }  

I try a simple case where I run five miles at five miles an hour, And yes, I get the expected one hour.

I try a function with a time variable using the three textfields on the bottom. I’ll compute the pace in minutes per mile for a given distance.

@IBAction func calculate(_ sender: UIButton) {

    runStats.distance = DistanceStat(miles: double(textField: variable1))

    let seconds = Int(double(textField: self.seconds))

    let minutes = Int(double(textField: self.minutes))

    let hours = Int(double(textField: self.hours))

    let time = TimeStat(hours: hours, minutes: minutes, seconds: seconds)

    runStats.pace(changedBy: time)

    result.text = runStats.pace.minutesSecondsPerMile()

  }

I build and run this. A simple test is a one hour run of five miles, which should give us a twelve minute mile.

And my target time for my first marathon of five hours 45 minutes ( yeah, I’m a bit slow)

As expected, I get the 13:10 pace. I’d do a lot of tests like this to make sure the components work with the model.

A Model Change

During the testing, I get the feel for using the code in the model. There’s one thing I don’t like. Consider these two statements

runStats.distance.distance(miles: 5.0)

print(runStats.distance.miles())

I use different identifiers for the setter and getter. I like this better:

runStats.distance.miles(5.0)

print(runStats.distance.miles())

It is easer to read and shorter to type. It is not a huge change in the model code, and it works better. I can change the setters to this:

//set distance from miles

  public func meters(_ meters:Double){

    distanceStat = meters

  }

  public func miles(_ miles:Double){

    distanceStat = miles * 1609.344

  }

  public func kilometers(_ kilometers:Double){

    distanceStat = kilometers * 1000.0

  }


I change the resulting code using these functions in the rest of the model. I’ll do the same for the other stat types. The current code only requires changes to the stat types DistanceStat, PaceStat and TimeStat. Pace and time add one wrinkle in two and three parameter setters. The first parameter becomes the name of the setter, and the rest are parameters like this:

public func hours(_ hours:Int,minutes:Int,seconds:Int) {

    timeStat = Double(seconds + minutes * 60 + hours * 3600)

  }

I didn’t do it intially, but some uses of these functions in initializers will need to specify self to work properly.

public init(minutes:Int,seconds:Int){

    self.hours(0, minutes: minutes, seconds: seconds)

  }

Running the code as written I get the same results, and no problems.

The 44 Warnings 

There’s one last thing to address: those 44 warnings. Closing the folders they are in, I find they fall into the following categories:

Those warnings are why Apple wants me to change this code, and threw me out of the app store to do it. This is all old stuff that needs updating. Sometimes living in the Swift world, I forget the Objective-C is improving over time too. The deprecations for example are all the same issue: there’s no more UIActionSheets in iOS.  Many of the others are number conversions which changed in newer versions of iOS in Objective-C

I have choices here: I can fix this code and remove the errors, I can ignore the errors for now, or I can delete the code now. While the errors are annoying, they are not affecting the code. I go for the last one. I will delete all of the Objective-C code before shipping, replacing it with the new Swift code. It doesn’t need to be there, but I use it as documentation for the old version until I don’t need it anymore.

So I got the new model into the old code. I started replacing components, and got the old code to actually function in the latest Xcode. My next steps are to get the views and controllers working properly. I”ll start that process not at the root, but by replacing those text views with UIPickers in the next installment.

I’m a LinkedIn Learning author, with five courses up in the LinkedInLearning library.  My latest is a trilogy for iOS notifications: Learning iOS NotificationsiOS App Development: Notifications, and watchOS App Development: Notifications If you a re interested in Notifications, I’d Check them out. 

Parsing Strings from Time and Fractions to Doubles

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.

2016-12-12_07-33-06  2016-12-12_07-35-25     2016-12-12_07-34-30

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.

Converting a String to Double.

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.

Converting Minutes and Seconds to TimeInterval

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.

A Flexible TimeInverval Converter

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.

Converting Fractions.

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

One more bug

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.

Adding to Last Week’s Project

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:
2016-12-20_08-03-05

For a time you get this:
2016-12-20_08-02-48

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

2016-12-20_08-07-58

But it does work in this case.
2016-12-20_08-09-11

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.

2016-12-20_08-14-41

 

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.

The Whole Code

 

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")