October 2012

Volume 27 Number 10

Windows Phone - Building an App for Both Windows Phone and iOS

By Andrew Whitechapel | October 2012

There’s plenty of documentation about porting apps from iOS to Windows Phone, but in this article, I start from the premise that you want to build a new app from scratch that targets both platforms. I make no value judgments about which platform is better. Instead, I take a practical approach to building the app, and describe the differences and similarities of both platforms encountered on the way.

As a member of the Windows Phone team, I am passionate about the Windows Phone platform—but my key point here is not that one platform is superior to the other, but that the platforms are different and require some different programming approaches. Although you can develop iOS apps in C#, using the MonoTouch system, that’s a minority environment. For this article, I use standard Xcode and Objective-C for iOS, and Visual Studio and C# for Windows Phone.

Target UX

My aim is to achieve the same UX in both versions of the app while ensuring that each version remains true to the model and philosophy of the target platform. To illustrate what I mean, consider that the Windows Phone version of the app implements the main UI with a vertically scrolling ListBox, whereas the iOS version implements the same UI with a horizontal ScrollViewer. Obviously, these differences are just software—that is, I could build a vertically scrolling list in iOS or a horizontally scrolling list in Windows Phone. Forcing these preferences would be less true to the respective design philosophies, however, and I want to avoid such “unnatural acts.”

The app, SeaVan, displays the four land border crossings between Seattle in the United States and Vancouver, British Columbia, in Canada, with the wait times at each of the different crossing lanes. The app fetches the data via HTTP from both U.S. and Canadian government Web sites and refreshes the data either manually via a button or automatically via a timer.

Figure 1presents the two implementations. One difference you’ll notice is that the Windows Phone version is theme-aware and uses the current accent color. In contrast, the iOS version doesn’t have a theme or accent-color concept.

The Main UI Screen for the SeaVan App on an iPhone and a Windows Phone Device
Figure 1 The Main UI Screen for the SeaVan App on an iPhone and a Windows Phone Device

The Windows Phone device has a strictly linear page-based navigation model. All significant screen UI is presented as a page, and the user navigates forward and backward through a page stack. You can achieve the same linear navigation on the iPhone, but the iPhone isn’t constrained by this model, so you’re free to apply whatever screen model you like. In the iOS version of SeaVan, the ancillary screens such as About are modal view controllers. From a technology perspective, these are roughly equivalent to Windows Phone modal popups.

Figure 2presents a schematic of the generalized UI, with inter­nal UI elements in white and external UI elements (launchers/­choosers in Windows Phone, shared applications in iOS) in orange. The settings UI (in light green) is an anomaly that I’ll describe later in this article.

Generalized Application UI
Figure 2 Generalized Application UI

Another UI difference is that Windows Phone uses an ApplicationBar as a standardized UI element. In SeaVan, this bar is where the user finds buttons to invoke ancillary features in the app—the About page and the Settings page—and to manually refresh the data. There’s no direct iOS equivalent to ApplicationBar, so in the iOS version of SeaVan, a simple Toolbar provides the equivalent UX.

Conversely, the iOS version has a PageControl—the black bar at the bottom of the screen, with four positional dot indicators. The user can scroll horizontally through the four border crossings, either by swiping on the content itself or by tapping the PageControl. In Windows Phone SeaVan, there’s no PageControl equivalent. Instead, the Windows Phone SeaVan user scrolls through the border crossings by swiping the content directly. One consequence of using a PageControl is that it’s easy to configure it so that each page is docked and completely visible. With the Windows Phone scrolling ListBox, there’s no standard support for this, so the user can end up with partial views of two border crossings. Both the ApplicationBar and the PageControl are examples of where I haven’t attempted to make the UX across the two versions any more uniform than it can be by just using standard behavior.

Architecture Decisions

The use of the Model-View-ViewModel (MVVM) architecture is encouraged in both platforms. One difference is that Visual Studio generates code that includes a reference to the main viewmodel in the application object. Xcode doesn’t do this—you’re free to plug your viewmodel into your app wherever you choose. In both platforms, it makes sense to plug the viewmodel into the application object.

A more significant difference is the mechanism by which the Model data flows through the viewmodel to the view. In Windows Phone, this is achieved through data binding, which allows you to specify in XAML how UI elements are associated with viewmodel data—and the runtime takes care of actually propagating values. In iOS, while there are third-party libraries that provide similar behavior (based on the Key-Value Observer pattern), there’s no data-binding equivalent in the standard iOS libraries. Instead, the app must manually propagate data values between the viewmodel and the view. Figure 3 illustrates the generalized architecture and components of SeaVan, with viewmodels in pink and views in blue.

Generalized SeaVan Architecture
Figure 3 Generalized SeaVan Architecture

Objective-C and C#

A detailed comparison between Objective-C and C# is obviously beyond the scope of a short article, but Figure 4 provides an approximate mapping of the key constructs.

Figure 4 Key Constructs in Objective-C and Their C# Equivalents

Objective-C Concept C# Counterpart
@interface Foo : Bar {} Class declaration, including inheritance class Foo : Bar {}

@implementation Foo

@end

Class implementation class Foo : Bar {}
Foo* f = [[Foo alloc] init] Class instantiation and initialization Foo f = new Foo();
-(void) doSomething {} Instance method declaration void doSomething() {}
+(void) doOther {} Class method declaration static void doOther() {}

[myObject doSomething];

or

myObject.doSomething;

Send a message to (invoke a method on) an object myObject.doSomething();
[self doSomething] Send a message to (invoke a method on) the current object this.doSomething();
-(id)init {} Initializer (constructor) Foo() {}
-(id)initWithName:(NSString*)n price:(int)p {} Initializer (constructor) with parameters Foo(String n, int p) {}
@property NSString *name; Property declaration public String Name { get; set; }
@interface Foo : NSObject Foo subclasses NSObject and implements the UIAlertViewDelegate protocol (roughly equivalent to a C# interface) class Foo : IAnother

Core Application Components

To start the SeaVan app, I create a new Single View Application in Xcode and a Windows Phone Application in Visual Studio. Both tools will generate a project with a set of starter files, including classes that represent the application object and the main page or view.

The iOS convention is to use two-letter prefixes in class names, so all the custom SeaVan classes are prefixed with “SV.” An iOS app starts with the usual C main method, which creates an app delegate. In SeaVan, this is an instance of the SVAppDelegate class. The app delegate is equivalent to the App object in Windows Phone. I created the project in Xcode with Automatic Reference Counting (ARC) turned on. This adds an @autoreleasepool scope declaration around all code in main, as shown here:

int main(int argc, char *argv[])
{
  @autoreleasepool {
    return UIApplicationMain(
    argc, argv, nil, 
    NSStringFromClass([SVAppDelegate class]));
  }
}

The system will now automatically reference-count the objects I create and automatically release them when the count goes to zero. The @autoreleasepool neatly takes care of most of the usual C/C++ memory management issues and brings the coding experience much closer to C#.

In the interface declaration for SVAppDelegate, I specify it to be a <UIApplicationDelegate>. This means that it responds to standard app delegate messages, such as application:didFinishLaunchingWith­Options. I also declare an SVContentController property. In SeaVan, this corresponds to the standard MainPage class in Windows Phone. The last property is an SVBorderCrossings pointer—this is my main viewmodel, which will hold a collection of SVBorderCrossing items, each representing one border crossing:

@interface SVAppDelegate : UIResponder <UIApplicationDelegate>{}
@property SVContentController *contentController;
@property SVBorderCrossings *border;
@end

When main starts, the app delegate is initialized and the system sends it the application message with the selector didFinishLaunching­WithOptions. Compare this with Windows Phone, where the logical equivalent would be the application Launching or Activated event handlers. Here, I load an Xcode Interface Builder (XIB) file named SVContent and use it to initialize my main window. The Windows Phone counterpart to a XIB file is a XAML file. XIB files are in fact XML files, although you normally edit them indirectly with the Xcode graphical XIB editor—similar to the graphical XAML editor in Visual Studio. My SVContentController class is associated with the SVContent.xib file, in the same way that the Windows Phone MainPage class is associated with the MainPage.xaml file.

Finally, I instantiate the SVBorderCrossings viewmodel and invoke its initializer. In iOS, you typically alloc and init in one statement to avoid the potential pitfalls of using uninitialized objects:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [[NSBundle mainBundle] loadNibNamed:@"SVContent" 
    owner:self options:nil];
  [self.window addSubview:self.contentController.view];
  border = [[SVBorderCrossings alloc] init];
  return YES;
}

In Windows Phone, the counterpart to the XIB-loading operation is normally done for you behind the scenes. The build generates this part of the code, using your XAML files. For example, if you unhide the hidden files in your Obj folder, in the MainPage.g.cs you’ll see the InitializeComponent method, which loads the object’s XAML:

public partial class MainPage : Microsoft.Phone.Controls.PhoneApplicationPage
{
  public void InitializeComponent()
  {
    System.Windows.Application.LoadComponent(this,
      new System.Uri("/SeaVan;component/MainPage.xaml",
      System.UriKind.Relative));
  }
}

The SVContentController class as my main page will host a scroll viewer, which in turn will host four view controllers. Each view controller will eventually be populated with data from one of the four Seattle-Vancouver border crossings. I declare the class to be a <UIScrollViewDelegate> and define three properties: a UIScrollView, a UIPageControl and an NSMutableArray of view controllers. The scrollView and pageControl are both declared as IBOutlet properties, which allows me to connect them to the UI artifacts in the XIB editor. The equivalent in Windows Phone is when an element in XAML is declared with an x:Name, generating the class field. In iOS, you can also connect your XIB UI elements to IBAction properties in your class, allowing you to hook up UI events. The Silverlight equivalent is when you add, say, a Click handler in XAML to hook up the event and provide stub code for the event handler in the class. Interestingly, my SVContentController doesn’t subclass any UI class. Instead, it subclasses the base class NSObject. It functions as a UI element in SeaVan because it implements the <UIScrollViewDelegate> protocol—that is, it responds to scrollView messages:

@interface SVContentController : NSObject <UIScrollViewDelegate>{}
@property IBOutlet UIScrollView *scrollView;
@property IBOutlet UIPageControl *pageControl;
@property NSMutableArray *viewControllers;
@end

In the SVContentController implementation, the first method to be invoked is awakeFromNib (inherited from NSObject). Here, I create the array of SVViewController objects and add each page’s view to the scrollView:

- (void)awakeFromNib
{   
  self.viewControllers = [[NSMutableArray alloc] init];
  for (unsigned i = 0; i < 4; i++)
  {
    SVViewController *controller = [[SVViewController alloc] init];
    [controllers addObject:controller];
    [scrollView addSubview:controller.view];
  }
}

Finally, when the user swipes the scrollView or taps the page control, I get the scrollViewDidScroll message. In this method, I switch the PageControl indicator when more than half of the previous/next page is visible. Then I load the visible page, plus the page on either side of it (to avoid flashes when the user starts scrolling). The last thing I do here is invoke a private method, updateViewFromData, which fetches the viewmodel data and manually sets it into each field in the UI:

- (void)scrollViewDidScroll:(UIScrollView *)sender
{
  CGFloat pageWidth = scrollView.frame.size.width;
  int page = floor((scrollView.contentOffset.x - 
    pageWidth / 2) / pageWidth) + 1;
  pageControl.currentPage = page;
  [self loadScrollViewWithPage:page - 1];
  [self loadScrollViewWithPage:page];
  [self loadScrollViewWithPage:page + 1];
  [self updateViewFromData];
}

In Windows Phone, the corresponding functionality is implemented in MainPage, declaratively in the XAML. I display the border-crossing times using TextBlock controls within the DataTemplate of a ListBox. The ListBox scrolls each set of data into view automatically, so the Windows Phone SeaVan has no custom code for handling scroll gestures. There’s no counterpart for the updateViewFromData method because that operation is taken care of via data binding.

Fetching and Parsing Web Data

As well as functioning as an app delegate, the SVAppDelegate class declares fields and properties to support fetching and parsing the crossing data from the U.S. and Canadian Web sites. I declare two NSURLConnection fields, for the HTTP connections to the two Web sites. I also declare two NSMutableData fields—buffers I’ll use to append each chunk of data as it comes in. I update the class to implement the <NSXMLParserDelegate> protocol, so as well as being a standard app delegate, it’s also an XML parser delegate. When XML data is received, this class will be called first to parse it. Because I know I’ll be dealing with two completely different sets of XML data, I’ll immediately hand off the work to one of two child parser delegates. I declare a pair of custom SVXMLParserUs/­SVXMLParserCa fields for this. The class also declares a timer for the auto-refresh feature. For each timer event, I’ll invoke the refreshData method, as shown in Figure 5.

Figure 5 Interface Declaration for SVAppDelegate

@interface SVAppDelegate : 
  UIResponder <UIApplicationDelegate, NSXMLParserDelegate>
{
  NSURLConnection *connectionUs;
  NSURLConnection *connectionCa;
  NSMutableData *rawDataUs;
  NSMutableData *rawDataCa;
  SVXMLParserUs *xmlParserUs;
  SVXMLParserCa *xmlParserCa;
  NSTimer *timer;
}
@property SVContentController *contentController;
@property SVBorderCrossings *border;
- (void)refreshData;
@end

The refreshData method allocates a mutable data buffer for each set of incoming data and establishes the two HTTP connections. I’m using a custom SVURLConnectionWithTag class that subclasses NSURLConnection because the iOS parser delegate model necessitates kicking off both requests from the same object, and all the data will come back into this object. So I need a way to differentiate between the U.S. and Canadian data coming in. To do this, I simply attach a tag to each connection and cache both connections in an NSMutableDictionary. When I initialize each connection, I specify self as the delegate. Whenever a chunk of data is received, the connectionDidReceiveData method is invoked, and I implement this to append the data to the buffer for that tag (see Figure 6).

Figure 6 Setting up the HTTP Connections

static NSString *UrlCa = @"https://apps.cbp.gov/bwt/bwt.xml";
static NSString *UrlUs = @"https://wsdot.wa.gov/traffic/rssfeeds/CanadianBorderTrafficData/Default.aspx";
NSMutableDictionary *urlConnectionsByTag;
- (void)refreshData
{
  rawDataUs = [[NSMutableData alloc] init];
  NSURL *url = [NSURL URLWithString:UrlUs];
  NSURLRequest *request = [NSURLRequest requestWithURL:url];   
  connectionUs =
  [[SVURLConnectionWithTag alloc]
    initWithRequest:request
    delegate:self
    startImmediately:YES
    tag:[NSNumber numberWithInt:ConnectionUs]];
    // ... Code omitted: set up the Canadian connection in the same way
}

I must also implement connectionDidFinishLoading. When all the data is received (for either of the two connections), I set this app delegate object as the first parser. The parse message is a blocking call, so when it returns, I can invoke updateViewFromData in my content controller to update the UI from the parsed data:

- (void)connectionDidFinishLoading:(SVURLConnectionWithTag *)connection
{
  NSXMLParser *parser = 
    [[NSXMLParser alloc] initWithData:
  [urlConnectionsByTag objectForKey:connection.tag]];
  [parser setDelegate:self];
  [parser parse];
  [_contentController updateViewFromData];
}

In general, there are two types of XML parsers:

  • Simple API for XML (SAX) parsers, where your code is notified as the parser walks through the XML tree
  • Document Object Model (DOM) parsers, which read the entire document and build up an in-memory representation that you can query for different elements

The default NSXMLParser in iOS is a SAX parser. Third-party DOM parsers are available for use in iOS, but I wanted to compare standard platforms without resorting to third-party libraries. The standard parser works through each element in turn and has no understanding of where the current item fits in the overall XML document. For this reason, the parent parser in SeaVan handles the outermost blocks that it cares about and then hands off to a child delegate parser to handle the next inner block.

In the parser delegate method, I do a simple test to distinguish the U.S. XML from the Canadian XML, instantiate the corresponding child parser and set that child to be the current parser from this point forward. I also set the child’s parent parser to self so that the child can return parsing control back to the parent when it gets to the end of the XML that it can handle (see Figure 7).

Figure 7 The Parser Delegate Method

- (void)connection:(SVURLConnectionWithTag *)connection didReceiveData:(NSData *)data
{
  [[urlConnectionsByTag objectForKey:connection.tag] appendData:data];
}
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
  qualifiedName:(NSString *)qName
  attributes:(NSDictionary *)attributeDict
{
  if ([elementName isEqual:@"rss"]) // start of US data
  {
    xmlParserUs = [[SVXMLParserUs alloc] init];
    [xmlParserUs setParentParserDelegate:self];
    [parser setDelegate:xmlParserUs];
  }
  else if ([elementName isEqual:@"border_wait_time"]) // start of Canadian data
  {
    xmlParserCa = [[SVXMLParserCa alloc] init];
    [xmlParserCa setParentParserDelegate:self];
    [parser setDelegate:xmlParserCa];
  }
}

For the equivalent Windows Phone code, I first set up a Web request for the U.S. Web site and for the Canadian Web site. Here I use a WebClient even though an HttpWebRequest is often more appropriate for optimal performance and responsiveness. I set up a handler for the OpenReadCompleted event and then open the request asynchronously:

public static void RefreshData()
{
  WebClient webClientUsa = new WebClient();
  webClientUsa.OpenReadCompleted += webClientUs_OpenReadCompleted;
  webClientUsa.OpenReadAsync(new Uri(UrlUs));
  // ... Code omitted: set up the Canadian WebClient in the same way
}

In the OpenReadCompleted event handler for each request, I extract the data that has been returned as a Stream object and hand it off to a helper object to parse the XML. Because I have two independent Web requests and two independent OpenReadCompleted event handlers, I don’t need to tag the requests or do any testing to identify which request any particular incoming data belongs to. I also don’t need to handle each incoming chunk of data to build up the overall XML document. Instead, I can sit back and wait until all the data is received:

private static void webClientUs_OpenReadCompleted(object sender, 
  OpenReadCompletedEventArgs e)
{
  using (Stream result = e.Result)
  {
    CrossingXmlParser.ParseXmlUs(result);
  }
}

For parsing the XML, in contrast to iOS, Silverlight includes a DOM parser as standard, represented by the XDocument class. So instead of a hierarchy of parsers, I can use XDocument directly to do all the parsing work:

internal static void ParseXmlUs(Stream result)
{
  XDocument xdoc = XDocument.Load(result);
  XElement lastUpdateElement = 
    xdoc.Descendants("last_update").First();
  // ... Etc.
}

Supporting Views and Services

In Windows Phone, the App object is static and available to any other component in the application. Similarly, in iOS, one UIApplication delegate type is available in the app. To make things easy, I define a macro that I can use anywhere in the app to get hold of the app delegate, cast appropriately to the specific SVAppDelegate type:

#define appDelegate ((SVAppDelegate *) [[UIApplication sharedApplication] delegate])

This allows me, for example, to invoke the app delegate’s refreshData method when the user taps the Refresh button—a button that belongs to my view controller:

- (IBAction)refreshClicked:(id)sender
{
  [appDelegate refreshData];
}

When the user taps the About button, I want to show an About screen, as shown in Figure 8. In iOS, I instantiate an SVAboutViewController, which has an associated XIB, with a scrolling text element for the user guide, as well as three additional buttons in a Toolbar.

The SeaVan About Screen in iOS and Windows Phone
Figure 8 The SeaVan About Screen in iOS and Windows Phone

To show this view controller, I instantiate it and send the current object (self) a presentModalViewController message:

- (IBAction)aboutClicked:(id)sender
{
  SVAboutViewController *aboutView =
    [[SVAboutViewController alloc] init];
  [self presentModalViewController:aboutView animated:YES];
}

In the SVAboutViewController class, I implement a Cancel button to dismiss this view controller, making control revert to the invoking view controller:

- (IBAction) cancelClicked:(id)sender
{
  [self dismissModalViewControllerAnimated:YES];
}

Both platforms offer a standard way for an app to invoke the functionality in built-in apps, such as e-mail, phone and SMS. The key difference is whether control is returned to the app after the built-in functionality returns, which always happens in Windows Phone. In iOS, this happens for some features but not others.

In the SVAboutViewController, when the user taps the Support button, I want to compose an e-mail for the user to send to the development team. The MFMailComposeViewController—again presented as a modal view—works well for this purpose. This standard view controller also implements a Cancel button, which does exactly the same work to dismiss itself and revert control to its invoking view:

- (IBAction)supportClicked:(id)sender
{
  if ([MFMailComposeViewController canSendMail])
  {
    MFMailComposeViewController *mailComposer =
      [[MFMailComposeViewController alloc] init];
    [mailComposer setToRecipients:
      [NSArray arrayWithObject:@"tensecondapps@live.com"]];
    [mailComposer setSubject:@"Feedback for SeaVan"];
    [self presentModalViewController:mailComposer animated:YES];
}

The standard way to get map directions in iOS is to invoke Google maps. The downside to this approach is that it takes the user out to the Safari shared application (built-in app), and there’s no way to programmatically return control to the app. I want to minimize the places where the user leaves the app, so instead of directions, I present a map of the target border crossing by using a custom SVMapViewController that hosts a standard MKMapView control:

- (IBAction)mapClicked:(id)sender
{   
  SVBorderCrossing *crossing =
    [appDelegate.border.crossings
    objectAtIndex:parentController.pageControl.currentPage];
  CLLocationCoordinate2D target = crossing.coordinatesUs;
  SVMapViewController *mapView =
    [[SVMapViewController alloc]
    initWithCoordinate:target title:crossing.portName];
  [self presentModalViewController:mapView animated:YES];
}

To allow the user to enter a review, I can compose a link to the app in the iTunes App Store. (The nine-digit ID in the following code is the app’s App Store ID.) I then pass this to the Safari browser (a shared application). I have no option here but to leave the app:

- (IBAction)appStoreClicked:(id)sender
{
  NSString *appStoreURL =
    @"https://itunes.apple.com/us/app/id123456789?mt=8";
  [[UIApplication sharedApplication]
    openURL:[NSURL URLWithString:appStoreURL]];
}

The Windows Phone equivalent of the About button is a button on the ApplicationBar. When the user taps this button, I invoke the NavigationService to navigate to the AboutPage:

private void appBarAbout_Click(object sender, EventArgs e)
{
  NavigationService.Navigate(new Uri("/AboutPage.xaml", 
    UriKind.Relative));
}

Just as in the iOS version, the AboutPage presents a simple user guide in scrolling text. There’s no Cancel button because the user can tap the hardware Back button to navigate back from this page. Instead of the Support and App Store buttons, I have Hyperlink­Button controls. For the support e-mail, I can implement the behavior declaratively by using a NavigateUri that specifies the mailto: protocol. This is sufficient to invoke the EmailComposeTask:

<HyperlinkButton 
  Content="tensecondapps@live.com" 
  Margin="-12,0,0,0" HorizontalAlignment="Left"
  NavigateUri="mailto:tensecondapps@live.com" 
  TargetName="_blank" />

I set up the Review link with a Click handler in code, and then I invoke the MarketplaceReviewTask launcher:

private void ratingLink_Click(object sender, 
  RoutedEventArgs e)
{
  MarketplaceReviewTask reviewTask = 
    new MarketplaceReviewTask();
  reviewTask.Show();
}

Back in the MainPage, rather than offer a separate button for the Map/Directions feature, I implement the SelectionChanged event on the ListBox so that the user can tap on the content to invoke this feature. This approach is in keeping with Windows Store apps, in which the user should interact directly with the content rather than indirectly via chrome elements. In this handler, I fire up a BingMapsDirectionsTask launcher:

private void CrossingsList_SelectionChanged(
  object sender, SelectionChangedEventArgs e)
{
  BorderCrossing crossing = (BorderCrossing)CrossingsList.SelectedItem;
  BingMapsDirectionsTask directions = new BingMapsDirectionsTask();
  directions.End =
    new LabeledMapLocation(crossing.PortName, crossing.Coordinates);
  directions.Show();
}

Application Settings

On the iOS platform, app preferences are managed centrally by the built-in Settings app, which provides a UI for users to edit settings for both built-in and third-party apps. Figure 9 shows the main Settings UI and the specific SeaVan settings view in iOS, and the Windows Phone settings page. There’s just one setting for SeaVan—a toggle for the auto-refresh feature.

Standard Settings and SeaVan-Specific Settings on iOS and the Windows Phone Settings Page
Figure 9 Standard Settings and SeaVan-Specific Settings on iOS and the Windows Phone Settings Pages

To incorporate settings within an app, I use Xcode to create a special type of resource known as a settings bundle. Then I configure the settings values by using the Xcode settings editor—no code required.

In the application method, shown in Figure 10, I make sure the settings are in sync and then fetch the current value from the store. If the auto-refresh setting value is True, I start the timer. The APIs support both getting and setting the values within the app, so I could optionally provide a settings view in the app in addition to the app’s view in the Settings app.

Figure 10 The Application Method

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSUserDefaults *defaults =
    [NSUserDefaults standardUserDefaults];
  [defaults synchronize];
  boolean_t isAutoRefreshOn =
    [defaults boolForKey:@"autorefresh"];
  if (isAutoRefreshOn)
  {
    [timer invalidate];
    timer =
      [NSTimer scheduledTimerWithTimeInterval:kRefreshIntervalInSeconds
        target:self
        selector:@selector(onTimer)
        userInfo:nil
        repeats:YES];
  }
  // ... Code omitted for brevity
  return YES;
}

In Windows Phone, I can’t add the app settings to the global settings app. Instead, I provide my own settings UI within the app. In SeaVan, as with the AboutPage, the SettingsPage is simply another page. I provide a button on the ApplicationBar to navigate to this page:

private void appBarSettings_Click(object sender, 
  EventArgs e)
{
  NavigationService.Navigate(new Uri("/SettingsPage.xaml", 
    UriKind.Relative));
}

In the SettingsPage.xaml, I define a ToggleSwitch for the auto-refresh feature:

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <toolkit:ToggleSwitch
    x:Name="autoRefreshSetting" Header="auto-refresh"
    IsChecked="{Binding Source={StaticResource appSettings},
    Path=AutoRefreshSetting, Mode=TwoWay}"/>
</StackPanel>

I have no choice but to provide settings behavior within the app, but I can turn this to my advantage and implement an AppSettings viewmodel for it and hook it up to the view via data binding, just as with any other data model. In the MainPage class, I start the timer based off the value of the setting:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
  if (App.AppSettings.AutoRefreshSetting)
  {
    timer.Tick += timer_Tick;
    timer.Start();
  }
}

Version Notes and Sample App

Platform versions:

  • Windows Phone SDK 7.1, and the Silverlight for Windows Phone Toolkit
  • iOS 5 and Xcode 4

SeaVan will be published to both the Windows Phone Marketplace and the iTunes App Store.

Not That Difficult

Building one app that targets both iOS and Windows Phone isn’t that difficult: the similarities are greater than the differences. Both use MVVM with an app object and one or more page/view objects, and the UI classes are associated with XML (XAML or XIB), which you edit with a graphical editor. In iOS, you send a message to an object, while in Windows Phone you invoke a method on an object. But the difference here is almost academic, and you can even use dot notation in iOS if you don’t like the “[message]” notation. Both platforms have event/delegate mechanisms, instance and static methods, private and public members, and properties with get and set accessors. On both platforms, you can invoke built-in app functionality and support user settings. Obviously, you have to maintain two codebases—but your app’s architecture, major component design and UX can be held consistent across the platforms. Try it—you’ll be pleasantly surprised!


Andrew Whitechapel has been a developer for more than 20 years and currently works as a program manager on the Windows Phone team, responsible for core pieces of the application platform. His new book is “Windows Phone 7 Development Internals” (Microsoft Press, 2012).

Thanks to the following technical experts for reviewing this article: Chung Webster and Jeff Wilcox