Configuring the Content in Tables

Now that you’ve squared away how your row controller is going to behave, you can use it in your RunLogInterfaceController. Unlike UITableView from UIKit, where you’d return the number of rows from a data source method and then configure each row on demand, you’ll set the number of rows manually and then iterate through them and configure them as you go. You’ll also configure your NSFormatter subclasses to do formatting and then pass them to each row controller. You’ll get everything set up in the interface controller’s willActivate method. First, however, you need the data to put in the rows. Add a new Swift file to your WatchKit extension, and name it Run.swift. In it, create a new class with some properties for a run:

 class​ ​Run​ {
 
 let​ distance: ​Double​ ​// in meters
 let​ laps: [​NSTimeInterval​]
 let​ startDate: ​NSDate
 
  init(distance: ​Double​, laps: [​NSTimeInterval​], startDate: ​NSDate​) {
 self​.distance = distance
 self​.laps = laps
 self​.startDate = startDate
  }
 
 }

You could have made Run a struct instead, but you’ll be using it for some class-only functionality in the future, so marking it as a class now avoids future headaches. Now that you have the class to store the data, head back to RunLogInterfaceController.swift and add an array of runs to display:

 var​ runs: [​Run​]?

Before you can add rows to the table, you need a way to reference it. Open the storyboard and the Assistant Editor, and then connect the table in your Run Log interface controller to a new @IBOutlet property called runTable:

 @IBOutlet​ ​weak​ ​var​ runTable: ​WKInterfaceTable​!

Connecting the @IBOutlet is especially easy using the Assistant Editor; simply click the circle to the left of its declaration; then drag to the table in your UI, just as in this image:

images/TapALap-connecting-table-outlet.png

You’re now ready to display run data in the table. In the interface controller’s willActivate method, you’ll iterate over the runs array:

1: override​ ​func​ willActivate() {
super​.willActivate()
guard​ ​let​ runs = runs ​else​ { ​return​ }
5: 
runTable.setNumberOfRows(runs.count, withRowType: ​"RunRow"​)
for​ i ​in​ 0 ..< runTable.numberOfRows {
guard​ ​let​ rowController = runTable.rowControllerAtIndex(i)
10: as?​ ​RunLogRowController​ ​else​ { ​continue​ }
configureRow(rowController, forRun: runs[i])
}
}

You get started with the table on line 6, when you set the number of rows to the number of runs in your runs array. Then you get to a for loop on line 8, and this is where the real fun begins. The table tells you how many row controllers it created with its numberOfRows method, so you can loop through them. Inside each loop, you call the table’s rowControllerAtIndex method to get the row controller it’s created for the row, and then you can modify it—but before you can use it, you need to make sure it’s the right class, because rowControllerAtIndex returns AnyObject?.

To do that, you’ll create a method called configureRow(_:forRun:). In that method, you simply pass in your formatters and the proper Run instance, and the code you wrote in the section on row configuration,, runs to configure the row. Here’s what the method should look like:

 func​ configureRow(rowController: ​RunLogRowController​, forRun run: ​Run​) {
  rowController.dateFormatter = dateFormatter
  rowController.distanceFormatter = distanceFormatter
  rowController.durationFormatter = durationFormatter
 
  rowController.configure(date: run.startDate,
  distance: run.distance,
  duration: run.duration)
 }

That method was easy. If you try to build, you’ll notice that you never declared the formatters, so let’s do that now. You don’t need them to be created until you actually use them, so lazy variables in Swift are a perfect match. Head up to the class extension and add some declarations for them:

 lazy​ ​var​ dateFormatter: ​NSDateFormatter​ = {
 let​ dateFormatter = ​NSDateFormatter​()
  dateFormatter.dateStyle = .​ShortStyle
 return​ dateFormatter
 }()
 
 lazy​ ​var​ distanceFormatter = ​NSLengthFormatter​()
 
 lazy​ ​var​ durationFormatter: ​NSDateComponentsFormatter​ = {
 let​ dateComponentsFormatter = ​NSDateComponentsFormatter​()
  dateComponentsFormatter.unitsStyle = .​Positional
 return​ dateComponentsFormatter
 }()

Now that you’ve declared them, you can use them whenever they’re needed, and because they’re marked lazy, they’ll be initialized the first time they’re used. Note that two of them need more customization, so wrap the customizations in a closure, using its return value as the initialized value of the property. Next, you need a duration property for the Run class, which you can create as a computed property. The duration of the run is the same as the sum of all of its laps, so you can use Swift’s reduce method to add them:

 var​ duration: ​NSTimeInterval​ {
 return​ laps.reduce(0, combine: +)
 }

You’re finished! Build and run the app, and the content of your runs array will be displayed in the run log. Only there isn’t anything in that array. For now, you can add some test data:

 override​ init() {
  srand48(time(​UnsafeMutablePointer​<time_t>(bitPattern: 0)))
 
 let​ randomRun: (​NSDate​) -> ​Run​ = { date ​in
 let​ lapCount = arc4random_uniform(20) + 5
 let​ lapDistance = arc4random_uniform(1000) + 1
 
 var​ laps: [​NSTimeInterval​] = []
 
 for​ _ ​in​ 0 ..< lapCount {
 // Pace is in m/s. 9 minutes per mile is about 3 meters per second.
 // Generate a random pace between ~7.5 min/mi and ~10.5 min/mi.
 let​ speed = 3.0 + (drand48() - 0.5) ​// in meters per second
 let​ lapDuration: ​NSTimeInterval​ = ​Double​(lapDistance) / speed
 
  laps.append(lapDuration)
  }
 
 let​ run = ​Run​(distance: ​Double​(lapDistance * lapCount),
  laps: laps,
  startDate: date)
 
 return​ run
  }
 
  runs = []
 
 for​ i ​in​ 0 ..< 5 {
  runs?.append(randomRun(​NSDate​().dateByAddingTimeInterval(​Double​(i)
  * 24 * 60 * 60)))
  }
 }
images/TapALap-run-log-on-watch.png

For real this time, you can display data. Build and run one more time and you’ll see something like this:

As you can see, getting your data into the table is pretty straightforward. If you needed to use multiple types of rows with different row controllers, you’d use the table’s setRowTypes method instead of setNumberOfRows(_:withRowType:). To use that method, simply create an array of strings, one for each row in the table, set to the row type identifier. If you do that, keep in mind that the row controller class you get back from your table’s rowControllerAtIndex method will depend on that identifier! In your willActivate you can assume you’re getting a RunLogRowController, but in more complicated table layouts you won’t be so lucky.

Another reason this code is so straightforward is that you have static content. You create the table in the storyboard and set the number of rows in your interface controller’s code, and then you’re finished with it. The watch takes care of scrolling for you, and everything is smooth as butter. If you need to modify the contents of the table, things get a bit trickier.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.209.130