This Old App: Communicating with Container Views

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 episode, I got the table to use multiple cells and the app to total those cells in the upper half of the display. In this installment I’ll set the units correctly in the table based on units changes in the root, and thus finish setting up the interaction between the top and bottom of the app.

To backtrack a little, I set the root controller to send the model in the root to the table:

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

    if segue.identifier == "IntervalTable"{

      tableViewController = segue.destination as! IntervalTableViewController

      tableViewController.statsIntervals = runStatsIntervals

      tableViewController.delegate = self

     

    }

  }

That brings a question I avoided answering since the beginning of this project: What is the relationship between the three stats in the root at the top of the app and the table on the bottom?

The interaction from the table to the root stats I’ve already established. The top values will be the totals of the intervals I placed in the table. But how does interactions on the top three stats affect the table?

The first is easy. When I change the units, any new stat should be in that stat. What If I change the entries in the root controller? The ideal would be to recalculate the intervals to those values. That’s a lot of work, and would be a new function to the app that wasn’t there before. The second is much easier. When the Time, Distance or Pace changes on top, this becomes a simple stats calculator, not an interval calculator. I’ll clear the table.

This is a refurbish effort, not a new app. The ideal version could be version 2.1, with other improvements I can make after the App is back in the store. My primary purpose in refurbishing this app is to get it into the store again. I want that as soon as possible. updates can come later. I’ll add complex calculations to my list of improvements including that nagging one of adding notifications to make this an interval running timer.

With that in mind, I stop sending anything but the delegate to the table from the RootViewController

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

    if segue.identifier == "IntervalTable"{

      tableViewController = segue.destination as! IntervalTableViewController

      //tableViewController.statsIntervals = runStatsIntervals

      tableViewController.delegate = self     

    }

  }

I’ll treat the table as a view. I’ll tell it what to do from the root, but nothing more than that.

Setting Units

Going back to the IntervalTableViewController, I have two models floating around I‘ve been using. I’m going to use both of them:

 var runStats = RunStats()

 var statsIntervals = RunStatsIntervals()

The runStats will be my template for a new interval. I’ll change the name to reflect that.

var runStatsTemplate = RunStats()

I’m only using this in viewDidLoad, so I’ll go down there and refactor the identifier

statsIntervals.intervals[0] = runStatsTemplate

I run that. That gives me a blank interval and the marathon 26.2 stats on top.

Adding intervals does this:

I’ll change the template when I change units. Changing the template is tricky. I set those units in closures as part of the action sheet. I’ll need to set the units for the table in those closures. I’ll add a method to the RootViewController first:

func updateTableUnits(){

    let template = tableViewController.runStatsTemplate

    template.pace.displayUnits = runStats.pace.displayUnits

    template.distance.displayUnits = runStats.distance.displayUnits

  }

This will save a lot of long code in the closures. I’ve set the display units in the closure for the runStats, so calling this after I’ve done that will update them. For example in changeDistanceUnits My two alert actions look like this.

 let kilometers = UIAlertAction(title: "Kilometers", style: .default) 
    { (alertAction) in

      self.distanceUnits.setTitle("Km", for: .normal)

      self.runStats.distance.displayUnits = .kilometers

      self.updateTableUnits()

      self.updateDisplay()

    }

 let miles = UIAlertAction(title: "Miles", style: .default) 
    { (alertAction) in

      self.distanceUnits.setTitle("Mi", for: .normal)

      self.runStats.distance.displayUnits = .miles

      self.updateTableUnits()

      self.updateDisplay()

    }




I do the same to pace, then run the app.

Testing and Bugs

I add 10:00 min mile for a mile.

Then I change the distance to kilometers: I get this.

When I hit the add button, I lose kilometers.

This isn’t what supposed to happen. The new cell should be in kilometers. I realize I did make a silly omission, which explains why the new cell does not have kilometers. I never assigned addCell with the units.

func addCell(){

    let newStats = RunStats()

    newStats.distance.displayUnits = runStatsTemplate.distance.displayUnits

    newStats.pace.displayUnits = runStatsTemplate.pace.displayUnits

    statsIntervals.add(stat:newStats)

    updateDisplay()

  }


That explains one error. The second error is also related. Look a the delegate method in RootViewController which sends back the totals

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

    self.runStats = controller.statsIntervals.totalStats()

    updateDisplay()

  }

It reads directly from totalStats in the the RunStatsIntervals model.

func totalStats()->RunStats{

    let runstat = RunStats()

    runstat.pace = avgPace()

    runstat.time = totalTime()

    runstat.distance = totalDistance()

    return runstat

  }

That is using a default initialized runstat. What I need to do is change the delegate method to have the correct units before I update.

While I’m in the code, I’m going to make one more change. In the IntervalTableViewController, I’ll make one more method.

func initIntervals(){
        statsIntervals = RunStatsIntervals()
        currentRow = 0
        statsIntervals.add(stat: runStatsTemplate)
    }

In the pickerDidFinish for the RootViewController, I’ll add that method and reset the intervals pointer before updating.

    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:
           runStats.pace.secondsPerMile(value)
        }
        tableViewController.initIntervals()
        updateDisplay()
    }

I get almost everything working right when I add a few intervals, switching to kilometers on the last one.

Changing to a 10 Minute pace, it removes, but does not clear the top interval.

So I got two more problems to fix. I’m kicking myself right now. I got bugs for not planning this out carefully. I procrastinated this part in planning and I’m paying for it.

With a little thought, I know why the top row is doing what it is doing. Init is the only place I don’t want to send the template. I want it blank.

func initIntervals(){

    statsIntervals = RunStatsIntervals()

    currentRow = 0

    statsIntervals.intervals[currentRow] = RunStats()

    tableView.reloadData() 

  }

This time it works.

The lesson the last two installments made, I saw the other side this time. By not planning and thinking out a critical issue ahead of time, I got bugs. I got them fixed — at least to how much I know. My next step is to load the app onto my phone and start some user testing. In our next installment I’ll discuss those changes and testing.

 Check out my latest  courses on LinkedIn Learning:  Advanced iOS ApplicationDevelopment:Core Motion and Learning Swift Playgrounds Application Development

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s