Not every transition can be implemented in your storyboard. Whether you need to gut your app’s interface entirely and start fresh or dynamically choose interface controllers to present, you may need to use some of WKInterfaceController’s methods to accomplish your tasks. Because of this, you may see interface controllers in your storyboard that don’t appear to be connected to anything. Fear not: you can always instantiate one by name, using the identifier that you give it in the storyboard. For that reason, Xcode will warn you if you have an interface controller in your storyboard with no linked segues or an identifier—there’s no way to get to it! Let’s look at two ways to transition in code. First, let’s transition when the user taps a button.
Remember the button you created to configure the track on the Go Running interface controller? Tapping it should probably transition to a screen on which you can configure a track. To accomplish that, you’ll need a new interface controller. Select File → New → File in Xcode, choose WatchKit Class in the templates, and make a new subclass of WKInterfaceController named TrackConfigurationInterfaceController. Once you’ve added the interface controller to your project, you can add a new interface controller to the storyboard and use the Identity Inspector to set its class to TrackConfigurationInterfaceController. Instead of making a segue from the button to the interface controller, to do it in code you need to give the interface controller an identifier. Open the Attributes Inspector, select the new interface controller, and give it the identifier “TrackConfiguration.” Since you’re going to present it modally, you’ll leave the interface controller’s title blank.
To perform this transition in code, first you need to add a new method to GoRunningInterfaceController.swift:
| @IBAction func trackButtonPressed() { |
| presentControllerWithName("TrackConfiguration", context: self) |
| } |
Rather than creating the interface controller in code, this method simply calls presentControllerWithName(_:context:) with a string. This string is the identifier you set in your storyboard, so be sure that they match!
All that’s left is to configure the button to call this method when you tap it. Head back to your Interface.storyboard and Control-drag from the Selected Track button to the Go Running interface controller, and then select trackButtonPressed from the pop-up. Build and run, tap the button, and you’ll see the new interface controller! It doesn’t do anything yet, but you’ll fix that later. For your next code-based transition, you’ll use a more complicated process and reload the entire app’s interface.
The main point of your app is to time users while they run laps at the track, but none of these presentation methods you’ve seen so far seem appropriate. The Go Running interface is a likely place to start, but it has more to do with selecting a track than doing the timing, and having it do both would make it needlessly complex. You could add a new interface controller to the end of the list, but what would it look like when your users are not on a run? Furthermore, do you really want your users inspecting their run logs while they’re on the track? I, for one, would prefer they keep their eyes in front of them to avoid accidents. At my local gym, much of the track is on a second-floor balcony overlooking the basketball courts, and I certainly don’t want my users’ eyes glued to their wrist while they’re navigating those turns up there.
To avoid all of these problems, when a user starts a run, you’ll replace the user interface in its entirety. While a run is active, the lap timer will be the only screen accessible to the user. When the user stops a run, you’ll come back to the existing user interface. This kind of transition can’t be done in the storyboard, so you need to write some code.
Return to your Go Running interface controller in Interface.storyboard. Open Xcode’s Assistant Editor to GoRunningInterfaceController.swift and connect it to a new IBAction method in your GoRunningInterfaceController class by Control-dragging from the button to the implementation file. Call it startRunButtonPressed (to see how to connect an @IBAction this way, consult the figure).
To implement startRunButtonPressed, you need a reference to a RunTimerInterfaceController instance that will become the new interface. You can’t create it directly as you would other classes by calling RunTimerInterfaceController or some other initializer, though, since all WatchKit user interface objects—even interface controllers—must come from the storyboard. Instead, you have to reference it through its storyboard identifier. Open the storyboard, give it the identifier “RunTimer”, then return to GoRunningInterfaceController and use it:
| @IBAction func startRunButtonPressed() { |
| let names = ["RunTimer"] |
| |
| WKInterfaceController.reloadRootControllersWithNames(names, |
| contexts: nil) |
| } |
Once you have the identifier that the storyboard uses as the interface controller’s name, it’s straightforward to change your interface. Notice that the first parameter of reloadRootControllersWithNames(_:contexts:) is an Array. This allows you to pass multiple names and switch to a page-based layout. You simply provide the controllers in the order you’d like them to appear. For your needs, you just pack the one name into an array and pass it along. Don’t worry about the contexts parameter yet; you’ll see more about passing data later in this chapter.
Build and run the app; then press the Start Run button. You’ll be whisked away magically to your RunTimerInterfaceController. The page indicator at the bottom of the screen is gone, because you’re now viewing only the one interface controller, just as you wanted. Unfortunately, there’s no cancel button, nor can you swipe to go back. You need to provide your users a way to go back to where they started, and it’ll have to be in code. You’ll probably want to do this when the user ends a run, so let’s give them a way to do just that. Modify the endRun method accordingly:
| func endRun() { |
| let names = ["GoRunning", "RunLog"] |
| |
| WKInterfaceController.reloadRootControllersWithNames(names, |
| contexts: nil) |
| } |
Be sure to go into Interface.storyboard and give these interface controllers the identifiers “GoRunning” and “RunLog,” respectively. Tapping End Run will now take you back to where you began. As you can see, when you need to transition between interface controllers in code, it can get a bit more complicated than when you do it in the storyboard, but either way gets you where you need to go.
You’re not quite finished here, however. When the user taps End Run, she goes back to the Go Running screen because that’s the first interface controller in your list. It’s pretty unlikely that the user will want to go running again immediately after finishing, so wouldn’t it be more useful to jump directly to the run log? Let’s see how to do exactly that.
18.217.40.118