April 2018

Volume 33 Number 4

[Visual Studio for Mac]

Programming for watchOS with Xamarin and Visual Studio for Mac

By Dawid Borycki

Small wearable devices like personal activity trackers and smart watches (such as Microsoft Band, Android Wear or Apple Watch) are becoming more and more popular. These wearables are equipped with various sensors, which monitor wearer’s health parameters in real time. Many wearables also have communication interfaces, so sensor data can be easily transmitted to custom or dedicated cloud services (such as Microsoft Health) for storage or advanced processing. As a result, the wearable can act as an additional endpoint in an Internet of Things (IoT) ecosystem. This, in turn, can help advance personal health care to a new level, where IoT predictive algorithms can inform the user about emerging health issues in advance.

Wearables can also run custom apps. Developers are provided with dedicated SDKs. However, as is the case for many mobile devices, each platform has its own specific API, which can be accessed through platform-specific programming languages and tools. To make things easier, Xamarin provides support for Android Wear and watchOS within the Xamarin.Android and Xamarin.iOS libraries, respectively. You can develop wearable apps in a similar manner as mobile apps, by utilizing the common .NET code base, which is referenced in the platform-specific projects.

In this article, I’ll show you how to utilize such an approach to build the watchOS app depicted in Figure 1. When you run this app, it begins retrieving the collection of objects from the REST Web service. This collection is composed of fake photos, each of which has a title and a bitmap (single color image). At this stage, the app only shows one button with the caption Get list. This button is disabled until the data is downloaded, as shown in the first row of Figure 1.

A Preview of the watchOS App
Figure 1 A Preview of the watchOS App

Tap the button and an action sheet (second row of Figure 1) appears displaying an alert that’s composed of several buttons, defined as actions or action buttons (bit.ly/2EEUZpL). In this example, the action sheet provides the action buttons, whose captions contain the range of photos to be displayed. When you tap an action, the selected photos are displayed in the table control (bit.ly/2Caq0nM) just below the Get list button as shown in the last row in Figure 1. This table is scrollable, so you can scroll down the list to see all photos in the group.

I’ll implement the communication with the Web service in a separate .NET Standard Class Library (bit.ly/2HArfMq). Following the referenced documentation, .NET Standard is a formal specification of the .NET APIs designed to accomplish uniform access to programming interfaces available on all .NET implementations. One of the main advantages of this approach is that a shared library can be easily referenced in .NET projects to reduce or eliminate conditional compilation of the shared code. As a result, the .NET Standard Class Library can be implemented once and then referenced in various .NET projects, targeting Universal Windows Platform (UWP), .NET Core, ASP.NET Core, Xamarin.iOS, Xamarin.Android, Xamarin.Forms and so on without the need to recompile for each platform.

Here, I’ll show how to reuse common code in the watchOS app. For the Web service I’ll use the fake REST API server JSONPlaceholder (jsonplaceholder.typicode.com), which provides several resources, including the photos resource employed in the sample app. This resource stores a collection of fake pictures, each of which is represented as the following JSON object:

{
  "albumId": 1,
  "id": 1,
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "https://placehold.it/600/92c952",
  "thumbnailUrl": "https://placehold.it/150/92c952"
}

Each photo has an associated id, photo album, title and two URLs pointing to a bitmap and its thumbnail. For this exercise, the bitmap is just a one-color image with a label showing the bitmap dimensions.

Everything you see here is created using Visual Studio for Mac. You can find the complete sample code for this project on GitHub at github.com/dawidborycki/Photos.

Parent App and Shared Code

The structure of a typical watchOS solution comprises three projects (see apple.co/2GwXhrn and bit.ly/2EI2dNO). The first is the parent iOS app. Two other projects are dedicated to the watch app: Watch app bundle and WatchKit extension bundle. The parent iOS app is used as a proxy to deliver watch bundles to the wearable. The Watch app bundle contains interface storyboards. As is the case with iOS, developers use interface storyboards to define scenes and segues (transitions) between them. Finally, the WatchKit extension bundle contains resources and the app code.

Let’s start by creating the parent iOS app using the new Single View iOS project, which can be found in the New Project creator of Visual Studio for Mac. I set the project and solution names to Photos.iOS and Photos, respectively. After creating the project, I supplement the Photos solution by adding another project Photos.Common, which I create using the .NET Standard Library project template. This template is located under the Multiplatform | Library group of the New Project creator.

When you create the .NET Standard Library you’re given an option to choose the version of .NET Standard. This version determines the available API (the higher the version, the more functionality you can access) and supported platforms (the higher the version, the fewer supported platforms). Here, I set .NET Standard version to 2.0, which already includes the HttpClient class to communicate with the Web service over HTTP. You can retrieve the list of APIs for each .NET Standard version using the .NET API browser at bit.ly/2Fl44Fa.

After setting up the common project, I install one NuGet package—Newtonsoft.JSON—which will be used to deserialize HTTP responses. To install the NuGet package in Visual Studio for Mac, you proceed as you would in Visual Studio for Windows. In the Solution Explorer, right-click Dependencies | NuGet node and from the context menu choose Add Packages. Visual Studio displays a window, in which you search for packages. Alternatively, you can use Package Console and install the NuGet package from the command line.

REST Client

Now I’m ready to implement the client class for the Photos Web service. To simplify deserialization, I map the JSON object, shown previously, to a C# class. This can be done manually or with a dedicated tool. Here, I’ll use JSONUtils (jsonutils.com). This Web site has an intuitive interface that consists of the following elements:

  • Class Name textbox where you enter your class name
  • JSON Text or URL textbox, in which you place your JSON code or its URL
  • Several radio buttons, which let you choose your language (C#, VB.NET and so on)
  • Two checkboxes: Add Namespace and Pascal Case

To generate the C# class, I set the Class Name to Photo and paste the following URL for the JSON Text or URL textbox: jsonplaceholder.typicode.com/photos/1. Finally, I enable the Pascal Case checkbox and click the Submit button. The generated Photo class now appears at the bottom of the page. Photo class (see companion code: Photos.Common/Model/Photo.cs) does not require additional comments—it’s just composed of the auto-implemented properties, representing the JSON object shown previously.

To implement the REST client, I create the static PhotoClient class (Photos.Common project). This class has one field of type HttpClient. This field is instantiated within the static constructor to set the BaseAddress property such that it points to the JSONPlaceholder URL, as shown here:

private static HttpClient httpClient;
static PhotosClient()
{
  httpClient = new HttpClient()
  {
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
  };
}

As Figure 2 shows, the PhotosClient has two public methods:

  • GetByAlbumId: Achieves the collection of photos from the given album using the GetAsync method of the HttpClient class. After checking the HTTP response status code (CheckStatusCode), the resulting response is deserialized to a collection of C# Photo objects using a generic helper method DeserializeResponse (helper methods are discussed later).
  • GetImageData: Retrieves the byte array, representing the photo from the provided URL. To get the image data I use GetByteArrayAsync of the HttpClient class instance.

Figure 2 Public Methods of the PhotoClient Class

public static async Task<IEnumerable<Photo>> GetByAlbumId(int albumId)
{
  var response = await httpClient.GetAsync($"photos?albumId={albumId}");
  var photoCollection = new List<Photo>();
  try
  {
    CheckStatusCode(response);
    photoCollection = await DeserializeResponse<List<Photo>>(response);
  }
  catch(Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  return photoCollection;
}
public static async Task<byte[]> GetImageData(Photo photo)
{
  var imageData = new byte[0];
  try
  {
    Check.IsNull(photo);
    imageData = await httpClient.GetByteArrayAsync(photo.ThumbnailUrl);
  }
  catch(Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  return imageData;
}

PhotosClient also implements two private methods: Check­StatusCode and DeserializeResponse. The first method accepts an instance of the HttpResponseMessage class and checks the value of its IsSuccessStatusCode property, throwing an exception if the status code was different than 200 (success code).

private static void CheckStatusCode(HttpResponseMessage response)
{
  if (!response.IsSuccessStatusCode)
  {
    throw new Exception($"Unexpected status code: {response.StatusCode}");
  }
}

The second method, DeserializeReponse, also accepts an instance of the HttpResponseMessage class, but reads the message body as a string using HttpResponseMessage.Content.ReadAsStringAsync method. The resulting value is then passed to the static DeserializeObject method of the Newtonsoft.Json.JsonConvert class. The latter returns the C# object of the given type, as shown here:

private static async Task<T> DeserializeResponse<T>(HttpResponseMessage response)
{
  var jsonString = await response.Content.ReadAsStringAsync();
  return JsonConvert.DeserializeObject<T>(jsonString);
}

In Figure 2 I also use the IsNull method of the custom Check class. IsNull method performs simple argument validation to check whether argument is null. If so, an exception of type ArgumentNullException will be thrown (see companion code: Photos.Common/Helpers/Check.cs).

The shared functionality is now ready, so I can proceed to imple­ment the watchOS app.

The watchOS App and Its Structure

To create the actual watchOS app I first right-click the solution name (in this case Photos) under the Solution Explorer and then choose Add | Add New Project from the context menu. A New Project dialog appears, in which I click the following tab: watchOS | App (make sure you don’t use an Extension or Library tab). A list of project templates appears, from which I choose WatchKit App C# project template. Afterward, the list of configurable options will be presented, as shown in Figure 3.

Configuring the watchOS App
Figure 3 Configuring the watchOS App

As mentioned previously, every watchOS app has an associated parent iOS app. In this instance it’s the Photos.iOS app. Then, in the App Name textbox I type WatchKit, set the Target to watchOS 4.2 (note that the particular items in this list will depend on the installed SDK version) and uncheck all checkboxes under the Scenes group.

After clicking the Next button, Visual Studio will create two additional projects: Photos.iOS.WatchKit and Photos.iOS.WatchKitExtension.

The first project (WatchKit) contains the storyboard, which you use to define scenes and segues (transitions) between them. The second project (WatchKitExtension) contains associated logic, including view controllers that in the watchOS are called interface controllers. So, you typically modify the UI of the watch app using storyboard designer (shown in Figure 4), which is activated by double-clicking the Interface.storyboard file of the WatchKit app.

Designing the UI of the watchOS App
Figure 4 Designing the UI of the watchOS App

The storyboard designer lets you drag and drop controls from the Toolbox onto the scenes. In Figure 4 I have just one scene, which is associated with the InterfaceController class, defined under the Photos.iOS.WatchKit­Extension project. This class is like the UIViewController for an iOS app—that is, it presents and manages content on the screen and implements methods that handle user interactions. Note that once controls are added to the scene, you can modify their properties using the Properties pad and then access them from the code through the InterfaceController class.

Before editing the UI, let’s briefly investigate the structure of this class, which derives from the WatchKit.WKInterfaceController—the base class for all interface controllers. A default implementation of the InterfaceController overrides three methods related to the view lifecycle (see apple.co/2GwXhrn):

  • Awake: Invoked by the system right after the InterfaceController is initialized. You typically use this method to load data and prepare the UI.
  • WillActivate: Invoked when the associated view is about to become active. You use this method to prepare final updates right before the interface will be displayed.
  • DidDeactivate: Called when the view becomes inactive. You typically use this method to release dynamic resources, which aren’t needed anymore.

These methods are used to configure your view, depending on its visibility. To give you a simple example, I’ll analyze the following Awake method:

public override void Awake(
  NSObject context)
{
  base.Awake(context);
  SetTitle("Hello, watch!");
}

This code first invokes the Awake method of the base class (WKInterfaceController) and then calls the SetTitle method, which changes the string displayed in the top-left corner of the view. This title is a default element of each interface controller.

To test this modification, you can execute the Photos.iOS.WatchKit app in the simulator. First, you need to set this app (not the extension bundle) as the startup project, using a dropdown from the toolbar in Visual Studio for Mac. Next to that list you have two other dropdown lists—one for selecting the configuration (Debug or Release), and the other for selecting from a list of simulators. Here, I’m using the Debug configuration and the Apple Watch Series 3 – 42 mm – watchOS 4.2 emulator. After choosing the simulator and clicking the Play icon, the app compiles and deploys. Note that two simulators are launched: iOS simulator and its paired watchOS simulator (refer back to the left screen in Figure 1).

Action Sheet

Now I can move forward to implement the actual UI of the Photos.iOS.WatchKit app. As shown in Figure 1, the UI of the app comprises three elements: a button, an action sheet and a table view. When the user taps the button, an action sheet is activated. It presents several options, which let the user choose the group of photos to be displayed in the table view. I implemented this photo grouping to comply with Apple’s guidance to limit the number of rows in the table view as a way to improve app performance (apple.co/2Cecrnt).

I’ll start by creating the list of buttons, which will be displayed in the action sheet. The list of these buttons is generated based on the photo collection (photos field) retrieved from the Web service as shown in Figure 5. Each action button is represented as an instance of the WatchKit.WKAction class. This class lacks any public constructors but implements the static Create method, which you use to create actions. As shown in Figure 5, the Create method accepts three arguments:

  • Title defines the action button caption
  • Style indicates the style of action button
  • Handler specifies a method to be executed when the user taps the action button

Figure 5 Partitioning Photo Titles

private const int rowsPerGroup = 10;
private IEnumerable<Photo> photos;
private WKAlertAction[] alertActions;
private void CreateAlertActions()
{
  var actionsCount = photos.Count() / rowsPerGroup;
  alertActions = new WKAlertAction[actionsCount];
  for (var i = 0; i < actionsCount; i++)
  {
    var rowSelection = new RowSelection(
      i, rowsPerGroup, photos.Count());
    var alertAction = WKAlertAction.Create(
      rowSelection.Title,
      WKAlertActionStyle.Default,
      async () => { await DisplaySelectedPhotos(rowSelection); });
    alertActions[i] = alertAction;
  }
}

In Figure 5 all actions have a default style, represented as the Default value from the WatchKit.WKAlertStyle enumeration. This enumeration defines two other values (apple.co/2EHCAZr): Cancel and Destructive. You use the first one to create an action that cancels an operation without any changes. The destructive style should be applied to actions that produce irreversible modifications.

The CreateAlertActions method from Figure 5 partitions the photos into chunks, each of which contains 10 elements (rowsPerGroup constant). To get a selected group of photos from the collection, I need two indices—one where the group begins (beginIndex variable) and one where it ends (endIndex). To calculate these indices, I use the RowSelection class, which is also used to create titles for items displayed in the action sheet. RowSelection class is implemented in the Photos.iOS.WatchKitExtension project (RowSelection.cs) and its most important parts are shown in Figure 6.

Figure 6 Calculating Indices with RowSelection Class

public int BeginIndex { get; private set; }
public int EndIndex { get; private set; }
public int RowCount { get; private set; }
public string Title { get; private set; }
private static string titlePrefix = "Elements to show:";
public RowSelection(int groupIndex, int rowsPerGroup, int elementCount)
{
  BeginIndex = groupIndex * rowsPerGroup;
  EndIndex = Math.Min((groupIndex + 1) * rowsPerGroup, elementCount) - 1;
  RowCount = EndIndex - BeginIndex + 1;
  Title = $"{titlePrefix} {BeginIndex}-{EndIndex}";
}

Finally, each button of the action sheet has an associated handler, which invokes the DisplaySelectedPhotos method. This method is responsible for presenting the table of selected photos and will be described later.

To activate the action sheet I first reference the Photos.Common project. To do so, in Solution Explorer I right-click References of the Photos.iOS.WatchKitExtension, select Edit References, choose the Project tab and select Photos.Common. Once in the Reference Manager I also need to reference Newtonsoft.Json.dll library to ensure that it will be copied to the output directory. I do this using the .NET Assembly tab, clicking the Browse button and then choosing Newtonsoft.Json.dll from the folder packages/Newtonsoft.Json/lib/netstandard20. This folder is created after installing the Newtonsoft.Json NuGet package.

These steps are required to access a shared code base (including PhotosClient that was implemented earlier) from the watchOS app. I then modify the UI using the storyboard. A detailed description of how layout works in watchOS can be found in Apple (apple.co/2FlzADj) and Xamarin documentation (bit.ly/2EKjCRM).

After opening the storyboard designer, I drag the Button control from the Toolbox onto the scene. Using the Properties pad, I set the button’s Name and Title properties to ButtonDisplayPhotoList and Get list, respectively. Then, I create the event handler, which executes whenever the user taps the button. To create an event handler, I use the Properties pad, clicking the Events tab and then typing ButtonDisplayPhotoList_Activated in the Action search box. After pressing the enter key, Visual Studio declares the new method in the InterfaceController class. Finally, the Button­DisplayPhotoList_Activated is defined as follows:

partial void ButtonDisplayPhotoList_Activated()
{
  PresentAlertController(string.Empty,
    string.Empty,
    WKAlertControllerStyle.ActionSheet,
    alertActions);
}

To create and present an action sheet, I use PresentAlertController. This method accepts four arguments:

  • Title indicates the title of the alert.
  • Message specifies the text to be displayed in the alert’s body.
  • PreferredStyle specifies the style of the alert controller. Style is represented by one of the values, defined in the Watch­Kit.WKAlertControllerStyle enumeration: Alert, SideBy­SideButtonsAlert or ActionSheet. Differences between them are summarized at apple.co/2GA57Rp.
  • Actions are a collection of action buttons to be included in the alert. Note that the number of actions depends on the alert style as described in the referenced documentation.

Here, both title and message are set to string.Empty, while the alert style is set to ActionSheet. As a result, only action buttons will be displayed (refer back to Figure 1). To ensure that alertActions are ready before the user taps the Get list button, I retrieve photos and titles within the Awake method (as shown in Figure 7):

Figure 7 Retrieve Photos and Titles

public async override void Awake(NSObject context)
{
  base.Awake(context);
  SetTitle("Hello, watch!");
  // Disable button until the photos are downloaded
  ButtonDisplayPhotoList.SetEnabled(false);
  // Get photos from the web service (first album only)
  photos = await PhotosClient.GetByAlbumId(1);
  // Create actions for the alert
  CreateAlertActions();
  ButtonDisplayPhotoList.SetEnabled(true);
}

You can now run the app. When the photo album is retrieved from the remote server, the button will be enabled. Click it to activate the action sheet (shown in the middle part of Figure 1).

Table View

To complete this implementation, I need to create the table view showing the selected group of photos. To that end I open the storyboard designer and from the Toolbox drag the Table control onto the scene (I’ll place it just below the Get list button).

In the next step, I need to define the cell layout. By default, this layout has a single control: Group. I can use this as the parent for other controls, but first I need to ensure that the group control is active. So, I click the Document Outline pad (visible in the bottom part of Figure 4) and then click Group Control under the Table/Table Row. Next, I drag Image and Label controls onto the table. Image and Label will appear in the table view and also in the Document Outline. I’ll configure all control properties as follows: Image (Name: ImagePhotoPreview; Size: Fixed Width and Height of 50 px), Label (Name: LabelPhotoTitle), Table (Name: TablePhotos), Group (Size: Fixed height of 50 px).

It’s important to explicitly set control names so you can easily refer to them from the code. Once you specify the control name, Visual Studio makes an appropriate declaration under the InterfaceController.designer.cs file.

In watchOS every row has a dedicated row controller, which you use to control row appearance. Here, I’ll use this controller to specify content for the image and label of each row. To create the row controller, select the Table Row in the Document Outline pad, and then open the Properties tab, where you type PhotoRowController in the Class textbox. A new file, PhotoRowController.cs, will be added. It contains the class of the same name. I then supplement the definition of this class by another method, as follows:

public async Task SetElement(Photo photo)
{
  Check.IsNull(photo);
  // Retrieve image data and use it to create UIImage
  var imageData = await PhotosClient.GetImageData(photo);
  var image = UIImage.LoadFromData(NSData.FromArray(imageData));
  // Set image and title
  ImagePhotoPreview.SetImage(image);
  LabelPhotoTitle.SetText(photo.Title);
}

The SetElement function accepts one argument of type Photo to display a photo thumbnail along with photo title in the appro­priate row of table view. Then, to actually load and configure table rows, I extend the definition of the InterfaceController using the following method:

private async Task DisplaySelectedPhotos(RowSelection rowSelection)
{
  TablePhotos.SetNumberOfRows(rowSelection.RowCount, "default");
  for (int i = rowSelection.BeginIndex, j = 0;
    i <= rowSelection.EndIndex; i++, j++)
  {
    var elementRow = (PhotoRowController)TablePhotos.GetRowController(j);
    await elementRow.SetElement(photos.ElementAt(i));
  }
}

RowSelection is passed to DisplaySelectedPhotos method in order to provide necessary information about the rows to be displayed. More specifically, RowCount property is used to set the number of rows to be added to the table (TablePhotos.SetNumberOfRows). Subsequently, DisplaySelectedPhotos iterates through table rows to set the content for each row. At each iteration I first obtain a reference to the PhotoRowController associated with the current row. Given that reference I invoke the PhotoRowController.SetElement method in order to get image data and title, which are displayed in the table cell.

Finally, after running the app, you’ll get the results shown previously in Figure 1.

Wrapping up

In this article, I showed how to develop watchOS apps with Xamarin, Visual Studio for Mac and a shared C# .NET code base implemented within the .NET Standard Class Library. Along the way I explored some of the most important elements of watchOS apps, including the app structure, interface controllers and selected UI controls (button, action sheet, table view). Because the shared code base is implemented using the same approach employed with mobile apps, you can easily extend your mobile solution to target smart wearables. You can get more info on watchOS from the Apple at apple.co/2EFLeaL, as well as detailed Xamarin documentation at bit.ly/2ohSwLU.


Dawid Borycki is a software engineer and biomedical researcher, author and conference speaker. He enjoys learning new technologies for software experimenting and prototyping.

Thanks to the following technical expert for reviewing this article: Brad Umbaugh
Brad Umbaugh writes iOS and related documentation for the Xamarin team at Microsoft. Catch up with him on Twitter @bradumbaugh, where he primarily retweets bad puns.


Discuss this article in the MSDN Magazine forum