Make App Pie

Training for Developers and Artists

This Old App: Models in the Playgrounds

Back a few months ago, an  app of mine  was removed from the App Store for being too old and not updated enough. In this series of articles I’ll document what I did on rebuilding and improving the app. In our first installment I analyzed the changes necessary. In this second installment,  I’ll start making the model.

I think in Model-View-Controller or MVC. If you are not familiar with MVC, I suggest you take a look at my video in LinkedInLearning on MVC and delegates. In short, a common programming practice is to break an application into three parts: the model, the View and the controller. The model is the code that processes data. The view is the user interface.  We don’t let the view and model interact with each other. Instead we use a controller to coordinate between the two.  This way we can change one while affecting the other two minimally.

While I might get some ideas for the view in my planning stages, I tend to start  coding with the model. In one sense the controller and view are window dressing for the model, where your data is. Views and controllers are the presentation and interaction with that model.

I’ll make a confession here: I hate working on the model. It the most tedious type of programming in my mind. It has none of the glamor or action of views or controllers.  It is just data, and I’m not much of a data person. As such I’ll give you a warning that this is a bit dry, but there’s a lot there to look at, and in the case of  RunCalc, some interesting problems that drove me up the wall the first time I wrote this code.

What’s in the Old Model

Experience in mobile applications has changed me, and the model in the original code was a reminder of this.  Written in Objective-C, The model for version 1.0 was a case of putting all the eggs in one basket.  I did everything in one model.  The original model had four properties to calculate running statistics:

  • Distance
  •  Time
  •  Pace
  • Speed

I added speed to handle treadmill settings, though it is measuring the same thing as pace: How fast the runner is going.

There are four core  methods which derive pace, speed, time and distance.  Basically you can derive each like this:

distance = pace * time

time = distance / pace

pace = time / distance

speed = distance / time

My measurements were seconds for time measurements, which I would format as strings in HH:MM:SS format for display. Distance was in miles. It was easy for me to calculate test cases in miles. I had conversion methods to metric measurements for miles and kilometers.  This meant that I had pace seconds per mile and speed as mile per second, which would have some more conversion to compute it to minutes per mile and miles per hour and kilometers per hour. Metric pace was in kilometers per seconds for some unknown reason that isn’t in my original notes. I think it was laziness.

This was the basics of the model, but there were a few more features that got added to it, The first and most important was the “locking” mechanism. There is a subtle problem with using the formulas above. Suppose you change pace. What do you update, distance or time? If you update both, then your numbers are wrong because things get updated on top of each other, and you can’t change all your settings. For changes in the calculator, what you need is to lock either distance or time in place and only compute one of them when you change pace. I did this with an enumeration in the early version to keep track of what I locked.

The last part of the model was running totals for the splits. It meant I had another set of four properties with totals for distance and time. There were two more for average pace and speed.

Building the new model : Properties and Units

The original had a lot of rounding errors and strange calculations due to switching between kilometers and Imperial measurements. Unlike other measurements systems, there will be a lot of switching between units, particularly in distance, and I really didn’t think this out well the first time. Long distance running uses different distance measurements at the drop of a hat. There are 5K, 8K, 10K, and 15k races. But there are half marathons at 13.1 Miles and full marathons at 26.2 miles. This and other measurements gave me the following measurements:

  • Distance
    • Meters
    • Kilometers
    • Miles
  • Time
    • Seconds
    • Minutes:Seconds
    • Hours:Minutes:Seconds
  • Pace/Speed
    • Meters per second
    • Minutes per mile
    • Minutes per Kilometer
    • Kilometers per hour
    • Miles per hour

Instead of computing in the units I have displayed, I kept with two standard units, meters and seconds, and converted everything else from them. These units match units found in other API’s such as CoreMotion, if I ever decide to interface this with other apps or API’s.

Pace and speed are different ways of describing the same thing, so I made them the same with one measure: Meters per Second.  Everything else  is a conversion.

Derivation Functions and Locking

The derivation function problem the first time took me a month to come up with the enumeration solution. For some reason I could nt wrap my head around the initial problem of handling that many variables.  Version 2.0 will use a different solution the model. There are six methods that change the model, two for each of the three properties. Each one changed one of the two other properties For example for distance:

func distance(changedBy pace:Double) {
    self.distance = distance(pace: pace, time:self.time)
}
func distance(changedBy time:Double) {
    let newDistance = distance(pace: self.pace, time:time )
    self.distance = newDistance
}

The Model and Conversions

I built the model as described into two models. One, RunStats for the model described above and one RunStatsIntervals for a collection of RunsStats objects and calculations for finding summations of the collection data. It was here I ran into my problem: The summations would need their own conversions, which were a repeat of the conversions above.

Here’s where object-oriented programming gets its biggest strength: I made up three classes PaceStat, TimeStat, and DistanceStat, that had the conversions. IN stead of using Doubles in my RunStats class I used these three classes. I went one step more and made constructors that would work in any unit I wanted, making the result the correct unit.  For example here’s distance:

public class DistanceStat{
// Basic unit is meters
// you can use meters, kilometers or miles for this stat
private var distanceStat:Double = 0.0 //private variable so you must use the setters and getters.
//Constructors
public init (){
    self.distanceStat = 0.0
}

public init(meters:Double){
   self.distanceStat = meters
}

public init(kilometers:Double){
    distance(kilometers:Double)
}

public init(miles:Double){
    distance(miles:miles)
}

// getters distance
public func miles()->Double{
    return distanceStat / 1609.34 
}

public func meters()->Double{
    return distanceStat
}

public func kilometers()->Double{
    return distanceStat / 1000.0
}

//Setters
public func distance(meters:Double){
    distanceStat = meters
}

public func distance(miles:Double){
    distanceStat = miles * 1609.34
}

public func distance(kilometers:Double){
    distanceStat = kilometers * 1000.0
}

}

Now to specify a 8K distance, I can say

let newDistance = DistanceStat(kilometers:8.0)

and return that as miles.

print( newDistance.miles())

I do the same with pace and time. With the pace, time, and distance stat in their own classes, I can use them anywhere and get the conversions I need anywhere.  With that I can make summation functions.  I’ll total the distance and the time, and use them to get an average pace for my summation data. That summation data can be in any of the units I have for that type.

I built the model not in the app, but in a separate playground. Playgrounds, especially for models offer an excellent way of looking at all your code and making the debugging process easier.  I can type them in on either my Mac or my iPad and check how every part works, finding errors as I go.  I did for example find an error in my  conversion formulas, and was able to find and fix it from the playground.

In the next installment we’ll add the model to the old Xcode project, get more than a few errors and start hooking up some user interfaces for testing.

8 responses to “This Old App: Models in the Playgrounds”

  1. […] 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 […]

  2. Just wondering if there was a conscious choice to use functions rather than computed properties for returning miles/km/m?
    (or will this become obvious later?!)

    1. There was. I’m only storing in meters in the model. The view controllers using this model may ask for any unit at any time. Using a function makes the coding a lot easier down the line when I’m switching between units and keeps the numbers a bit more accurate since they are only conversion functions from a base number. In the original I ran into rounding errors because I didn’t keep a single base number.

      1. I’ll add one more bit to that. I want to be clear they are conversion functions. Functions do that as documentation better than computed properties.

  3. I get the last bit, but you could still use public computed variables called miles, kilometres and meters that computes off of master distanceStat base number. Agree it’s 6 of one and half a dozen of the other whether you use a computed variable of ‘miles’ or a function of ‘miles()’ as they are both effectively functions that expose distanceStat in a clear, precise, consumable manner.
    Down to individual preference and perception of clarity at the end of the day I suppose.

    1. On reflection, there something habitual that I do I didn’t even think about: It’s a small thing here, but I use computed properties only when I need the values to always be current, or do something when the value changes. Three computed properties mean three calculations every change of distance compared to only one conversion calculation when I need it. On something small like this, it probably won’t affect performance much, but keeping to that old-school habit as part of my style will make a difference in other applications.

  4. I might be wrong, but I’m pretty sure (and it would be the sensible way to compile it) that the computed values are only calculated when the ‘getter’ is ‘got’, which is no different to calling your distance(meters/miles/kilometres:) methods when you want the value.

    We’re probably heading off into of semantics now, rather than the original purpose of the post :)

  5. […] but to journal that process as I did it. Over this summer, I’ve gone from a few sketches to a new data model to upgrading Xcode 6 code to Xcode 8, a working app with picker views. I finished debugging and […]

Leave a reply to This Old App: Update to Xcode 9 – Making App Pie Cancel reply

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