Windows Bridge for iOS: Customizing the Surface Dial Experience

Hesam Seyed Mousavi, December 23, 2016

hesam_seyed_mousavi_porting-ios-apps-to-windows

Source: dotnetsharper

blog.mousavi.fr

In part one of the Windows Bridge for iOS series, we created a simple to-do list app in Xcode and used the iOS bridge to bring it over to Windows 10. In part two of the series, we went on a tour of Visual Studio for iOS developers. In part three, we used the Windows Bridge for iOS to convert an iOS calculator app created using Storyboards and Auto Layout to a Universal Windows Platform app that adjusts to various form factors on Windows 10. In part four, we discussed how to mix and match UIKit with XAML controls in a bridged UWP app.

Today, we explain how to customize the Surface Dial Experience for bridged UWP apps using Objective-C projections.

Getting Started

For today’s tutorial, you will need:

If you don’t have a Windows 10 PC, you can download one of our pre-built evaluation virtual machines from the Windows Bridge for iOS website. Download the package for your preferred virtualization environment and you’ll be up and running in no time.

Understanding the Project Structure

With the radial control sample initial code downloaded and unzipped on your Mac development environment, open the Xcode project and briefly explore the application. The application consists of:

  • AppDelegate – contains the UIApplicationInitialStartupMode Category that ensures the app scales properly on Windows 10 for a variety of form factors.
  • ViewController – provides the view management infrastructure for the app’s user interface, which consists of a slider, a label that is updated with the value of the slider and a switch.

Build and run the application in the simulator and move the slider to make sure everything is properly set up.

Using vsimporter

You are now ready to run the app through the vsimporter tool. To do so, you’ll need to copy your Xcode project files to your Windows Machine (or VM). With the files copied, follow the steps under the Using vsimporter section of part three of the blog series. Once you’re done, return to your radial control project directory, which should now contain a brand new Visual Studio solution file.

Open the Visual Studio solution by double-clicking on the RadialControlSample-WinStore10.sln file and build and run the application on your local machine by clicking on the Run button on the top menu. You’ll notice the same UI we saw running in the Xcode Simulator — you now have a native iOS app running on Windows. Try moving the slider and you’ll see its value label change.

Adding a New Menu Item to the Radial Controller Tool

You will now update this app to add a custom menu item to the radial controller tool that will respond to rotate and click actions by changing the slider value and toggling the switch.

To implement radial controller features in your app, you will need the public headers for the relevant UWP frameworks. In the bridge SDK you downloaded, go to the include\Platform\Universal Windows\UWP directory and take a look at what you find. Each header file represents a different namespace within the Windows Runtime APIs. For our purposes, you will need APIs from Windows.UI.Input since the radial controller is an input device and all of its classes are contained in that namespace.

To include this framework – and make sure it’s only included when the code is being run on Windows – start by adding an #ifdef and the #import macros to the top of the view controller implementation file:

1
2
3
4
5
#import "ViewController.h"
  
  #ifdef WINOBJC
  #import <UWP/WindowsUIInput.h>
  #endif

To interact with the radial controller, you will need to add a property to the view controller of your app that will allow you to access it. In C++, wheel input devices are represented by the RadialController class. However, as you build using Objective-C projections, you will notice that the standard naming scheme for these objects has been modified to match Objective-C conventions, where classes are prefixed with the letters that constitute their containing namespace:

  • Windows.UI.Input.RadialController becomes WUIRadialController

As a result, add a WUIRadialController property to the @interface section of the view controller implementation file:

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface ViewController()
 @property UILabel *demoTitle;
 @property UILabel *demoInfo;
 @property UISlider *slider;
 @property UILabel *sliderLabel;
 @property UISwitch *switchControl;
 #ifdef WINOBJC
 @property WUIRadialController* radialController;
 #endif
 @end

Next, you need to get a reference to the WUIRadialController object with the CreateForCurrentView method as explained in the RadialController class documentation. Looking at the WindowsUIInput.h header you’ll find the equivalent Objective-C projection under the WUIRadialController class interface:

1
2
3
@interface WUIRadialController : RTObject
[...]
+ (WUIRadialController*)createForCurrentView;

Call this method at the end of the viewDidLoad method of the view controller file to instantiate the WUIRadialController property:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [...]
  #ifdef WINOBJC   
    // Create a reference to the radial controller
    self.radialController = [WUIRadialController createForCurrentView];
  #endif
  }

Now you need to get a reference to the radial controller menu and its items. This is done via the Menu property of the RadialController class that returns a RadialControllerMenu object. Looking back at the  WindowsUIInput.h header, you’ll find the equivalent Objective-C property under the WUIRadialController class interface that returns a WUIRadialControllerMenu object:

1
2
3
@interface WUIRadialController : RTObject
  [...]
  @property (readonly) WUIRadialControllerMenu* menu;

Call this property to get a reference to the radial controller menu:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
    [...]
  #ifdef WINOBJC   
    // Create a reference to the radial controller
    self.radialController = [WUIRadialController createForCurrentView];
    // Get the radial controller menu
    WUIRadialControllerMenu* menu = self.radialController.menu;
  #endif
  }

The menu items are accessible via the Items property of the RadialControllerMenu class. As before, the interface of the WUIRadialControllerMenu class in the WindowsUIInput.h header gives you the equivalent Objective-C property:

1
2
3
@interface WUIRadialControllerMenu : RTObject
[...]
@property (readonly) NSMutableArray* /* WUIRadialControllerMenuItem* */ items;

Call this property to get a reference to the menu items:

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad {
  [...]
  // Get the radial controller menu
  WUIRadialControllerMenu* menu = self.radialController.menu;
  // Get the menu items
  NSMutableArray* menuItems = menu.items;
#endif
}

Next, you need to create a new RadialControllerMenuItem object to add to the menu with the projection of the CreateFromKnownIcon class method:

1
2
@interface WUIRadialControllerMenuItem : RTObject
  + (WUIRadialControllerMenuItem*)createFromIcon:(NSString *)displayText icon:(WSSRandomAccessStreamReference*)icon;

Call this method to create the new menu item:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
    [...]
    // Get the menu items
    NSMutableArray* menuItems = menu.items;
    // Create a new menu item
    // To use your own custom icon for the menu item, use the createFromIcon method instead
    WUIRadialControllerMenuItem* newMenuItem = [WUIRadialControllerMenuItem createFromKnownIcon:@"Custom Tool" value:WUIRadialControllerMenuKnownIconRuler];
  #endif
  }

Note that we reused an existing icon for our tool from the RadialControllerMenuKnownIcon enumeration, but you can create your own and use the CreateFromIcon method instead.

Finally, add your new menu item to the menu items array:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
  [...]
  // Create a new menu item
  // To use your own custom icon for the menu item, use the createFromIcon method instead
  WUIRadialControllerMenuItem* newMenuItem = [WUIRadialControllerMenuItem createFromKnownIcon:@"Custom Tool" value:WUIRadialControllerMenuKnownIconRuler];
#endif
  // Add a new menu item
  [menuItems addObject:newMenuItem];
}

That’s it! Now build and run your application and press and hold the Surface Dial to see the new menu item appear.

Adding a Handler for Click Input

In this section, you will add a handler for click input that will toggle the application switch control if the radial controller is clicked when the new tool you added to the menu is selected. Taking a look at the WindowsUIInput.h header, you’ll see you need the addButtonClickedEvent: method:

1
2
3
@interface WUIRadialController : RTObject
   [...]
   - (EventRegistrationToken)addButtonClickedEvent:(void(^)(WUIRadialController*, WUIRadialControllerButtonClickedEventArgs

Since the callback relies on Objective-C blocks, you need to mark the self reference with the __block keyword before using it to access the switch to avoid creating a retain cycle. Add the following code at the end of the viewDidLoad method to do this:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
  [...]
  // Add a new menu item
  [menuItems addObject:newMenuItem];
  __weak ViewController* weakSelf = self; // Ensures self will not be retained
}

Now you can safely toggle the switch in the radial controller click callback:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
   [...]
   __weak ViewController* weakSelf = self; // Ensures self will not be retained
   // Add a handler for click input from the radial controller
   [self.radialController addButtonClickedEvent:^(WUIRadialController* controller, WUIRadialControllerButtonClickedEventArgs* args)
    {
        [weakSelf.switchControl setOn:!(weakSelf.switchControl.on) animated:YES];
    }];
 }

You can now build and run your application, select the new menu item, and click on the radial controller to see the switch toggle.

Adding a Handler for Rotation Input

In this section, you will add a handler for rotation input that will move the application slider control if the radial controller is rotated when the new tool you added to the menu is selected. Taking a look at the WindowsUIInput.h header, you’ll see that you need the addRotationChangedEvent: method:

1
2
3
@interface WUIRadialController : RTObject
 [...]
 - (EventRegistrationToken)addRotationChangedEvent:(void(^)(WUIRadialController*, WUIRadialControllerRotationChangedEventArgs

As for the click event handler, simply call the method and update the slider value in the callback block:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad {
  [...]
  __weak ViewController* weakSelf = self; // Ensures self will not be retained
  [...]
  // Add a handler for rotation input from the radial controller
  [self.radialController addRotationChangedEvent:^(WUIRadialController* controller, WUIRadialControllerRotationChangedEventArgs* args)
   {
       [weakSelf.slider setValue:(weakSelf.slider.value + ([args rotationDeltaInDegrees]/360.0f)) animated:YES];
   }];
}

That’s it! Now build and run your application, select the new menu item and rotate the radial controller to see the slider value change.

Wrapping Up

Thanks for following along! You can download the complete Radial Control sample for the final project. Take a look at the following resources for more information:

 

Source: dotnetsharper

blog.mousavi.fr

Advertisements