All posts by Steven Lipton

Trainer, App developer. Author. Artist. Proprietor of makeapppie.com and Host of Slice of App Pie Show

Advanced iOS App Development: Core Motion

My latest course for Lynda.com/LinkedIn Learning. I’ll take you through the code to write for the iPhone and iPad gyroscope, accelerometer, and magnetometer. Along the way we’ll use some API’s to make a pedometer and a fencing game.  Like the new  feature in iOS 11, I’ll also show you how to stop an app when the app detects someone is running or driving on a car.

If you have a lynda.com or Linked In premium subscription, check it out.

Advanced iOS App Development: Core Motion

 

5 Takeaways From the WWDC Keynote You Won’t Hear From the Press

For the last two years I keep saying the same thing about the Keynote: This is for the consumers and press, not the developers that signed an NDA. Much of what was said was hardly earth shattering or exciting for developers. Far more hardware that I would have expected, much of it shipping now. There were a few sneak peeks at new gadgets coming down the pike. Of all of them the HomePod, Apple’s smart speaker entry will garner the most press, since the press like to see everything as a competition, particularly with Amazon and Google.

There were many tidbits that sounded good, such as Amazon video on Apple TV and the adaptation of the iPad playgrounds keyboard for all apps. There were five things I thought about during the keynote that I think are the big takeaways, though they may not make the news as much. I also had some thoughts about the one last thing everyone will be talking about.

The Missing Apple TV

Under the “Coming later this year” phrase, all news except about Amazon Video was quietly swept under the rug for AppleTV. The last major Apple TV updates happen late August in their own event, which might be what is happening this time. The lack of news is a bit disturbing, especially when the only news was that an app that exists on the iPad now will be on Apple TV by winter. The lack of news gets my suspicion there’s some major hardware and OS work going on. I’d still like to see tvOS assimilate into iOS and be a low-end desktop iPad.

iPad is looking more like a Mac

With the inclusion of the dock on the iPad you get by swiping up from the bottom of the screen, the dragging and dropping of an app to make a new window for that app on top of another app, and the drag and drop of data between apps, the iPad is looking more and more like a Mac. Then there’s the newest feature: A true file system where you can access files from iCloud and from several third party cloud storage solutions such as Google Drive, Dropbox, and Box. This all blurs the line between tablet and laptop even more.

Markup on the iPad

Paradoxically, at the same time the iPad is looking more like an laptop, it also is looking more like a pad of paper. A new feature is Markup, which takes the tools already existing in the notes app in iOS10 and gives them even more life. You can get to notes directly from the lock screen by tapping with an Apple pencil. You can use the pen, pencil marker and eraser to draw or to write freehand text. In one of the more interesting machine learning aspects of iOS 11, the iPad can read your handwriting, and even search for words you hand wrote. Playing around with a beta version last night, my first words scribbled out on a notes pages were converted into a title for my note. Screenshots can be annotated by clicking a thumbnail of the sketch. Other text and windows can be converted to pdf and annotated there. You can even scan documents into notes, with distortion control and marking up the scan. This might seem like a small feature, but it brings some of the most powerful aspects of a piece of paper to the iPad. It gets the ideas out of people and onto a printed media – one easily and instantly shared by several people.

Virtual reality and augmented reality

If there is a theme at WWDC, it seems to be turning Apple devices across platform into virtual reality and augmented reality devices. This seemed to get a lot of the non-hardware time, with multiple demos and explanations. Apple did not however introduce any VR hardware, and left a lot of the heavy lifting to other, more standard engines. In my mind if you haven’t started to learn Unity or Unreal, it’s a good time to do so, since they seem to be the backbone of this effort, not an internal Apple product.

The Metal theme: Learning, not intelligence.

One of the frameworks mentioned frequently in and out of its usual context is Metal. Metal was a framework originally meant to run on a low level the Graphics Processing unit(GPU), A special CPU for rendering 3d graphics quickly without burdening the CPU. The GPU is a parallel processing chip, and as such can be used for other computations at high speeds the CPU could never manage. Apple has moved from using Metal as just a 3D rendering system to running the GPU as a second processor for fast computations. Metal is found in the VR and AR, and updating many of macOS’s older interfaces to a speedier, cleaner version.

Yet Metal is the bedrock for the answer to one of the biggest criticisms of Apple lately: Artificial Intelligence. Apple’s changed the focus here a bit, but gave developers the tools necessary to bring iOS and macOS intelligence by observing and learning, then making predictions from that learning. Apple is calling this Machine Learning, not Artificial Intelligence. There’s a difference here: Artificial Intelligence covers any case where something not human acts like a human. Machine learning is more specific. It is giving a machine data to come up with its own conclusions and predictions based on that data. Apple is banking on the more subtle world than a tube sitting on your desk or counter (more on this later) and talking to it, but instead making hundreds of interactions with your device a learning experience for your device. Ironically, while Apple will be blocking everyone else from getting a user’s personal data in Safari in intelligent ad blockers, they will be absorbing a lot more personal information for their devices to learn about their user and his or her preferences and habits. Machine learning is readily available to all developers, using Core ML and associated Frameworks like Vision which can do many forms of image detection easily and in only a few lines of code. The idea is not to just have a tube that answers your question, but that every app have some idea of what you want before you want it.

HomePod is a Smart Speaker, not a Smart Assistant

The one thing the press will criticize and get totally wrong is HomePod. The talking tubes that some claim are Apple’s competitors miss the point. Apple is not competing with them, and the WWDC keynote was making that abundantly clear for anyone who listens as well as Core ML. Comparing the HomePod to Alexia is like comparing Duck L’orange to candy bars. That’s not not the competition Apple is targeting: Bang and Olufsen is. The HomePod is not a intelligent assistant, it’s an intelligent wireless speaker. All the AI stuff was worth thirty seconds and one slide during a ten minute presentation on a beautifully sounding speaker. It’s not important to Apple now. No one should ignore Apple is a bunch of obsessive audiophiles. If they invented or popularized the smart phone with the iPhone, they are doing the same with the smart speaker as the HomePod. This is first and foremost a speaker, yes it is one to talk to, but the sound coming out has to have distortion levels so low as to make a grown man weep* at a mind shattering volume. It’s a speaker that figures out the acoustics of the room it is in, and then uses its speaker stack to make the best sound possible. The intelligent assistant doesn’t interest Apple as much, because all those Core ML Apps running on one’s phone, iPad and Mac will eventually do that for them.

For the last two years, I’ve come away from the Keynote a little disappointed, although I’m then blown away by the State of the platforms which follows it. The WWDC keynote is the stuff for public consumption, and has become the second string press conference for the bigger hardware announcements in late August or early September. Apple has several themes it follows which makes up its overall strategy. The first is to never reinvent the wheel. Apple will adapt from itself technology and will add technology from others to make the product that delights their consumers. The second is to give the absolutely best tools for 3rd party developers to rapidly develop fantastic products and get many first time developers working in their ecosystem. It is this second point that makes up the backbone of WWDC. Others have looked for more consumer oriented points in WWDC, but that’s not the point. The biggest cheers yesterday was about refactoring, not HomePod, iMac Pro, or Augmented Reality. Apple’s given the consumers and their distractions, now the rest of the week is for the developers.

Steve Lipton is the mind behind makeapppie.com for learning iOS programming. He is the author of several books on iOS programming and development. He is also a LinkedIn Learning author. His latest is a trilogy for iOS notifications: Learning iOS NotificationsiOS App Development: Notifications, and watchOS App Development: Notifications plus a quick lesson on delegation Swift Delegations and Data Sources

*Yes a quote from Douglas Adams. As long as HomePods they aren’t yellow and hang in the air the way a brick doesn’t I won’t get nervous.

This Old App: Container Views and Table Cell Actions

A few months ago, Apple removed my app Interval RunCalc 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 my last installment I added a custom table view cell. In this installment, I’ll get the root view and the table view sending models to each other. I’ll also also get the buttons in the container view to communicate with the root view.

I’ve gotten the cells working, but only when the model is completely internal to the table view controller. I want to send data from the table to the three root variables

The table view is in a container view. How do I send data to something in a container view?

Container views are subviews that act like child view controllers. You use a special segue to do this:

This is an embed segue. You use it a like any other segue. I’ll set an identifier for the segue

I’ll add a prepare:forSegue: to that

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    if segue.identifier == "IntervalTable"{

      let vc = segue.destination as! IntervalTableViewController

      vc.runStats = self.runStats


    }
}

I’ll remove the setting of the runStats in the table view, and set the number of rows back to 1 for testing.

If I change the pace on top to 11:00, the bottom doesn’t change however.

The segue is static, only launching once on load. Instead, the tableview should update when changes to the root happen. I’ll add a private variable to the class to act as a pointer to the table:

private var tableViewController = IntervalTableViewController()

Then change the prepare:forSegue to use the controller:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    if segue.identifier == "IntervalTable"{

      tableViewController = segue.destination as! IntervalTableViewController

      tableViewController.runStats = self.runStats

     

    }

  }

When I update the display for the root, I’ll also refresh the table:

//update the table

    tableViewController.tableView.reloadData()

Now I try 11:00, and both stats update.

I can’t check the other way yet, because the buttons don’t work.

Adding Actions to the Tableview Cell

Ideally we should be able to copy and paste the code from the Actions from the root controller into the tableview cell. Unfortunately, it’s not that easy. There’s three issues that prevent us from doing that.

The first is getting the picker using the storyboard identifier. Secondly the colors(pickerVC: from:sender) doesn’t exist in the cell. Finally, presenting the cell doesn’t work here. The table view cell is not a subclass of TableViewController, nor is it inheriting the Colors method. The colors method is the low hanging fruit here, and I’ll fix that first. Currently it is this.

 func colors(pickerVC:PickerViewController, from button:UIButton){

    if let backgroundColor = view.backgroundColor{

      pickerVC.backgroundColor = backgroundColor

    }

    if let toolBarColor = button.superview?.backgroundColor{

      pickerVC.toolBarColor = toolBarColor

    }

    if let titleColor = button.titleColor(for: .normal){

      pickerVC.textColor = titleColor

    }

  }


I make this part of the picker with a few changes. Instead of pickerVC as a parameter, I can use a view and a button.

func colors(view:UIView, button:UIButton){

    if let backgroundColor = view.backgroundColor{

      self.view.backgroundColor = backgroundColor

    }

    if let toolBarColor = button.superview?.backgroundColor{

      self.toolBarColor = toolBarColor

    }

    if let titleColor = button.titleColor(for: .normal){

      self.textColor = titleColor

    }

  }


I’ll comment out code for the moment and make a change to the timeEntry in the root to see if this works.

//colors(pickerVC: vc, from: sender)

      vc.colors(view: view, button: sender)

And it does:

I’ll change everything to this. It makes better sense to me that the picker controls it own colors than the view controller now that I think of it.

Actions in a Custom Tableview Cell

There’s a school of thought I didn’t follow up to now, but I see the logic at exactly this point: The cell has contents and outlets, but the actions are in the table view controller. Apple forces you to do this in WatchOS for example. The cell is too limited to do everything it needs to do. There’s a price for this: I need to know which cell I’m working with. I’ll start with one cell then deal with the tracking problem second.

I’ll move the actions from the cell the the tableview like this. I’ll cut the actions in the storyboard from the tableview cell, then copy my actions to the table view controller. I’ll clean the whole thing with Command-Shift-K , the reconnect to the table view controller’s copy of those actions. All the errors I had disappear, but there is a new one: the picker delegate. I’ll adopt the PickerViewControllerDelegate, and copy the pickerDidFinish method from RootViewController as a delegate method for the table view. I’ll change the updateDisplay method to tableView.reloadData.

I’ll now test if the buttons work. I open up the app and click the 26.2 distance. I get a picker. The colors are working correctly, since I have a white background for the table view, and a blue numbers for the button.

Changing the picker to 13.10 I get on the table view

Though nothing updates but the button. I can do that easily, after I implement the button strategy I came up in the last installment. When I was thinking about intervals, you can only lock distance or time. Pace is never locked for an interval. For intervals I only need a toggle. Instead of the code I had before that checks the lock if you can change a value, everything will be allowed to change and when you hit either distance or time, the other gets the lock. For time this look like this:

@IBAction func timeEntry(_ sender: UIButton) {

    runStats.locked = .distance

 …

}

Distance gets the same treatment:

@IBAction func distanceEntry(_ sender: UIButton) {

    runStats.locked = .time

…

}

In the tableViewController’s pickerDidFinish, I can recalculate before I reload the data.

runStats.recalculate()

tableView.reloadData()

I’ll run, and just like before, I’ll change the distance to a half marathon of 13.1

The time drops to half the original time. If I change the miles back to 26.2, then change the time to 2:11, I get the same result. If I change the pace to 13:30, then I get this:

The time is the last thing locked, so the distance changes. Change the distance to 13.10, and the time recalculates.

I’ll use a user interface arrangement to makes this a little simpler. If we always add pace first, then this display will make more sense. By placing Pace first we encourage inputting the pace then either distance or time. I do some quick changing of the layout.

This is a lot better. A behavioral change is far more elegant than a code change.

The Container View Delegate 

With the cell and table working, Now I want to get that value back to the root. For that I’ll use a delegate. In the IntervalTableViewController.swift file, I add the protocol

protocol IntervalTableViewControllerDelegate {

  func didChangeStats(stat:RunStats, controller:IntervalTableViewController)

}

I add the property to the IntervalViewController

var delegate:IntervalTableViewControllerDelegate! = nil

I’m getting so many things to update, I’ll make an updateDisplay method

func updateDisplay(){

    runStats.recalculate()

    tableView.reloadData()

    delegate.didChangeStats(stat: runStats, controller: self)

  }

I’ll replace the two lines in pickerDidFinish with the update display then adopt this protocol in the RootViewController

class RootViewController: UIViewController,PickerViewControllerDelegate,IntervalTableViewControllerDelegate

and make the required function

  func didChangeStats(stat: RunStats, controller: IntervalTableViewController) {

    self.runStats = stat

    updateDisplay()

  }

with a slight change so runStats is a var instead of let.

var runStats = RunStats()

Set the delegate to self in the prepare:forSegue:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    if segue.identifier == "IntervalTable"{

      tableViewController = segue.destination as! IntervalTableViewController

      tableViewController.runStats = self.runStats

      tableViewController.delegate = self

     

    }

  }


I try setting to a half marathon, with wonderful results:

I’ve gotten the cell to show values from the main display, and for the cell to send back values to the main display. Along the way required a few change to how I worked with the cell and the table. Actions for a cell belong in the table view if you need the power of view controller, like we do here. We’ve been using one cell so far. In the next installment, we’ll go to multiple cells.

This Old App: Custom Table View Cells

A few months ago, Apple removed my app Interval RunCalc from the App Store for being too old. In this series of articles I’ll document what I did on rebuilding and improving the app. In the last installment, I connected the model to the controller and was shocked to find they actually worked on the first attempt, leaving me in awe of the effects of planning out my code carefully. I’m ready for the next part for the application – the intervals. To start that I’ll need a custom table view cell.

The Storyboard

If you’ve never used custom table view cells before, Let me quickly get you up to speed. They are subviews used as a table view cell. You put whatever views you want in them, and set up a view controller to handle them. You start the whole process by changing from a basic cell to a custom cell.

Then you design your cell. I added three buttons and two labels. The interval will work similar to time, distance and pace I’ve already defined.

I realize I’ve forgotten something: locks. I’ll ask myself a question first: What kind of data do people put in intervals?

Time & Pace = Distance Distance & pace = Time

I don’t compute pace. Pace is always an input in an interval. This leads me to a different locking mechanism than the root view controller: when I input a Time, I lock distance, when I input a distance I lock time.

How do I tell the user which one is locked? There’s two possibilities. I could do the same as the root controller, with some sort of indicator. That requires real estate on an already small space. Another option is changing the font or color of the locked value. I change the alpha of the locked value. For an interval it is the least useful data. Bringing attention to the other two by diminishing the locked value makes sense. For example a locked time would look like this:

I can code the dimming later, but this will work, and leave a very visible pattern in the table for time and distance intervals. I could do more with the background colors here too, but I’ll wait on that. I want to see with a lot of data what it looks like before I pick how to work with color.

Making and Connecting IntervalTableViewCell

With a cell designed, I make a new class IntervalTableViewCell, subclassing UITableViewCell. I associate the cell with the table view in the identity inspector like any other controller.

I’ll use the assistant editor to hook up the outlets. Here’s one of those wrinkles I mentioned earlier: Table view cells are not automatic in the assistant editor.

You will manually navigate to the table view cell

I’ve found it sometimes after this first association shows up in Automatic, but not always. I always use Manual to get to the file.

Once the assistant editor has the right file, I make the connections.

@IBOutlet weak var timeEntry: UIButton!
  @IBOutlet weak var distanceEntry: UIButton!
  @IBOutlet weak var distanceUnits: UILabel!
  @IBOutlet weak var paceEntry: UIButton!
  @IBOutlet weak var paceUnits: UILabel!
//MARK: - Actions

  @IBAction func paceEntry(_ sender: UIButton) {
  }

  @IBAction func distanceEntry_ sender: UIButton) {
  }

  @IBAction func timeEntry(_ sender: UIButton) {
  }

While very similar to the root controller, there’s a few differences. Lock is missing since I’m not using those indicators. The units are labels not buttons. The units will inherit from the root. There is just not enough space for changing units here.

Testing the cell

There’s two ways to handle updating my custom cell. ONe is I can set the outlets and actions in the tableview:cellForRowWithIndexpath:, the second is within the cell. I’ll try the first of those to test the cell. In IntervalTableViewController, I change tableview:cellForRowWithIndexpath: to this:

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(
      withIdentifier: "cell", for: indexPath)  
      as! IntervalTableViewCell
    //let row = indexPath.row
    //cell.textLabel?.text = "Row \(row)"
    return cell
  }

While I also commented out the two demo lines from earlier, the key change is down casting the cell to class IntervalTableViewCell from its generic UITableViewCell. Running this with one row, I get:

 

which is a good sign. Now I’ll check the outlets, adding this code:

let row = indexPath.row
    cell.distanceEntry.setTitle("\(row)", for: .normal)
    cell.paceEntry.setTitle("10:00", for: .normal)
    cell.timeEntry.setTitle("05:45:23", for: .normal)
    cell.distanceUnits.text = "mile"
    cell.paceUnits.text = "min/mile"

I’ll set the number of rows to 5 in the data source, then run

Passing a Model

The outlets are working. I could set up my computations in the IntervalTableViewController, but that gets messy. That’s what caused version 1 to be such a mess. I’m going to pass a RunStats object to the table view cell, letting the cell do all the work.

In the cell, I initialize a property runStats.

var runStats = RunStats()

In the cell, I’ll write a method updateCellDisplay to display the values in runStats. Most of this looks familiar to code I’ve already written. The first part of the method is the time display.

//Time

    let timeTitle = runStats.time.hoursMinutesSeconds()

    timeEntry.setTitle(timeTitle, for: .normal)

The second the Distance

//Distance

    let distanceTitle = runStats.distance.displayString()
    distanceEntry.setTitle(distanceTitle, for: .normal)
    switch runStats.distance.displayUnits{
    case .kilometers:
      distanceUnits.text = "kilometers"
    case .miles:
      distanceUnits.text = "miles"
    }

Finally, there’s the pace

//Pace

    let paceTitle = runStats.pace.displayString()
    paceEntry.setTitle(paceTitle, for: .normal)


    switch runStats.pace.displayUnits{
    case .kilometersPerHour:
      paceUnits.text = "Km/hr"
    case .minutesSecondsPerMile:
      paceUnits.text = "min/mi"
    case .milesPerHour:
      paceUnits.text = "mi/hr"
    }


 

I’ll add this method to the cell’s awakeFromNib: 

// update the cell
    updateCellDisplay()

I’ll test with some set data which I can easily cut and paste, into awakeFromNib:

runStats.pace = PaceStat(minutes: 10, seconds: 00)

runStats.pace.displayUnits = .minutesSecondsPerMile

runStats.distance = DistanceStat(miles: 26.2)

runStats.distance.displayUnits = .miles

runStats.locked = .time

runStats.time = runStats.time(pace: runStats.pace, distance: runStats.distance)

I’ll build and run from that

This works inside the cell. I try cutting and pasting this data to the IntervalTableViewController’s tableView:CellForRowAtIndexpath:….And that doesn’t work.

The cell is already initialized so the cell didn’t update. Adding

cell.updateCellDisplay()

does the trick.

I’ll want that to be more automatic. So I’ll move updateCellDisplay call here in the cell’s controller as a property observer:

var runStats = RunStats(){

    didSet{

      updateCellDisplay()

    }

  }

Which also works. Any time runStats changes, I’ll update the display. That will be every time the table assigns a cell.

I’ve got a working table view cell. What I haven’t done yet is connect it to the Root controller, since the table controller is a subview of a container view. In the next installment, I’ll get the communications between root and table set up.

Things to Watch for at WWDC 2017

Every year there are several traditions that happen around early June. Someone will say Apple is doomed. Someone else will say Apple didn’t make any new innovations, because they didn’t announce any new hardware. Both are a misreading of another June tradition.

In early to mid June is Apple’s World Wide Developer’s Conference, which is really a early warning to developers what will change when new hardware ships in the fall. Apple will change Xcode, iOS, watchOS, macOS, and tvOS to reflect those hardware changes and add a few new API’s and update old ones. With that one sentence I’ve really told you everything you need to know about WWDC2017, yet it gets more interesting once you start to explore that. The press, ever eager to either find what new miracle Apple will pull out of its orchard or alternatively find some reason to say Apple is doomed, will listen carefully to the Keynote on June 5th to find a reason to back their view of Apple. Developers and Apple devotees will listen and watch what new features will appear in the new version of the OS of their choice.

My own tradition is to do a predictive preview of what will happen at the keynote June 5th and the days afterwards. There’s trends at Apple and I try to follow them as close as I can. As a developer of iOS training materials and author of both LinkedIn Learning and my own books, I tend to look at this a bit differently than the tech and business press and hit on things they are usually blind to. I also look with an bit of apprehension on how many things will change in my work due to those changes in WWDC17. This year there are a few themes to watch for at WWDC17 that aren’t the trite predictions about iOS11 or the iPhone8.

Healthcare, POC and Wellness Devices

Of most interest is the healthcare realm. In previous years, I mentioned this is where Apple plans to disrupt the world next. Last month, Tim Cook was testing and talking about one of the most disruptive of those devices: non-invasive blood sugar monitoring, possibly in an Apple Watch. Apple scooped up most of the resources and knowledge of a failed company working on this technology several years ago.

The biggest challenges to such a device are regulatory, yet that has an ever growing loophole. A glucometer used by diabetics daily is an approved medical device. US FDA and its sister organizations internationally have stringent regulations for designing, producing and testing such devices. This is a barrier to development of new devices. However FDA last year published guidance that creates a category of devices that are exempt from regulations:

A general wellness product, for the purposes of this guidance, has (1) an intended use that relates to maintaining or encouraging a general state of health or a healthy activity, or (2) an intended use that relates the role of healthy lifestyle with helping to reduce the risk or impact of certain chronic diseases or conditions and where it is well understood and accepted that healthy lifestyle choices may play an important role in health outcomes for the disease or condition.( General Wellness: Policy for Low Risk Devices)

This is the category that Apple, Fitbit, Garmin and Pulse already use for heart rate monitors. They are exempt from the regs if the product helps a healthy lifestyle. Apple already is talking about blood sugar in terms of diet, not diabetes. A wellness device can be fallible, and indeed I’ve had more than a few odd readings from my Apple Watch heart monitor. If the data from the wellness device start to look fishy, a user would go to an approved medical device. Besides blood, pulse oximetry may not be difficult to get from the watch optical sensors either. Many vitals might come directly from the watch for the user to decide what is a healthy choice for them.

Apple’s ResearchKit and data from wearables point to new ways of looking at the health of both individuals and groups not ever before possible. research with thousands of subjects and thousands of time-linked data points automatically gathered could give a clearer picture of human health. While limited to those who can afford an Apple Watch, there’s some evidence even that won’t always be the situation. One of my speculations from last year’s predictions about this looks like it is coming true. Aetna began subsidizing Apple watches to select large employers and individual customers late last year. While they do not mention anything directly about health monitoring, one of the big frontiers in health insurance is a change in how insurance gets priced. In my understanding, health insurance prices now is based on risk for a demographic, not an individual. With the huge data sets and monitoring of individuals, Insurance companies could monitor individuals, and give price breaks for healthy living and price increases for unhealthy living — on a monthly basis. While some of that data such as heart rate requires a watch some such as movement and exercise data is collected by the phone on a regular basis through the CoreMotion framework and HealthKit.

Of course the HIIPA in the room is patient privacy. ResearchKit has been the experimental platform about giving consent for health data to others. Most of the legal bugs will have been worked out by the time insurance companies move to a sliding scale of premiums.

Swift Playgrounds

The people in Starbuck’s must have thought I was insane dancing in my chair while I was watching the WWDC2016 keynote. I was ecstatic when Apple introduced Swift Playgrounds for iPad. I’ve wanted a mobile development environment for quite a while. Many developers kept thinking “Xcode for iPad” is a joke of something that Apple can’t deliver. The beginnings of it are here. I wrote a review of playgrounds last October in its educational uniform. Since then, I’ve done a lot more work with playgrounds. Apple has improved compatibility between Xcode playgrounds and iPad playgrounds. Now you can switch back and forth from each, running a playground on your iPad or Mac from a Playgrounds folder in iCloud. There are more frameworks available for playgrounds than ever before(for a full list see here) including some heavy hitters such as Metal, MapKit, CoreData and AVFoundation.

As I’ll talk about in my session at iDEv360 this August, playgrounds have the potential of being the ultimate prototyping tool. There’s a few features its missing to get there though. The most aggravating of all is the lack of Interface Builder. All views must be coded, not dragged and dropped. As the liveView for the iPad and Xcode are different sizes, this means auto layout, and a lot of extra code if wanting to run on both. For both the educational market and the developers who will use playgrounds as a prototyping tool, this is still an essential element. Hopefully this will be the big improvement to playgrounds in Xcode 9 and the iOS 11 version for the iPAd.

The second drawback is the lack of a Xcode template to build playground books. Playground books are playgrounds of multiple pages, each running a separate set of code. Different modules can be tested and developed on different pages of the book, or each page could be a new lesson for an educational book. For most applications in Xcode, you have a few buttons to press and you are ready to go. For playgrounds in Xcode, you have to import and modify a starter file in Finder before you start adding code to the book in Xcode, and then running that book on the iPad. This needs to be a lot easier, since even most computer science teachers don’t have the time to do all that file organizing and hand coding of property lists necessary to build a book. There should be a template which asks how many chapters, and how many pages in each chapter, and generates the files for the developer. Once again this is something to watch for at WWDC, as it would improve apple’s position to make playgrounds the gateway to a Swift filled world.

The third drawback of Playgrounds is the price. While the app is free, the iPad you need to use it isn’t — they can be very expensive. Actually iPads are beyond the reach of many educational institutions financially. Its one of the reasons Chromebooks are dominant in the classroom You can get four Chromebooks for one iPad. Apple seems to have addressed the educational market with iPad playgrounds, but still doesn’t have a good, cost effective platform for iPads. I have one idea where that can come from, and that my next thing to watch.

Apple TV Needs iOS

tvOS seems to be the least talked about from the developer’s perspective. From the very few times I’ve worked with it I find it a hassle and a bit alien to work with compared to the other Apple OS’s. Even Using the Apple tv is a pain and a big departure from iOS, macOS and watchOS, which all share features. As more people watch video on their iPads than on their televisions, tvOS needs to assimilate into iOS as a framework, and AppleTV needs to be another iOS product. This has two advantages. AppleTV becomes Apple’s low-cost desktop equivalent to an iPad, targeting the educational market they are losing to lower costing competitors. Secondly, all of the apps available for iPad is now available for the TV, meaning developers by default develop for the TV. AppleTV becomes part of the full Apple ecosystem instead of the black sheep-brick it appears to be.

There are challenges with this approach. Most of all, the input devices for the TV are different than the iPad. Creating pointing devices that work on non touchscreen displays may be a challenge, but nothing that an Apple Mouse or even the current trackpad remote and a keyboard couldn’t handle.

Properties in WatchKit

Apple in watchOS3 took the Apple Watch in the direction it needed to go: the watch can be completely independent of the phone. For iOS developers, the learning curve to watchOS is not steep. I’m biased about saying that since I authored a course on WatchOS3. There are some weaknesses in WatchKit that make it difficult for iOS developers to include watchOS as part of their phone apps. The biggest is unlike UIKit, which lets you change properties of views in code, WatchKit lets you change attributes on the storyboard only. Even finding the title on your button requires storage of the title in a variable. this can bulk up a watch application with clunky code. If not at WWDC2017 in some coming iteration of watchOS, look for this to change.

While Apple keeps making the watch more independent of the paired iPhone, it also needs the opposite: the ability to communicate with iPads, Macs and Apple TV’s. There a perfectly good motion sensor sitting on the wrist of every Apple watch owner. If the watch could talk to other devices, the libraries are there to make the watch into a game controller or 3d gesture sensor.

There will be a “Oh, yeah one more thing”

Apple’s last serious jaw dropper was Swift. No one saw it coming. With leaks and beta versions everywhere it harder to surprise anyone these days. We haven’t had the look of delight on some and the look of terror from others like we did at the June 2014 WWDC keynote announcing Swift. I have nothing to base this prediction on but a feeling that things are a little too quiet, and something mind-blowing is about to happen.

I Have No Crystal Ball

As a developer and author, I do know what bothers me. I’m not one to read most of the previews for WWDC, since most give the same clickbait every year: “This will make or break Apple” I can guarantee you what happens at WWDC will not make or break Apple. Apple learned their lesson with Apple Maps. There’s a reason WWDC is in June: If something goes horribly wrong now, it will be fixed — or pulled– by September. This is the warning that change is coming to developers. The beta versions that thousands of developers and users will jam Apple’s servers downloading Monday afternoon will go through our scrutiny. There will be changes between now and then.

Apple never makes anything truly new. They take the best of what’s out there, and combine it to make it into something remarkable. Once again, we’ll see what they do at WWDC2017.

During the keynote, I’ll be tweeting my reactions @Steve_Lipton

This Old App: Hooking up the Model

A few months ago, Apple removed my app Interval RunCalc 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 the last installment, I hooked up my picker view interface to my app, and built the first round of user interfaces, along the way finding I’ve painted myself into a corner a few times and made several really silly mistakes.  In this installment, I hook up the model so the app actually calculates something – and get a pleasant surprise.

Hook up the Model

Up to now I’ve been setting the initial data to the picker as a literal. For example in the time entry

@IBAction func timerEntry(_ sender: UIButton) {

    if runStats.locked != .time{

      let vc = storyboard?.instantiateViewController(withIdentifier:>"InputController") as! PickerViewController

      vc.delegate = self

      vc.units = .hoursMinutesSecond

      vc.value = 4283.0// in seconds 3600 + 600 + 60 + 20 + 3 = 1 hr 11min 23sec

      vc.modalTransitionStyle = .crossDissolve

      present(vc, animated: true, completion:nil)

    }

  }

I set the picker to always get a 1:11:23 time. I’m going to change this to the RunStats model.

Before I do this, I notice an error I missed up to now. I have timerEntry instead of timeEntry. It’s a little thing, but it affects documentation, so I’ll fix it. Since this is an action, I unconnected the action, changed the spelling, cleaned with Command-Shift-K and then re-connect it.

Once I do that, I change the value to the runStats value for time, based on the model I created a few installments ago.

vc.units = .hoursMinutesSecond

vc.value = runStats.time.seconds()

I’ll do this next for distance, which is a bit more complicated. Each of my stats in the model has a value, which has a getter for the correct units(Kilometer or mile), and a enum for the correct units for display. The picker does too, but the enum is a different type, and the value is a generic Double, getting all the unit information from the enum. I create a switch statement to convert from the model to the picker.

      switch runStats.distance.displayUnits{

      case .kilometers:

        vc.units = .kilometers

        vc.value = runStats.distance.kilometers()

      case .miles:

        vc.units = .miles

        vc.value = runStats.distance.miles()

      }

For pace I should have the same thing, but there a wrinkle. The picker requires a Double for all measurements. In the model I have a string for minutesSecondsPerMile. The picker is looking for a double of seconds per mile, which I don’t have in PaceStat.  In the PaceStat class, I defined that string like this:

  public func minutesSecondsPerMile() -> String{

    let secondsPerMile = 1609.344 / paceStat

    let minutes = Int(secondsPerMile) / 60

    let seconds = Int(secondsPerMile) % 60

    return String(format:"%02i:%02i",minutes,seconds)

  }

Since seconds per mile was a necessary calculation to create the string, this is an easy fix. I can break that method into two methods:

func secondsPerMile()->Double{

    return 1609.344 / paceStat

  }

   

func minutesSecondsPerMile() -> String{

    let minutes = Int(secondsPerMile()) / 60

    let seconds = Int(secondsPerMile()) % 60

    return String(format:"%02i:%02i",minutes,seconds)

  }

Then add to the paceEntry action in my view controller this switch to convert units and values for the picker.

switch runStats.pace.displayUnits{

case .kilometersPerHour:

  vc.units = .kilometersPerHour

  vc.value = runStats.pace.kilometersPerHour()

case .milesPerHour:

  vc.units = .milesPerHour

  vc.value = runStats.pace.milesPerHour()

case .minutesSecondsPerMile:

  vc.units = .minutesSecondsPerMile

  vc.value = runStats.pace.secondsPerMile()

}

The Locked Value

Next I’ll add calculations for the locked value. This was the big surprise. I’ll add this to the RunStats model:

func recalculate() {

    switch locked{

    case .distance:

      self.distance = distance(pace: pace, time: time)

    case .pace:

      self.pace = pace(time: time, distance: distance)

    case .time:

      self.time = time(pace: pace, distance: distance)

    }

  }

 

There are times when one over-analyzes a situation, and leads to a complex solution. I did that in version 1.0 with the calculations above. Because I planned out the model so carefully for V 2.0, this ends up incredibly easy to deal with. In the V1.0 model, I was basing things on what changed, not what was locked. Locked is my solution, not just a locked variable. It the difference between “Pace just changed! What do I do?” to “Solve for time given pace and distance” The second is a lot simpler to think about and code.

Initialization

I’ll make sure I have some starting data in view did load

override func viewDidLoad() {

  super.viewDidLoad()

  runStats.pace = PaceStat(minutes: 10, seconds: 00)

  runStats.pace.displayUnits = .minutesSecondsPerMile

  runStats.distance = DistanceStat(miles: 26.2)

  runStats.distance.displayUnits = .miles

  runStats.locked = .time

  runStats.time = runStats.time(pace: runStats.pace, distance: runStats.distance)

  updateDisplay()

}

I’m ready to build and run. I’m pleasantly surprised that the first run works perfectly. The initial value loads from the model.

This is my ultimate goal for a marathon: a 4:22 finish. That’s way in the future – I’ve never run more than a half marathon. I try my last 5K run to find pace, changing the distance units, and solving for pace.

Yeah, I got a lot of training (and weight loss) ahead of me. My last long run used a slower combination of run/walk so it looks like this:

I have a ten Mile race coming up based on those paces, I can calculate My time between 2:24:50 and 2:16:50.

All of these match data calculated by other apps, so it looks like the app is working. For a change, I don’t have a lot of work to do in a development stage. I just plugged things in and they worked. I know it is supposed to work when I plan things out carefully, but it still amazes me when it does. I’ve done too much debugging to believe something actually works right the first time. In the next installment, I’ll start adding another level of complexity and that might change. I’ll start adding the interval table. First part of that phase is making a custom table view cell.

This Old App: Adding User Interfaces

A few months ago, Apple removed my app Interval RunCalc 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 the last installment, I built a very flexible UIPickerView into my app for fast but reliable data entry. Now I have to hook up that picker view to my app, and build the first round of user interfaces, along the way finding I’ve painted myself into a corner a few times and made several really silly mistakes.

Back in the first installment in this series, I did a few sketches of what I wanted for the user interface. My final sketch was this:

After I made that sketch, I realized something I forgot. I’ll need a way to change units. I also want a better solution to locking one of the variables to be my result. In sketch above, that feature is scribbled in the margin. To change units, I made a button that displays units. Tapping it would give a selection of units.

I removed two of my five entries on the top, Speed and Repetition. I dropped the repetition feature completely. While writing the model, pace and speed became the same, just with different units. That left the three parts on top, a table for the intervals below it and a toolbar. I cleaned it up to look like this:

I liked the look of this, so I went to Xcode, deleted the controls on the storyboard I was using for testing and designed this:

I kept to a very simple palette. The three rows on top are UIViews, labeled Time, Pace and Speed. Each row has two or three buttons as subviews.

Using auto layout, I aligned the baselines of the of the Units  buttons( the one marked m) and Entry buttons( the one marked Distance), and aligned the Entry vertically to the view, The lock button was pinned up, down and left to 0 and I made the width of the lock button 10% the width of the view. If you need a refresher on auto layout, Check out my book Practical Autolayout for Xcode 8

I can pin all the elements where I want them. This keeps everything modular, and with no extra effort I have a landscape layout, something I never had in the original app.

In a later installment, I’ll use class sizes to make a different layout for iPad.
The lighter center section is a UIView which I use as a container view for my interval table. On the bottom is a space for the toolbar, which is not a real toolbar, but a UIView I’ll once again add buttons to. A UIView with buttons for subviews gives me greater control of the buttons than I get with a toolbar.

Once I have this on a storyboard and all my constraints in place, I add outlets and actions for all the buttons. I’m ready to code.

The Lock Button

I designed the app to input data on two of the three rows, and get a result on the third. To indicate what row will get the result, I’ll use a lock button that toggles between the rows. The selected lock button is bright yellow, the deselected buttons a dark transparent yellow. I’ll add two constants for that

let lockOffColor = UIColor(red: 1.0, green: 1.0, blue: 0.31, alpha: 0.3)
let lockOnColor = UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 0.9)

At the press of a lock button, I set the property in the model locked to the locked value, I turn on the touched button to glow yellow, and dim the other two buttons.

@IBAction func lockTime(_ sender: UIButton) {
runStats.locked = .time
timeLock.backgroundColor = lockOnColor
distanceLock.backgroundColor = lockOffColor
paceLock.backgroundColor = lockOffColor
}
@IBAction func lockDistance(_ sender: UIButton) {
runStats.locked = .distance
timeLock.backgroundColor = lockOffColor
distanceLock.backgroundColor = lockOnColor
paceLock.backgroundColor = lockOffColor

}
@IBAction func lockPace(_ sender: UIButton) {
runStats.locked = .pace
timeLock.backgroundColor = lockOffColor
distanceLock.backgroundColor = lockOffColor
paceLock.backgroundColor = lockOnColor

}

This is purely cosmetic. Figuring out what I’ll do with the lock will come later.

Setting the Unit of Measure

Each row has a unit of measure. The time row has no change of units, but the user can change the units for distance and pace. For changeable units, the button’s action will present a UIAlertController as an action sheet with possible units of measure. That will change the title of the button with the units measurement on it. Each unit of measure is a UIAlertAction with a closure. Here the distance action sheet code selecting miles and kilometers :

@IBAction func changeDistanceUnits(_ sender: UIButton) {
let actionSheet = UIAlertController(title: "Distance Units", message: "Select distance units", preferredStyle: .actionSheet)
let kilometers = UIAlertAction(title: "Kilometers", style: .default) { (alertAction) in
self.distanceUnits.setTitle("Km", for: .normal)
}
let miles = UIAlertAction(title: "Miles", style: .default) { (alertAction) in
self.distanceUnits.setTitle("Mi", for: .normal)
}
actionSheet.addAction(kilometers)
actionSheet.addAction(miles)
present(actionSheet, animated: true, completion: nil)
}

I start to get a sinking feeling here, one I had when finishing the picker as well. In version 1.0 the unit conversions were the biggest problem, and I’m beginning to wonder if they will be here too. There is nowhere in the model or controller to select the unit for distance or pace.

Opening the Entry Pickers

I’ll put that aside for the moment. Instead I’ll set up the actions for the three input buttons. In the last installment I went through the testing of a picker, and I copy that code to call the picker view as a modal. This time I’ll use a cross dissolve for the transition:

@IBAction func timerEntry(_ sender: UIButton) {
if runStats.locked != .time{
let vc = storyboard?.instantiateViewController(withIdentifier: "InputController") as! PickerViewController
vc.delegate = self
vc.units = .hoursMinutesSecond
vc.value = 4283.0 // in seconds 3600 + 600 + 60 + 20 + 3 = 1 hr 11min 23sec
vc.modalTransitionStyle = .crossDissolve
present(vc, animated: true, completion: nil)
}
}

I’m leaving the literal data in the action for now, so I’m testing known data. I’ll do similar for the distance and pace entry actions.

The Container View Table

Before I test, I want to add a little bit of code to the interval table view. I’ll implement the intervals after I get the basic app working. I’ll put some dummy data so I can look at the full User Interface. This is in a container view, a great way of getting a table view controller on a part of the superview without it using the complete view. I wrote about this more here if you are interested in the details. In short, I dragged a table view controller to the storyboard, and control-dragged from the UIView to the table view controller, selecting the Embed segue. That creates a relationship between controllers, which you see in the table view controller resizing to the size of the view.

I make a new UIViewController file. I tend not to use the UITableviewController template, since it has way too much junk in it. Instead I change the subclass UIViewController to UITableViewController.

class IntervalTableViewController: UITableViewController

Once I have this file made, I’ll use the identity inspector to set it as the class of the table view controller.

For the table view data sources I give one section and ten rows.

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}

For the cells I print the row number with alternating colors.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let row = indexPath.row
if (row % 2) == 0{
cell.backgroundColor = UIColor.blue
} else {
cell.backgroundColor = UIColor.white
}
cell.textLabel?.text = " Row:\(row)"
return cell
}

I’ll add a title to the one section I have.

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Intervals"
}

The table will show up for placement, but I’ll do nothing with it in this installment. I want calculations working first.

The First Iteration Test

I’m ready for my first test. Running the app gets me this:

The table is in place and the app looks nice and clean. Tapping the m/s for meters per second, opens up a action sheet.

Tapping Miles per Hour gets me mile per hour

If I tap Time, nothing happens. That’s the lock in action. But If I tap speed, the pickers show, with the correct value.

I lock speed

and try the time:

I can send data to the pickers.

 

Matching Colors in the Picker

I’m not happy about the colors. The pickers don’t match the rest of the app. I’d like the pickers to reflect the colors of the RootViewController without me playing around too much. If I change the root’s colors later on, the colors should still match.

I set up PickerViewController for background color property observers.

var backgroundColor = UIColor.darkGray{
didSet{
view.backgroundColor = backgroundColor
}
}
var toolBarColor = UIColor.lightGray{
didSet{
toolBar.backgroundColor = toolBarColor
}
}

In RootViewController, I assign the view’s background color to the PickerViewController’s background color.

@IBAction func timerEntry(_ sender: UIButton) {
if runStats.locked != .time{
let vc = storyboard?.instantiateViewController(withIdentifier: "InputController") as! PickerViewController
vc.units = .hoursMinutesSecond
vc.value = 4283.0 // in seconds 3600 + 600 + 60 + 20 + 3 = 1 hr 11min 23sec
vc.modalTransitionStyle = .crossDissolve
present(vc, animated: true, completion: nil)
vc.backgroundColor = view.backgroundColor!
}
}

When I change the property backgroundColor in the picker it should change the color in the picker. I try it and still get bright backgrounds, not dark ones. I forgot to take out test code. In the PickerViewController class, I have these three functions:

func distance(){
titleLabel.text = "Distance"
backgroundColor = UIColor.cyan
}
func paceOrSpeed(){
titleLabel.text = "Pace / Speed"
backgroundColor = UIColor.yellow
}
func time(){
titleLabel.text = "Time"
backgroundColor = UIColor.green
}

I comment out those background colors and run again. The background appears.

I see two more changes. I want to change the toolbar color to the button’s color. If I change the toolbar at top to the same color as the button’s background with this:

vc.toolBarColor = sender.backgroundColor!

It fails with an unexpectedly found nil while unwrapping an Optional value error. I know what’s wrong almost immediately. This is one of those silly mistakes, but one that is so subtle it could take a long time to find it. I’m relieved I found it early. The background isn’t in the button, but in the superview of the button. The button background is transparent, hence the nil. It should be this:

vc.toolBarColor = (sender.superview?.backgroundColor!)!

That works, but the result is worse for the text. Black on dark just doesn’t work.

I’m a little nervous about all those optionals and want a function to set the the text color. I add this to clean it all up.

func colors(picker:PickerViewController, from button:UIButton){
    if let backgroundColor = view.backgroundColor{
    picker.backgroundColor = backgroundColor
}
     if let toolBarColor = button.superview?.backgroundColor{
     picker.toolBarColor = toolBarColor
}
}

I replace the lines changing color in the actions with the colors method.

colors(picker: vc, from: sender)

My foreground text solution will go in the colors function. The color I’ll choose is the same as a title color, so I can start like this:

if let titleColor = button.titleColor(for: .normal){
}

Then I get stuck. I forgot to add a foreground color property to PickerViewController. The UIPickerView in the controller also does not have a foreground color property to set. Originally, I used the easiest of the UIPickerViewDatasource methods to display a picker component pickerView:titleForRow:forComponent. That returns a string. There’s a delegate method to return a UIView, such as a UILabel in whatever color or font I want:

func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    let label = UILabel()
    label.font = UIFont.preferredFont(forTextStyle: .title1)
    label.textColor = textColor
    switch components[component]{
    case .ten:
        label.text = String(format: "%01i", row)
    case .six:
        label.text = String(format: "%01i", row)
    case .colon:
        label.text = ":"
    case .decimal:
        label.text = "."
    }
    return label
}

I used a property textColor which I have yet to define. I’ll use a property observer to change all the other text on the picker controller to this color.

var textColor = UIColor.black{
    didSet{
        backButton.setTitleColor(textColor, for: .normal)
        doneButton.setTitleColor(textColor, for: .normal)
        titleLabel.textColor = textColor
        unitsOfMeasureLabel.textColor = textColor
}
}

Back in RootViewController, the colors method changes to:

func colors(picker:PickerViewController, from button:UIButton){
     if let backgroundColor = view.backgroundColor{
         picker.backgroundColor = backgroundColor
}
     if let toolBarColor = button.superview?.backgroundColor{
         picker.toolBarColor = toolBarColor
}
     if let titleColor = button.titleColor(for: .normal){
         picker.textColor = titleColor

}
}

Now I reflect the colors in my buttons in the picker, giving a consistent and readable look:

The title should read Distance, not 583.0 . That’s a testing element I left in the title, which I comment out.

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
updatePickerValue()
//titleLabel.text = "\(value)" //for testing only
}

Delegation From the Picker

I’ll send data from the picker to the root via delegation. I’ll add to PickerViewController.swift a protocol for a delegate:

protocol PickerViewControllerDelegate{
func pickerDidFinish(value:Double, controller:PickerViewController)
}

I’ll go through all the normal steps for making a delegate. If you don’t know those I’d suggest my LinkedInLearning course on delegation and datasources. The last of those steps is adding the delegate method to RootViewController.

func pickerDidFinish(value: Double, controller: PickerViewController) {
}

I’ve been ignoring an important question: value is a Double, but what does it measure? What’s the units? How do I reflect that in the user interface?

I designed PickerViewController with an enum for that.

enum Units {
        case meters,kilometers,miles //distance
        case milesPerHour,minutesSecondsPerMile,kilometersPerHour // speed and pace
        case hoursMinutesSecond //time
    }
}

I set up a switch clause, and assign value to the right place in the right unit system…well almost.

func pickerDidFinish(value: Double, controller: PickerViewController) {
    switch controller.units {
    case .meters:
        runStats.distance.meters(value)
    case .kilometers:
        runStats.distance.kilometers(value)
    case .miles:
        runStats.distance.miles(value)
    case .hoursMinutesSecond:
        runStats.time.seconds(value)
    case .kilometersPerHour:
        runStats.pace.kilometersPerHour(value)
    case .milesPerHour:
        runStats.pace.milesPerHour(value)
    case .minutesSecondsPerMile:
        //ummmmm.......not there!!!
    }
}

I need one more conversion setter I didn’t originally have in PaceStats. for .minutesSecondsPerMile going to the picker, I have seconds per mile coming back out, but no method to change and store that as meters per second. I did the calculations and included it as part of the minutes and seconds getter:

public func minutes(_ minutes:Int,seconds:Int){
let secondsPerMile = Double(60 * minutes + seconds)
paceStat = 1609.344 / secondsPerMile
}

I can break this up to

func secondsPerMile(_ secondsPerMile:Double){
paceStat = 1609.344 / secondsPerMile
}
public func minutes(_ minutes:Int,seconds:Int){
secondsPerMile(Double(60 * minutes + seconds))
}

then finish the delegate method with

case .minutesSecondsPerMile:
runStats.pace.secondsPerMile(value)

Display Updates

I returned data from the picker and changed the model. Next I’ll update the root view’s display with new values. I’ll start a new method and add this for the time using the hourMinutesSeconds function of TimeStat

func updateDisplay(){
//time
    let timeTitle = "Time: " + runStats.time.hoursMinutesSeconds()
    timeEntry.setTitle(timeTitle, for: .normal)
}

For distance, I have meters, kilometers and miles. Which one do I display? I have no idea and the model doesn’t either. I’ll have to add tracking of this. I can track this in the model or in the controller. I go for the model. I’ll add an enumeration, a property using that enumeration, and a method that returns a formatted string based on that property.

I’ll try this first with the simpler DistanceStat. The enum for distance DisplayUnits would be kilometers and miles, which I add to a property displayUnits.

enum DisplayUnits {
    case kilometers,miles
}
var displayUnits:DisplayUnits = .miles

To my getters, I add the method

func displayString()->String{
    let string = "Distance: %7.1"
    switch displayUnits {
    case .miles:
         return String(format:string, miles())
    case .kilometers:
        return String(format:string, kilometers())
}

}

With a model that tracks the units I’m using, I set displayUnits in the action sheets’ alert actions I set up earlier. I’ll also update the display when I change units. For distance’s kilometer Alert Action it would look like this:

let kilometers = UIAlertAction(title: "Kilometers", style: .default) { (alertAction) in
self.distanceUnits.setTitle("Km", for: .normal)
self.runStats.distance.displayUnits = .kilometers
self.updateDisplay()
}

Miles would follow the same pattern. I‘ll set the distanceEntry button title in updateDisplay to

distanceEntry.setTitle(runStats.distance.displayString(), for: .normal)

In distanceEntry, I’ll set vc to 5 kilometers as the initial value. I’ll hit run and get 5 kilometers.

I’ll change that to 8 and press Done. Nothing happens. That’s because I forgot to add update to the delegate method. I did add it to the units, so I’ll change to Kilometers and get 7, which is the wrong answer.

I change to Miles and also get 7.

Checking my code I find a seriously stupid error.

let string = "Distance: %7.1”

I forgot an f. It should be %7.1f. I add updateDisplay to the delegate method too. I try again. And this time it works. For kilometers I get 5.0 and for miles 3.11.

I’ll do the same thing for the pace, now that I know this works. The only variation is distinguishing between speed and pace, which I do in updateDisplay.

var paceTitle = runStats.distance.displayString()
if runStats.pace.displayUnits == .minutesSecondsPerMile{
    paceTitle = "Pace: " + paceTitle
} else {
    paceTitle = "Speed: " + paceTitle
}
paceEntry.setTitle(paceTitle, for: .normal)

I try that out in the simulator, and it fails, reading zero. The picker reads correctly as 11:23 min/mi and the speed/pace label changes correctly. Changing the units always reads zero. I add to the delegate method a print(value) to check if the value gets there, and it does. Still in the delegate method I try to see if the displayString() works right, and it does. The problem is in updateDisplay, and once again it’s a typo. I had runStats.distance instead of runStats.pace.

I try it one more time, And this time it works.

I’ve connected all the user interface elements to the controller. Along the way, I made several silly mistakes, which can and will happen to anyone writing code. Its silly mistakes like these that make the most bugs. I also gained a greater appreciation for enum and switch, which are doing an incredibly good job of keeping my units straight. I have a consistent user interface, one that I find streamlined and elegant, at least compared to version 1.0. I have a lot to go though. In the next installment, I’ll hookup the model to the entry buttons, and start calculating run stats.