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:
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))) |
| } |
| } |
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.
3.17.184.39