This Old App:Use Multiple Custom Cells

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 got the custom table view talking with the root view. This time I’m adding multiple cells to the table. I’ll address a really important question along the way: how to know what cell to use.

Working with a New Model: RunStatsIntervals

Back when we built models for the app, I made more than just the RunStats model. I made a second model class which contained a array of RunStats called RunStatsIntervals.

var intervals = [RunStats]()

It contained methods for totaling the stat. What it didn’t contain is a method for returning a RunStat for those totals. That will come in handy for passing to view controller’s runStat PROPERTY, so I’ll add that to the methods in the model:

func totalStats()->RunStats{

    let runstat = RunStats()

    runstat.pace = avgPace()

    runstat.time = totalTime()

    runstat.distance = totalDistance()

    return runstat

  }


While doing that I see one more problem with a division by zero error in averagePace, so I change

let averagePace = totalDistance / totalTime

to

var averagePace = 0.0
    if totalTime != 0{
        averagePace = totalDistance / totalTime
}

So the function returns zero instead of a division by zero error.

I’ll now head over the IntervalTableViewController and add the model there:

  var statsIntervals = RunStatsIntervals()

I’ll change the data sources to read from the intervals list my number of rows is now

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return statsIntervals.intervals.count

  }

My number of sections remains 1, and my cells will now be from the intervals array of statsIntervals. Cleaning up my code of all the test code I used in the last installment, I get 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.runStats = statsIntervals.intervals[row]

    return cell

  }


Running that code, I get this:

Which is what I expect. For that first interval, I’ll Go back to RootViewController and add another instance of the model

var runStatsIntervals = RunStatsIntervals()

In ViewDidLoad, I’ll make that first cell which is all zeroes equal the root’s display:

runStatsIntervals.intervals[0] = runStats

In prepare:forSegue: I send the model to the table’s viewController:

 tableViewController.statsIntervals = runStatsIntervals

Run again and I get a single cell with the root’s values.

Adding More Cells

To add another cell, I have a button on the bottom toolbar. That will activate in RootController. The root controller then has to tell the table view to add a new cell by adding an element to the statsIntervals.intervals array, then updating the display.

First I’ll make an action for the button. I left myself some comments here which I realize are completely obsolete. This is how I dealt with it in version 1:

 @IBAction func addInterval(_ sender: UIButton) {

 //step 1 instantiate an instance of runstats

// step 2 show an action sheet to pick between time/pace,time/distance or distance/speed. use the selection to lock the runstats.

//step 3 set units to reflect runstats

//step 4 add to runstats

//step 5 update table and totals     

}

Version 2 does a lot of this in the table view controller or a delegate method. I got rid of step 2 by the buttons on the cells to change time, pace and distance. I set up the segue to have a pointer to the table view controller, so what looks like a lot of work is really only this:

@IBAction func addInterval(_ sender: UIButton) {

    tableViewController.addCell()

  }

Then the IntervalTableView controller has a method

  func addCell(){

    statsIntervals.add(stat: RunStats())

    updateDisplay()

  }

This is ONE of those times I get a smile on my face for good planning. I run, press the plus and I get this:

Finding the Cell indexPath

Still feeling the nice warm glow of getting a lots of work done in three lines of code, I realize the next big hurdle. The second cell does nothing. I can try to change the numbers there, but nothing changes. The reason why is simple. Take a look at the updateDisplay method:

  func updateDisplay(){

    runStats.recalculate()

    tableView.reloadData()

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

  }

I’m still updating the display for the one cell, runStats, I used as a test. I want the array, not the one cell. That’s true in the Delegate pickerDidFinish too. I used runStats, not the interval.

Here comes the big question of the day: That interval needs an index. How do I get the index? Since I’m hitting the button and not the cell underneath it, I can’t get an index from didSelectRowAtIndexpath. I start thinking this out and come up with some crazy solutions to use the delegate to get my index.

Then I get sane. There has to be a way to do this since every social media app uses something like it to work properly. I browse the documentation. In the UITableViewdocumentation I find this gem:

func indexPath(for cell: UITableViewCell) -> IndexPath?

This takes the cell and returns the index path. I just need the cell itself. I’ll be needing this in only one situation: when I press a button in a table view cell. Going to the view hierarchy I see this:

The superview of the button is the content view. the superview of the content view is the cell. So I can get the superview of the super view, cast it as a UITableViewCell and use the indexPathForCell: method to get an index. I’ll do this for all three buttons, so I’ll write a function

 func currentRow(button:UIButton){

    let cell = button.superview?.superview as! IntervalTableViewCell

    guard let indexPath = tableView.indexPath(for: cell) else{

        print("not a cell")

        return

      }

    print("Selected Row \(indexPath.row)")

    currentRow = indexPath.row

     

  }

I added a few print statements for testing. That function sets a new property currentRow I can use in the update and picker delegate to get the cell I want to modify.

var currentRow = 0

I can add this function to the three entry buttons with

currentRow(button: sender)

That sets the current row. Running this, I find I can add a few rows and select the correct index for the row. Now I can use it. There a few places I’ll need it. In each action, I’ve been using the property runStats. Instead of doing a lot of extra refactoring, I’ll change to a local identifier called runStats.

let runStats = statsIntervals.intervals[currentRow]

I add this in to the updateDisplay and pickerDidFinish methods too, so their runStatsare local constants for the cell. I run this and I can change the numbers, they update correctly so I can add two more rows like this:

Rows seem to be working. I try one more , changing the first interval to a 30 second 10:00 min per mile and the second to a 30 second 20 minute per mile walk.

 

That works as expected. I’m having one of those moments where you look at an old picture of yourself and wonder” was that ever me? I’ve changed as a developer. I believe we all do. The code I wrote years ago is so different than the code I write today. No matter how old or long Iv’e been at this, my code writing style is more elegant than ever. Struggles I had years ago don’t happen. There also the change in language to Swift. I tend think more clearly in Swift than I do Objective-C.

Of course that does not mean this app works and I’m ready to ship. In the next installment, I’ll get the units in the intervals to work correctly and solve one of the big issues I haven’t addressed yet — How do the two halves of the app interact?

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 and Lynda.com author. His latest courses are Advanced iOS ApplicationDevelopment:Core Motion and Learning Swift Playgrounds Application Development

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.

Adventures in Swift and iOS App Development