January 20, 2016 2:00 pm

Building a Simple App with the Windows Bridge for iOS

By / Program Manager

bridges_for_iOS
Welcome to the first in a series of hands-on blog posts for the Windows Bridge for iOS. The Windows Bridge for iOS is an open-source project that allows you to create Universal Windows Platform (UWP) apps that can run on Windows 10 devices using iOS APIs and Objective-C code.

Today, we’re going to build a simple to-do list app in Xcode and use the Windows Bridge for iOS to bring it over to Windows 10, keeping all of the code in a single codebase so the project is completely portable between platforms. If you want to follow the complete, end-to-end journey, you’ll need:

  • A PC running Windows 10, with Visual Studio 2015 and the Windows Bridge for iOS installed. You can download Visual Studio from the Windows Dev Center and find the latest release of the bridge on GitHub here.
  • A Mac running Mac OS X 10.11 with Xcode 7 installed. If you want to run the iOS project on an actual iOS device, you’ll also need a paid Apple Developer account.

If you don’t have a 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 – the package already includes Windows 10, Visual Studio 2015 and the iOS bridge.

If you don’t have a Mac but are curious about developing in Objective-C on Windows 10, you’ll still be able to download the source code, go through the conversion process and edit the code in Visual Studio.

Building a to-do list app in Xcode

First, download the initial to-do list project, which can be found here. Open up the ToDo.xcodeproj file and let’s examine the project structure.

1 - Storyboard

In the Storyboard editor, we have a single UINavigationController as our root view controller and a UITableViewController that will be our main screen. Since the whole app is a single view, a navigation controller isn’t strictly necessary, but it leaves room for extensibility if you would like to experiment with taking the project further.

We’ve built most of the app programmatically, so the only other item of note is the “Clear” UIBarButtonItem, which has an IBAction outlet in the clearAllTodos: method in TDTableViewController.

Now let’s take a look at the classes in the left sidebar in Xcode:

  • TDItem – this is our barebones data structure for holding to-do list items.
  • TDTableViewController – this is where most of the logic of our app lies. This class inherits from UITableViewController and manages creating new to-do items and displaying in-progress and completed to dos.
  • TDTableViewCell – this class inherits from UITableViewCell and provides the layout for both in-progress and archived to dos. It uses a pan gesture recognizer to add the swiping functionality and keeps a reference to its currently displayed TDItem. Its delegate is its parent table view controller, which is notified when a cell is swiped left (to delete a to do) or right (to archive a to do).
  • TDInputTableViewCell – this class also inherits from UITableViewCell and is used to display the input field for adding new to dos. Like TDTableViewCell, its delegate is its parent table view controller which is notified when a new to do is added.
  • TDLabel – finally, TDLabel inherits from UILabel and simply provides a mechanism for having a thick strikethrough through its text.

Go ahead and run the app in the iOS simulator in Xcode, and you’ll see our app starts up and runs nicely:

2 - Xcode Simulator

Try adding a few to-do items and swiping right to archive an item and left to delete it. If you quit the simulator and relaunch, you’ll notice your list disappears; we’ll examine methods of persisting data across sessions once we bring the app over to Windows.

Now copy the project directory onto a thumb drive and open it on your Windows machine. (If you’re using a Mac with a virtual machine, you can also just copy the project to a shared directory that is accessible from both the Mac and Windows sides.)

Next, let’s turn our Xcode project into a Visual Studio solution.

Using vsimporter

On your Windows machine, open up the winobjc directory and navigate to winobjc/bin. Inside, you’ll find a file called vsimporter. Vsimporter is a command-line tool that turns an Xcode project file into a Visual Studio solution. It automatically handles Storyboards and Xibs, although Visual Studio does not currently have a Storyboard editor, so any changes to our Storyboard have to be made on the Mac side. (This is why we built most of the layout programmatically.)

In a separate window, open your to-do list project directory in file explorer. Select File > Open command line prompt and you’ll see a command line window appear. Drag the vsimporter file located in winobjc/bin on top of the command line window and you should see its full path appear. With the command line window in focus, hit Enter, and then return to your to-do list project directory, which should now contain a brand new Visual Studio solution file.

Using Visual Studio and the iOS bridge

Double click the new Visual Studio solution file that was just created and Visual Studio 2015 will launch. In the Visual Studio Solution Explorer sidebar, you’ll see the top-level solution file, which you can expand to see the familiar class structure we had in Xcode. Header files are stored in their own directory in Visual Studio, but otherwise the structure should look the same.

3 - VS Explorer

Hit F5 to run the app, wait for it to compile, and voila!

4 - VS Simulator

Our iOS app is running natively on Windows 10 using Objective-C.

The first thing you’ll notice is the app doesn’t scale properly. Windows 10 runs on a wide variety of form factors with different screen sizes, so to ensure a good user experience, your app should be aware of, and respond to, the configuration it’s being run on. To accomplish this, we’re going to create a Category for our app in our app delegate called UIApplicationInitialStartupMode.

In the Solution Explorer, double click AppDelegate.m. Beneath the very first #import, add the following code:


#ifdef WINOBJC

@implementation UIApplication (UIApplicationInitialStartupMode)

// Let WinObjC know how to render the app
+ (void) setStartupDisplayMode:(WOCDisplayMode*)mode
{
    mode.autoMagnification = TRUE;
    mode.sizeUIWindowToFit = TRUE;
    mode.fixedWidth = 0;
    mode.fixedHeight = 0;
    mode.magnification = 1.0;
}

@end

#endif

Here, we’re using the #ifdef and #endif preprocessor directives to check to see if the WINOBJC symbol is defined, giving us the ability to include Windows-specific code. This keeps the codebase portable, since the Windows-specific code will simply be ignored if we go back to Xcode and run the app on iOS.

For a full description of the properties of the WOCDisplayMode object (autoMagnification, sizeUIWindowToFit, fixedWidth, etc), see the Using the SDK section of our project wiki on GitHub.

Now hit F5 again to run the app and you should see the to-do list app properly and responsively render. Go ahead and add a few to dos and–

Uh oh! Looks like we found a bug:

5 - VS Simulator Bug

What to do when you find unsupported iOS API calls

With a little digging, we quickly find that we hit bugs when adding new to dos and archiving them. In both cases, we’re using UITableView’s beginUpdates and endUpdates instance method calls, which allow us to edit the underlying data structure and insert and move around rows in our table view and guarantees the validity of the whole transaction. A quick look at the runtime log shows that these methods aren’t supported in the iOS bridge:

6 - VS Log

What to do?

First, make sure you file a bug on GitHub. GitHub is the best way to get in touch with our team to let us know what tools you need. If you find unimplemented APIs, features you’d like to see, or bugs anywhere in the bridge, please let us know.

Next, we can use the same preprocessor directives we used to fix the app rendering problems to create workarounds specifically for this use case. Open up TDTableViewController.m in Visual Studio and let’s tweak the toDoItemDeleted:, toDoItemCompleted:, and toDoItemAdded: methods:


- (void)toDoItemDeleted:(id)todoItem
{ 
#ifdef WINOBJC
	[_toDoItems removeObject:todoItem];
    [self.tableView reloadData];
#else
	NSUInteger index = [_toDoItems indexOfObject:todoItem];
    [self.tableView beginUpdates];
    [_toDoItems removeObject:todoItem];
    [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:TODO_SECTION]]
                          withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];
#endif
}

- (void)toDoItemCompleted:(id)todoItem
{ 
#ifdef WINOBJC
	[_toDoItems removeObject:todoItem];
    [_completedItems insertObject:todoItem atIndex:0];
    [self.tableView reloadData];
#else
	NSUInteger index = [_toDoItems indexOfObject:todoItem];
    [self.tableView beginUpdates];
    [_toDoItems removeObject:todoItem];
    [_completedItems insertObject:todoItem atIndex:0];
	[self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:TODO_SECTION]]
                          withRowAnimation:UITableViewRowAnimationLeft];
	[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:COMPLETE_SECTION]]
                          withRowAnimation:UITableViewRowAnimationLeft];
    [self.tableView endUpdates];
#endif
}

#pragma mark - TDInputTableViewCell delegate methods

- (void)toDoItemAdded:(TDItem*) todoItem
{ 
#ifdef WINOBJC
	[_toDoItems insertObject:todoItem atIndex:0];
    [self.tableView reloadData];
#else
    [self.tableView beginUpdates];
    [_toDoItems insertObject:todoItem atIndex:0];
    [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:TODO_SECTION]]
                          withRowAnimation:UITableViewRowAnimationTop];
    [self.tableView endUpdates];
#endif
}

This method lets us easily share code between an Xcode project and a Visual Studio solution. When running the app on iOS, we continue to use beginUpdates and endUpdates to manage inserting and moving cells, but on Windows we simply update the underlying data structure and call reloadData which forces the entire table view to rerender.

Hit F5 and your to-do list app should run without errors.

Persisting data

Now, a to-do list app isn’t all that much use if it can’t remember your to dos, and currently our app keeps everything in memory, so every time you launch it you have to start from scratch. We can do better.

Since we have such a simple use case, we can use property list serialization to store our to dos in a .plist file. This way, we can write out the file every time a to do is added, deleted or archived, and simply read the file on app load. (A more robust implementation would only write out the relevant change every time one is made, rather than the complete list of to dos, but for the sake of simplicity we’ll just write everything out after every change.)

Head back over to TDTableViewController.m and add the following methods at the bottom:


- (void)writeToDosToDisk
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
        
        NSMutableArray *allItems = [[NSMutableArray alloc] init];
        
        
        [_toDoItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            TDItem *item = obj;
            [allItems addObject:[item serialize]];
        }];
        
        [_completedItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            TDItem *item = obj;
            [allItems addObject:[item serialize]];
        }];
        
        NSArray *directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documents = [directories firstObject];
        NSString *filePath = [documents stringByAppendingPathComponent:@"todos.plist"];
        
        if([allItems writeToFile:filePath atomically:YES]) {
            NSLog(@"Successfully wrote to dos to disk.");
        }
        
    });
}

- (void)readToDosFromDisk
{
    NSArray *directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documents = [directories firstObject];
    NSString *filePath = [documents stringByAppendingPathComponent:@"todos.plist"];
    
    NSArray *loadedToDos = [NSArray arrayWithContentsOfFile:filePath];
    [loadedToDos enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSDictionary *dict = obj;
        NSString *string = [[dict allKeys] firstObject];
        BOOL complete = ((NSNumber*)[[dict allValues] firstObject]).boolValue;
        TDItem *toDo = [TDItem todoItemWithText:string isComplete:complete];
        
        if(toDo.completed) {
            [_completedItems addObject:toDo];
        }
        
        else {
            [_toDoItems addObject:toDo];
        }
    }];
    
    [self.tableView reloadData];
}

In order to store our custom TDItem object in a property list, we’ll need to convert it into an NSDictionary. Luckily, our TDItem implementation has a serialize method that does exactly that and returns an NSDictionary. What a coincidence!

Now, we simply need to update our toDoItemDeleted:, toDoItemCompleted:, toDoItemAdded:, and clearAllTodos: methods to call [self writeToDosToDisk] right before returning and add a call to [self readToDosFromDisk] at the end of viewDidLoad.

Press F5 again to run your app and your to dos will now be remembered across launches, so you’ll never forget anything at the grocery store again. The new app is completely portable across Windows 10 and iOS, so you can open your old Xcode project file up on your Mac and the app will continue to function exactly as expected.

Ready to try out your own app? Head over to GitHub to download the bridge.

Thanks for following along! You can download the complete to-do list Xcode project from our GitHub wiki, and be sure to check out the other posts in our series:






Updated February 18, 2016 4:25 pm

Join the conversation

  1. Hi. Great article.
    A question: is already possible to put iOS Bridged apps in the Windows Store?
    Thanks

    • Hi Luis, yes – it’s totally possible to publish into the Store now. There’s actually a few of them in the Windows Store already.

      • @Cliff, does this mean you’ve added the compiler optimization support – building in release mode and ARM support so the apps can run on phones?
        Are you allowed to give a name of a ported app that might be in the store today ?

        Thanks for the updates… definitely was looking for some movement in this space.

          • I was sort of hinting at that page with my question.
            In that list you can also see that “compiler optimization support” which translates to “no release builds”.
            So I was secretly hoping that list isn’t accurate anymore since some apps were published to the store – according to @Cliff
            Some clarifications would definitely be helpful.
            Thanks.

      • Any hope for CoreAudio support? We make extensive use of CoreAudio and without it we are sunk. I already filed an issue in Github and it’s sat there for two months with no activity.

        • CoreAudio is a big and technically complicated library. We’ve had a few requests for it so it’s on our radar, but we don’t have any immediate plans to include it in the bridge.

    • We don’t currently support CocoaPods since our clang compiler front end doesn’t support virtual filesystems, which CocoaPods depends on. However, this is a feature request we get a lot so it’s a top priority for us and we’re investigating options. You can check out our roadmap for the next month or two here:

      https://github.com/Microsoft/WinObjC/wiki/Roadmap

  2. Instead of building some android and ios app you can build important and necessary app for your mobile platform.
    How you can build a luncher app for android but can’t build good personalization app for windows phone??
    It’s really unbelievable that a great company in software developing can’t makes their platform good for us.
    This bridge is good and I hope other companies start to release their app for windows phone.