Let’s write something harder than Hello, world for Apple Watch
By: Heads
Tags: iOS, Applewatch, Swift, Apple

Apple release of their new product Apple Watch happened a long time ago. New Watch OS 2.0 was also launched. Still, there are little articles written about how to code something more complex than ‘Hello, world!’ for Apple Watch. We decided to resolve the case and create an app consisting of several screens with list, data load and interop with the main app.

In all the articles we found about coding for Apple Watch there were detailed work principles described, the schemes were drawn, all the pros and cons were covered. That is why we decided to skip the theory in favor of practice. Let’s start!

The one thing necessary to mention is that app is being launched on the phone, while the watch is just showing its’ user interface. On the whole, you can grasp the main idea from this pic from Apple documentation. Of course, if you’ve interested in development for Apple Watch, we assume you to see this picture quite a lot.

Now we can start working. Our first step is to create a WatchKit Extension for our app. It’s quite easy to do: in Target list you click on + button and in the menu opened find the WatchKit App. On the next screen you need to check that you’re using your main app, all the BundleID are written correctly. After these small actions you can click Finish.

After these easy manipulations you’ll have two new Targets in your project: extension for your main app and the exact app for Watches.

Moreover, there will be two new folders in the project tree (for extension and the app accordingly). In app folder you can find familiar Interface.storyboard.

Here you can find all familiar controllers that inherited WKInterfaceController class, all the UI components (actually, there is still a small number of them) and all transitions between Segue screens.

Main thing to consider is that here you have 3 entries instead of one that we had on iOS (Initial controller). It’s main screen, notifications and glance (another type of notifications).

In our main controller let’s add a button (drag it from right-bottom of Object Library). Here you’ll see another feature of Apple Watch — the interface was simplified greatly and all components can be placed in one way only: one after another from top to bottom. If you need to place components in a row, you need to use Group component. It’s a container of different components placed together under the one Layout parameter (vertical or horizontal one).

Moreover, component placed inside the parent container can be aligned both horizontally and vertically or given the absolute and relative size.

On the whole, we have a simple concept that covers the big part of our needs.

Let’s add several buttons to the main screen and one transition to the next screen when one of the buttons is clicked. On the next screen we’ll create a list. Transitions between screens are similar to iOS and are done with a help of segue. Still, there is one interesting feature here: on the Connections Inspector tab you’ll see Next page property. By connecting this controller with another one you can create transitions between pages by swiping similar to Page View Controller ones.

On the next screen we create the list of data that loads from the network. There is no UITableViewController analogue in Watches, because everything is inherited from one and only WKInterfaceController. That is why we simply convey the Table component on our screen and bind it to the outlet property of controller. In such way we can create TableRow prototype automatically.

Let’s look at the inner parts of the controller. By default there are three methods created. Let’s look closer at all of them.

First is the method on screen initialisation. Here the data from all other screens goes with a help of context parameter. It’s a place where you need to initialize data and load it.

- (void)awakeWithContext:(id)context {

[super awakeWithContext:context];

}

Second method is summoned after the screen showing. Here you can launch animations and make some simple data updates.

- (void)willActivate {

[super willActivate];

}

The last one is a method of screen unloading where you can clean, save and delete.

- (void)didDeactivate {

[super didDeactivate];

}

Let’s try filling up our list with data in awakeWithContext method. All you need to do is to write down the class of list box and inherit it in the same manner as with regular NSObject. Here how it looks:

@interface WKRow : NSObject @property (weak, nonatomic) IBOutlet WKInterfaceLabel *rowTitle; @end

In InterfaceBuilder we put the class of the box and bind it to the properties of UI components. Also, in the InterfaceBuilder box we need to show Identifier on which reference it’ll be created.

Afterwards, the code that adds data into the table will look like this:

[self.table setNumberOfRows:items.count withRowType:@”itemRow”];

for (Item *itm in items){

WKRow *row = [self.table rowControllerAtIndex:i];

[row.rowTitle setText:itm.title];

}

setNumberOfRows creates the necessary number of boxes with right identificator from IB in the table. Later we can address every box separately with rowControllerAtIndex method that will return us our controller box’ class.

Some time ago the was a common bug that made program to malfunction when setNumberOfRows interface is called often. In the current updates this bug was fixed.

Now it’s time to face a new question — can we take the data uploaded to the main app to the Extension without additional loads?

There is one basic thing to consider — communication of WatchKit app with the parent iOS app. The idea is simple: you send requests to the parent app, it finds th data in its’ database, load it from the network and return it back to us. The work is organised with the help of openParentApplication controller method. As a parameter we take the dictionary with action parameter. In reply we insert the parameter that processes the reply.

[WBridgeInterfaceController openParentApplication:@{

@”action”:@”remind”

}

reply:^(NSDictionary *replyInfo, NSError *error){

NSLog(@”Result: %@”, [replyInfo objectForKey:@”result”]);

}];

In delegate app of parent app you can reidentify the handleWatchKitExtensionRequest method. It gives us another dictionary that was sent from the Watches. By processing it we can return the result to the block as we did with the help of dictionary.

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply{

[BridgeReminder setRemind];

reply(@{

@”result”: @”ok”

});

}

After loading the data we can handle the tap by the list rows and show the next screen, e.g. the one with the detailed information.

The simplest way to open a screen by tapping the row is to open Storyboard and bundle row selection component with controller matching the place you want to go in the Connection Inspector. Later, we need to input Identifier in the Attribute Inspector to process the transition and data.

In the controller itself we re-identify the contextForSegueWithIdentifier method that opens with the segue start. In there we return the sign to data we need to process to the next screen after checking the transition with the identificator. It can be done so easily:

- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier inTable:(WKInterfaceTable *)table rowIndex:(NSInteger)rowIndex{

if ([segueIdentifier isEqualToString:@”openCard”]){

return [items objectAtIndex:rowIndex];

}

return nil;

}

In the next screen controller we can load the data in the well-known awakeWithContext method and then use it whenever we like, e.g. show it on screen.

- (void)awakeWithContext:(id)context {

[super awakeWithContext:context];

Item *b = context;

[self.itemTitle setText: b.title];

}

While I was writing this article I found one strange feature in Xcode — if you’re writing Segue identificator with capital letters, than you cannot catch it with contextForSegueWithIdentifier method later. It happens because such identificator will not be saved and the transition won’t have IDE generated.

There are several more qualities of the development that I saw for the first time. For example, some pods are not able for usage in extensions. AFNetworking wrote that it doesn’t know what the [UIApplication sharedApplication] is, because extension doesn’t have such class at all. Such problem can be solved easily with adding an

AF_APP_EXTENSIONS=1 parameter in build macross.

Let’s call it a day now. Try writing your own Apple Watch app now.