iPhone to Netbook Porting: Beyond the Basics
Beyond the Basics
iPhone development has grown rapidly because of the existence of the App Store but also because of the number of specialized features available on the device and the high-level interfaces Apple made available to use them. Now that netbooks are getting more popular, much of the hardware formerly relegated to smartphones is becoming available in the netbook world. In fact, almost every netbook sold now includes a camera — something rarely seen on a Windows PC a few years ago.
In the following snippets I’ll dive deeply into the hardware differences between the iPhone and netbooks and show you code samples for accessing those features in each platform (where and when available). In addition to this article there are several good resources in the ADP articles collection that discuss broader techniques used during porting. In his interview in the ADP article, “From iPhone to AppUp: Porting of ‘Smiles’”, Mike Kasprzack, CEO of Syhkronics, addresses many of the differences in hardware when discussing how he moved his iPhone game to AppUp.
The Apple iPhone OS Reference Library and Microsoft’s MSDN Library are rich resources for reference material, tutorials and community discussions on each platform.
Rotation and Accelerometer
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Screen Rotation |
||
|
Accelerometer |
The iPhone uses the onboard accelerometer to detect orientation changes. The iPhone API provides a two methods to allow and act on orientation changes. shouldAutorotateToInterfaceOrientation tells the framework whether it should automatically rotate the UI when it detects that the device is rotated. If you allow rotation, then the iPhone SDK fires didRotateFromInterfaceOrientation to allow your app to act on the orientation change.
iPhone Objective C
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
NSMutableString *newOrientation = [[[NSMutableString alloc] init] autorelease];
switch ([[UIDevice currentDevice] orientation]) {
case UIDeviceOrientationLandscapeLeft:
[newOrientation setString:@"LandscapeLeft"];
break;
case UIDeviceOrientationLandscapeRight:
[newOrientation setString:@"LandscapeRight"];
break;
case UIDeviceOrientationPortrait:
[newOrientation setString:@"Portrait"];
break;
default:
[newOrientation setString:@"(unknown)"];
break;
}
[self setDiagInfo:@"Orientation" value:newOrientation];
}
As Kasprzack says, “Locking the game to a single orientation on PC is the usual solution.” Some netbooks (notably tablets) support screen rotation and will notify the system when the screen is rotated.
You can detect screen rotation by assigning an event handler in C# (or other managed code) to the system event DisplaySettingsChanged as the following example Windows Presentation Foundation (WPF) application shows.
Windows XAML
<Window x:Class="Rotation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Rotation " Height="246" Width="431" Loaded="Window_Loaded" Unloaded="Window_Unloaded">
<Grid>
<Label Name="label1" Content="Orientation:" Height="28" Width="131"
HorizontalAlignment="Left" HorizontalContentAlignment="Right" VerticalAlignment="Top"
Margin="12,44,0,0" FontSize="14" FontWeight="Bold" />
<Label Name="labelOrientation" Content="(starting)" Height="28" Width="230"
HorizontalAlignment="Right" HorizontalContentAlignment="Left" VerticalAlignment="Top"
Margin="0,44,30,0" FontSize="14" FontWeight="Bold" />
</Grid>
</Window>
Windows C#
using System;
using System.Windows;
using Microsoft.Win32;
namespace Rotation
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void UpdateOrientation(object sender, EventArgs e)
{
double width = System.Windows.SystemParameters.PrimaryScreenWidth;
double height = System.Windows.SystemParameters.PrimaryScreenHeight;
string orientation;
if (height > width)
orientation = "Portrait";
else
orientation = "Landscape";
string description = orientation + " (" + width.ToString() + "x" + height.ToString() + ")";
labelOrientation.Content = description;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
SystemEvents.DisplaySettingsChanged += new EventHandler(UpdateOrientation);
UpdateOrientation(sender, e);
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
SystemEvents.DisplaySettingsChanged -= UpdateOrientation;
}
}
}
In C++ for native applications you listen for and respond to the WM_DISPLAYCHANGE message.
Windows C++
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
//. . .
//. . .
case WM_DISPLAYCHANGE:
{
//Separate the screen width and height and then
//check to see if screen width is greater than screen height
if ((LOWORD(lParam) > HIWORD(lParam))
//Run the application in landscape, for example:
MessageBox(NULL,"Run in landscape.","Landscape",MB_OK);
else
//Run the application in portrait, as in:
MessageBox(NULL,"Run in portrait.","Portrait",MB_OK);
}
break;
Complete details, including this code sample, can be found in the MSDN article, “Detecting Screen Orientation and Screen Rotation in Tablet PC Applications.”
If your app depends on the accelerometer as input, say, for a game, it may not be suitable to porting to a netbook as most netbooks do not include an accelerometer. However, as more tablets enter the market in mid- to late-2010, I expect that accelerometers will become more prevalent.
On the iPhone you can get accelerometer events by registering with the UIAccelerometer class as a delegate and consuming the events.
iPhone Objective C
-(void)viewDidLoad
{
[super viewDidLoad];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1.0 / 60.0];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
}
// UIAccelerometerDelegate method, called when the device accelerates.
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
// Do something with the data
// acceleration.x, acceleration.y, acceleration.z
}
Windows 7 has support for accelerometers as a type of “sensor” through Sensor API, a COM interface accessible from native C++ code. To access sensors through managed code you need to get the Windows API Code Pack from MSDN. Unlike the iPhone, Windows doesn't provide a high-level API that abstracts accelerometer data from distinct devices. Currently accelerometers are mostly found in handhelds and Windows Mobile supports two devices from HTC and Samsung, each through direct hardware interfaces1
To support accelerometers on tablets or netbooks, you’ll need to investigate the SDK or interfaces published by each manufacturer.
[1] For more details and source code see the unofficial Windows Mobile Unified Sensor API by Koushik Dutta.
From Touch to Mouse
Unless you are using gestures, mouses behave much like touch, with the caveat that mouse actions gives you a hover event, which touch cannot provide. So moving from touch-based interaction to mouse should be simple. Given the expectation of Windows users, you probably want to consider adding hover effects if you have custom UI components (as games often do). Like hyperlinks in web browsers, most Windows controls these days provide visual feedback on hover to notify the user if the item will respond to a click event.
If your application supports gestures or depends on some of the iPhone-specific gestures you may have to reconsider how to make this work on your Windows app.
Windows Touch
Tablets and touch-screen netbooks often do support multi-touch gestures and Windows 7 Touch has built in support for drag, scroll, zoom, flick, rotate and many more. There’s a good discussion of it in the MSDN article, “Getting Started with Windows Touch Gestures.” For non-touch devices, what follows are some approaches to handling these events.
Scroll
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Scrolling |
The scroll gesture on the iPhone works similarly to the mouse wheel supported by Windows applications, scroll bar controls and any controls with scrolling content. In a custom UI situation, you may need to capture the scrollwheel event to replicate this. For example, Google Maps in a web browser responds to the scroll wheel to zoom in or out on the map. In an iPhone application you probably don’t have to think about scrolling because it’s handled by UI components for you or you implemented a UIScrollView class.
Note that there is no equivalent of the ‘flick’ behavior in standard Windows controls, but you’d likely think of this as a page-up and page-down call on a scroll bar event or you could infer it from the acceleration of the scroll wheel event. You can capture the event by overriding the CWnd::OnMouseWheel method in C++/MFC or the Control.OnMouseWheel method in .NET
Windows C++
BOOL CWndEx::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
if( zDelta < 0 )
{
if( zDelta < -PAGE_DELTA ) {
// Do action to "scroll" down one "line"
} else {
// Do action to "scroll" down one "page"
}
}
else
{
if( zDelta < PAGE_DELTA ) {
// Do action to "scroll" up one "line"
} else {
// Do action to "scroll" up one "page"
}
}
return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}
Windows C#
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
if( e.Delta < 0 ) {
if( e.Delta < -PAGE_DELTA ) {
// Do action to "scroll" down one "line"
} else {
// Do action to "scroll" down one "page"
}
} else {
if( e.Delta < PAGE_DELTA ) {
// Do action to "scroll" up one "line"
} else {
// Do action to "scroll" up one "page"
}
}
}
Zoom
As there is no native support for pinch-zoom or a ‘zoom’ action on non-touch devices you’ll need to decide whether you need this action in your app. There could be other ways to provide similar zoom capability perhaps by adding controls to the user interface. Many Windows apps include a “View” menu with “Zoom In”, “Zoom Out” and “Normal Size” menu options. Commonly these are mapped to keystrokes Ctrl +, Ctrl - and Ctrl 0, respectively and Windows 7 has teh global Windows + and Windows - keystrokes that zoom the entire display.
Rotate
The two-finger rotate gesture that the iPhone recognizes is also not supported natively on non-touch devices for Windows. Again, commonly, Windows applications that allow rotation will include a “View” menu with items “Rotate Clockwise” and “Rotate Counterclockwise.”
Drag
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Drag and Drop |
While drag-and-drop isn’t natively supported in the iPhone (you have to implement it through the touchesMoved method), drag events are thoroughly supported in Windows API’s and well documented. There’s a 30-minute video on MSDN that describes how to implement drag and drop in your MFC application. For .NET see the MSDN article, “Performing Drag-and-Drop Operations on Windows” that provides the following example code:
Windows C#
private void button1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
button1.DoDragDrop(button1.Text, DragDropEffects.Copy |
DragDropEffects.Move);
}
private void textBox1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.None;
}
private void textBox1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
textBox1.Text = e.Data.GetData(DataFormats.Text).ToString();
}
Scaling Graphics
The screen size on a netbook is larger than an iPhone. While netbook screen sizes vary, the vast majority of them are 1024x600 pixels. To accommodate this larger screen resolution, and to accommodate varied screen resolutions, you may want to adjust the size and location of objects based on the window or screen size. If you are using WPF for your Windows app, you can have it auto-locate objects relative to each other and scale to fit the window. For graphics-rich apps, like games, Kasprzack, in his interview, suggested that you can scale graphics by starting with a reference resolution larger than any of your target resolutions. By building your images at this size you can scale down graphics appropriately to each platform. Additionally, any placement or movement of objects (such as, in games) can be scaled to to each device resolution from the reference resolution.
Location awareness/GPS
|
Quick Reference |
iPhone API |
Windows 7 API |
|---|---|---|
|
Location Services |
In iPhone apps, Apple provided the Core Location Framework that allows the developer to use the available hardware to locate the user and track changes to the user’s location, without regard to the underlying hardware. The LocateMe sample app in the iPhone reference library provides an example of gathering current location and getting notified of changes to location.
To get a single location on the iPhone you create a controller or delegate that implements CLLocationManagerDelegate
iPhone Objective C
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@interface LocationViewController : UIViewController <CLLocationManagerDelegate> {
UILabel *labelLatitude;
UILabel *labelLongitude;
UILabel *labelStatus;
CLLocationManager *locationManager;
}
@property(nonatomic,retain) IBOutlet UILabel *labelLatitude;
@property(nonatomic,retain) IBOutlet UILabel *labelLongitude;
@property(nonatomic,retain) IBOutlet UILabel *labelStatus;
@property(nonatomic,retain) CLLocationManager *locationManager;
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation;
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error;
@end
Then you define methods to start the location service, gather the result and handle any errors.
@implementation LocationViewController
@synthesize labelLatitude,labelLongitude,labelStatus,locationManager;
- (void)viewDidLoad {
[super viewDidLoad];
[labelStatus setText:@"Started"];
locationManager = [[[CLLocationManager alloc] init] autorelease];
locationManager.delegate = self;
[locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
[labelStatus setText:@"Got location"];
[labelLatitude setText:[NSString stringWithFormat:@"%.6f",newLocation.coordinate.latitude]];
[labelLongitude setText:[NSString stringWithFormat:@"%6f",newLocation.coordinate.longitude]];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
[labelStatus setText:[error description]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
}
- (void)dealloc {
[super dealloc];
}
@end
With Windows 7 Location Platform, this is now almost as easy to do on location-enabled netbooks. Note that many netbooks don’t currently have an ability to find the location because they don’t have cell-based wireless or GPS. There is a component forthcoming in the Component Catalog on the ADP site that will find the netbook’s location based on built-in GPS (or other localizing service) or go out to the web and try to determine the location based on the IP address of the device. You could also look at licensing products from Skyhook Wireless who provide a Wifi-based location service that are used in the iPod touch.
If you wish to add location support to your applications on your own, you can use the Windows Location API to detect location-identification devices and get data from them. Here is a snippet of code form the aforementioned component to get you started.
Windows C++
#include <LocationApi.h>
GeoLocation::GeoLocation() : Component(id)
{
//Setup COM Location interface
HRESULT hr;
GeoCOM = false;
LOCATION_REPORT_STATUS locationReportStatus = REPORT_NOT_SUPPORTED;
IID REPORTS[] = { IID_ILatLongReport, IID_ICivicAddressReport };
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
if(SUCCEEDED(hr))
{
hr = pLocation.CoCreateInstance(CLSID_Location);
if (SUCCEEDED(hr))
{
hr = pLocation->RequestPermissions(NULL, REPORTS, ARRAYSIZE(REPORTS), TRUE);
if (SUCCEEDED(hr))
{
hr = pLocation->GetReportStatus(IID_ILatLongReport, &locationReportStatus);
if (SUCCEEDED(hr))
{
switch (locationReportStatus)
{
case REPORT_NOT_SUPPORTED:
case REPORT_ERROR:
case REPORT_ACCESS_DENIED:
GeoCOM = false;
default:
GeoCOM = true;
}
}
}
}
}
http = new HTTPRequest();
locationData = new Location();
}
/** GeoLocation destructor
frees the current location data and HTTPRequest class.
*/
GeoLocation::~GeoLocation()
{
//tear down com interface
CoUninitialize();
if(locationData)
delete locationData;
locationData = NULL;
}
DWORD GeoLocation::GetCOMLocation()
{
HRESULT hr = ERROR_SUCCESS;
CComPtr<ILocationReport> pLocationReport;
CComPtr<ILatLongReport> pLatLongReport;
DOUBLE dValue;
hr = pLocation->GetReport(IID_ILatLongReport, &pLocationReport);
if(SUCCEEDED(hr))
{
if(pLocationReport)
hr = pLocationReport->QueryInterface(&pLatLongReport);
if(SUCCEEDED(hr))
{
hr = pLatLongReport->GetLongitude(&dValue);
if(SUCCEEDED(hr))
locationData->dLongitude = dValue;
hr = pLatLongReport->GetLatitude(&dValue);
if(SUCCEEDED(hr))
locationData->dLatitude = dValue;
hr = pLatLongReport->GetAltitude(&dValue);
if(SUCCEEDED(hr))
locationData->dAltitude = dValue;
hr = pLatLongReport->GetAltitudeError(&dValue);
if(SUCCEEDED(hr))
locationData->dAltitudeError = dValue;
hr = pLatLongReport->GetErrorRadius(&dValue);
if(SUCCEEDED(hr))
locationData->dErrorRadius = dValue;
}
}
return hr;
}
The Location API is also accessible from .NET and is much simpler. Here is a complete application for Windows using .NET and WPF that harnesses the GeoCoordinateWatcher class to update the display when the location changes.
Windows XAML
<Window x:Class="Location.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Location" Height="238" Width="400">
<Grid Height="177" Width="360">
<Label Name="label1" Content="Latitude:"
FontSize="18" FontWeight="Bold"
Margin="12,23,0,0"
HorizontalAlignment="Left" HorizontalContentAlignment="Right" VerticalAlignment="Top" Width="124" />
<Label Name="label2" Content="Longitude:"
FontSize="18" FontWeight="Bold"
Margin="12,63,0,0"
HorizontalAlignment="Left" HorizontalContentAlignment="Right" VerticalAlignment="Top" Width="124" />
<Label Name="label3" Content="Status:"
FontSize="18" FontWeight="Bold"
Margin="12,103,0,0"
HorizontalAlignment="Left" HorizontalContentAlignment="Right" VerticalAlignment="Top" Width="124" />
<Label Name="labelLatitude" Content=""
FontSize="18" FontWeight="Bold"
Margin="142,23,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top" Width="206" />
<Label Name="labelLongitude" Content=""
FontSize="18" FontWeight="Bold"
Margin="142,63,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top" Width="206" />
<Label Name="labelStatus" Content="(starting)"
FontSize="18" FontWeight="Bold"
Margin="142,103,12,0" VerticalAlignment="Top" />
</Grid>
</Window>
Windows C#
using System;
using System.Windows;
using System.Device.Location;
namespace Location
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
GeoCoordinateWatcher watcher = new GeoCoordinateWatcher();
watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(LocationUpdate);
if (watcher.TryStart(false, TimeSpan.FromMilliseconds(1000)))
labelStatus.Content = "Started";
else
labelStatus.Content = "Couldn't start location";
}
public void LocationUpdate(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
labelStatus.Content = "Got location";
labelLatitude.Content = e.Position.Location.Latitude.ToString();
labelLongitude.Content = e.Position.Location.Longitude.ToString();
}
}
}
Data storage
Almost all applications need to retain state between sessions. Some of that comes in the form of application settings (e.g. sound on/off) while other data storage may be larger (e.g. media files, game play state, collected data).
Small Data
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Settings |
The iPhone provides Preferences for storing the smaller configuration data, that includes a high-level API making it easy to store a simple named value for the current user on the current host.
iPhone Objective C
@implementation SmallDataViewController
@synthesize binarySetting,stringSetting;
// Load settings from NSUserDefaults and update the display
- (void)viewDidLoad {
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
[binarySetting setOn:[settings boolForKey:@"binarySetting"]];
[stringSetting setText:[settings stringForKey:@"stringSetting"]];
[super viewDidLoad];
}
// When the text box value is changed, update the settings and synchronize
- (IBAction) updateText:(id) sender {
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
[settings setObject:stringSetting.text forKey:@"stringSetting"];
[settings synchronize];
[sender resignFirstResponder];
}
// When the switch is changed, update the settings and synchronize
- (IBAction) switchValueChanged:(id) sender {
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
[settings setBool:binarySetting.on forKey:@"binarySetting"];
[settings synchronize];
}
@end
Windows provides similar high-level settings management in .NET applications, through the Application Settings. You can do this directly in Visual Studio through the designer or choose to write and code settings on your own as the following example shows.
Windows XAML
<Window x:Class="SmallData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SmallData" Height="182" Width="398"
>
<Grid>
<Label Name="label1" Content="Binary setting:"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="25,23,0,0"
FontSize="16" FontWeight="Bold" />
<Label Name="label2" Content="String setting:"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="27,78,0,0"
FontSize="16" FontWeight="Bold" />
<CheckBox Name="CBBinarySetting" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="164,32,0,0" />
<TextBox Name="TBStringSetting" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="164,84,0,0" Width="175" />
</Grid>
</Window>
Windows C#
using System;
using System.Configuration;
namespace SmallData
{
class MyUserSettings : ApplicationSettingsBase
{
[UserScopedSetting()]
[DefaultSettingValue("False")]
public bool binarySetting
{
get { return ((bool)this["binarySetting"]); }
set { this["binarySetting"] = (bool)value; }
}
[UserScopedSetting()]
[DefaultSettingValue("")]
public string stringSetting
{
get { return ((string)this["stringSetting"]); }
set { this["stringSetting"] = (string)value; }
}
}
}
Windows C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SmallData
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyUserSettings settings = new MyUserSettings();
TBStringSetting.Text = settings.stringSetting;
CBBinarySetting.IsChecked = settings.binarySetting;
CBBinarySetting.Checked += CBBinarySetting_Changed;
CBBinarySetting.Unchecked += CBBinarySetting_Changed;
TBStringSetting.TextChanged += TBStringSetting_TextChanged;
}
private void CBBinarySetting_Changed(object sender, RoutedEventArgs e)
{
MyUserSettings settings = new MyUserSettings();
settings.binarySetting = (bool)CBBinarySetting.IsChecked;
settings.Save();
}
private void TBStringSetting_TextChanged(object sender, TextChangedEventArgs e)
{
MyUserSettings settings = new MyUserSettings();
settings.stringSetting = TBStringSetting.Text;
settings.Save();
}
}
}
Larger Data
For larger data such as databases or files, you can store them locally on the iPhone in the dedicated storage set aside for an iPhone application. You use the NSSearchPathForDirectoriesInDomains function to locate the storage location and then use standard IO calls to read and write. If you need read-only access to a file or database, you can bundle it with the application as a Resource in Xcode.
Windows has less limitation on where files can be stored (some parts of the system are limited access on Windows 7). However, it’s considered a good practice these days to keep read-only files in the application’s program file folder, installed with the installation package. For writable files and user-collected or generated data, the common practice is to store it in the AppData folder for the application. There are a number of high-level API’s to help you manage different data storage cases.
Fortunately, if you you have included any data files in your iPhone app then likely you are parsing or accessing them in a similar way in Windows. For example, a SQLite database could would be read using the native C interfaces on the iPhone that can be copied directly to your Windows app and consumed as a native C library. If you are moving to .NET you can use the third-party System.Data.SQLite library to map your calls to .NET. As of version 2.6.3 the SQLite engine can read it’s database files even if they were created on a machine with a different endian.
Consuming web services
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
HTTP Services |
A fair number of iPhone apps connect to remote web services and consume data or post updates through them. These 'services' may be SOAP-base Web Services, but are more likely RESTful interfaces or even just HTTP connections that return and accept JSON, XML, HTML or structured data.
The following snippet uses the NSURLConnection class to read a URL and then uses the third-party json-framework for for Objective C to parse the response.
iPhone Objective C
- (void)viewWillAppear:(BOOL)animated {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
[NSURLConnection connectionWithRequest:request delegate:self]
}
...
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString* out = [[NSString alloc] initWithData:_receivedData encoding:NSUTF8StringEncoding];
if ( out ) {
SBJSON* jsonObject = [[SBJSON alloc] init];
NSError* error;
NSDictionary* outputDictionary = [jsonObject objectWithString:out error:&error];
// Do something with the JSON response data
} else {
// Give an error or warning message
}
[_receivedData release];
_receivedData = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog([NSString stringWithFormat:@"Request failed. Reason: %@", error.localizedDescription]);
// Give an error or warning message
[_receivedData release];
_receivedData = nil;
}
SOAP-based web services, while not natively supported in the iPhone SDK, can also be consumed in an iPhone app, using a third-party tool such as wsdl2objc that generates Objective C classes for accessing SOAP-based web services from a WSDL service definition.
Under native Windows the WinHTTP library provides access to a client for connecting to a web server. Because network interactions are error prone, you generally will want to include a good amount of error capturing, testing all your return values and acting appropriately and gracefully so that the user experience isn’t hampered excessively should any part of the HTTP request fail. That said, here is psuedo-code to get you started, that should be wrapped in error handling.
Windows C++
void HTTPRequest::Request(const std::wstring wsUrl,
const std::wstring wsHeaders,
const std::wstring wsContent,
std::string &sOutBuffer)
{
WinHttpCrackUrl( wsUrl.c_str(), wsUrl.length(), 0, &urlComponents );
Url.assign(urlComponents.lpszHostName, urlComponents.dwHostNameLength);
hConnect = WinHttpConnect( hSession, Url.c_str(), urlComponents.nPort, 0 );
hRequest = WinHttpOpenRequest(
hConnect,
"GET",
urlComponents.lpszUrlPath,
HTTP_VERSION.c_str(),
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
dwOpenFlags);
WinHttpSendRequest( hRequest,
wsHeaders.c_str(),
wsHeaders.length(),
WINHTTP_NO_REQUEST_DATA,
0,
0,
0 );
WinHttpReceiveResponse( hRequest, NULL );
GetResponseData( hRequest, sOutBuffer);
if(hRequest)
WinHttpCloseHandle(hRequest);
hRequest = NULL;
if(hConnect)
WinHttpCloseHandle(hConnect);
hConnect = NULL;
}
DWORD HTTPRequest::GetResponseData( HINTERNET hRequest, string &data)
{
DWORD dwSize;
DWORD dwStatus = ERROR_SUCCESS;
DWORD dwDownloaded;
LPSTR pszOutBuffer;
dwSize = 0;
do {
WinHttpQueryDataAvailable( hRequest, &dwSize);
pszOutBuffer = new char[dwSize+1];
ZeroMemory(pszOutBuffer, dwSize + 1);
WinHttpReadData( hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded);
if(pszOutBuffer)
{
data.append(pszOutBuffer);
delete [] pszOutBuffer;
pszOutBuffer = NULL;
}
} while (dwSize > 0);
if(pszOutBuffer)
delete [] pszOutBuffer;
pszOutBuffer = NULL;
return dwStatus;
}
Under .NET this is all much simpler with the HttpWebRequest class.
Windows C#
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlString); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Encoding encoding = Encoding.GetEncoding(1252); // Windows default Code Page StreamReader reader = new StreamReader(response.GetResponseStream(),encoding); string data = reader.ReadToEnd(); response.Close(); reader.Close();
Notice that in the native Windows C++ case the HTTP mechanisms is synchronous. The method call blocks continuation of the code until the response is received or the timeout period has expired. While the .NET example above also uses a synchronous approach, the class provides for asynchronous request/response. Under iPhone the call is asynchronous so you didn’t have to worry about threading. For performance and responsiveness you should always use asynchronous calls or put network requests into a separate thread (so they don’t block the user interface thread). This is covered in the next section.
Threading and Concurrency
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Threading |
Many of the long-running tasks that the iPhone library exposes are asynchronous. You declare a delegate, initiate an event and wait for a response. This leave the threading or concurrency of these issues to the API to handle, meaning faster programming and more stable code. Therefore, most iPhone developers don’t have to think about threading, although Apple does support both traditional threads and enhanced (and recommended) options such as operation objects and Grand Central Dispatch (GCD) on the iPhone.
When moving your applications to Windows you may have to implement your own threads for handling some events asynchronously. For example, an HTTP request should be handled on a separate thread so that the user interface is still responsive. You can launch that from an MFC app with the AfxBeginThread function.
Windows C++
typedef struct THREADINFOSTRUCT {
HWND hWnd;
bool bPush;
std::wstring strUrl;
int nApi;
} THREADINFOSTRUCT;
UINT __cdecl FnThreadedHttpRequest(LPVOID lParam) {
THREADINFOSTRUCT* tis = (THREADINFOSTRUCT*)lParam;
// Make the HTTP request and handle response
}
...
void App::CallApi(url) {
THREADINFOSTRUCT* tis = new THREADINFOSTRUCT;
tis->hWnd = hWnd;
tis->bPush = false;
tis->strUrl = url;
tis->nApi = API_LOGIN;
m_pHttpThread = AfxBeginThread(FnThreadedHttpRequest, tis, THREAD_PRIORITY_NORMAL, 0, 0);
}
If you are building a .NET application, this is also handled simply through the ThreadStart delegate among several approaches.
Windows C#
static void CallApi(string url)
{
ThreadedHttpRequest worker = new ThreadedHttpRequest();
worker.Push = false;
worker.Url = url;
worker.Api = API_LOGIN;
ThreadStart threadDelegate = new ThreadStart(worker.DoRequest());
Thread thread = new Thread(threadDelegate);
thread.Start();
}
class ThreadedHttpRequest
{
public boolean Push;
public string Url;
public int Api;
public void DoRequest() {
// Make Http request and handle response
}
}
Keep in mind that multi-threaded programming is complicated. When multiple threads access the same parts of code or make changes to shared data you can run into race conditions and concurrency issues. Threading is a powerful tool to improve responsiveness and effectiveness of your application as long as you carefully synchronize your code. Where asynchronous interfaces are provided, consider using them to improve thread-safety.
Contacts list
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Address Book |
Your iPhone customers have an address book with all their contacts stored in their phone. If your iPhone app includes social sharing capabilities you probably used this. With the address book and related services you can grab contact information to send mail, sms or share other information.
For example, to get the name of a selected contact you would use the ABPeoplePickerNavigationController class and implement the ABPeoplePickerNavigationControllerDelegate protocol in your class.
iPhone Objective C
@interface AddressBookQuickStartViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
IBOutlet UILabel *firstName;
IBOutlet UILabel *lastName;
}
@property (nonatomic, retain) UILabel *firstName;
@property (nonatomic, retain) UILabel *lastName;
- (IBAction) showPicker:(id) sender;
@end
@implementation AddressBookQuickStartViewController
@synthesize firstName;
@synthesize lastName;
-(IBAction) showPicker: (id) sender {
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
NSString* name = (NSString *)ABRecordCopyValue(person,
kABPersonFirstNameProperty);
self.firstName.text = name;
[name release];
name = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
self.lastName.text = name;
[name release];
[self dismissModalViewControllerAnimated:YES];
return NO;
}
See the Address Book UI Framework for more details. For direct access to the contact list you can use the Address Book Framework.
For your Windows applications, you could use the Windows Contacts API, but this has been deprecated in Windows 7, seemingly to be replaced with Windows Live Contacts API. You’ll need an active Internet connection to get access to the Live Contacts address book. The API uses a REST interface exchanging XML files. There is a sample application detailing this on MSDN.
Camera
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Camera |
Most netbooks ship with a built in camera. This is good news if you have built an iPhone app that uses the camera and are deciding how to port it to Windows. However, the camera on netbooks faces the users (often referred to in product specifications as a webcam). Compared to the camera on the back side of the iPhones (to date, that iPhone, iPhone 3G and iPhone 3GS) this means you need to think carefully about the user experience of your app on the new platform. For example, you can use the rear-facing camera on an iPhone to scan a bar code by holding the phone over the bar code. With a netbook you would need to hold the bar code up to the camera. The camera on a netbook can, however, be used for video conferencing or any other application where the user is taking a picture of him- or herself. This is not possible with the 3G generation of iPhones.
The iPhone lets you capture pictures from the camera and capture video (in 3.0 and later) with the UIImagePickerController class and implementing a UIImagePickerControllerDelegate protocol in your class.
iPhone Objective C
-(IBAction) recordVideo:(id) sender {
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
UIImagePickerController * picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeMovie];
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentModalViewController:picker animated:YES]
}
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissModalViewControllerAnimated:YES];
movieFileUrl = [info objectForKey:@"UIImagePickerControllerMediaURL"];
}
Under Windows you can use the DirectShow library to capture video content from the camera device and manipulate it in the process. You must first create a capture graph with which you configure the file stream and start the video render process.
Windows C++
IBaseFilter *pMux; hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi, L"C:\\Example.avi", &pMux, NULL); hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, NULL, pMux); pMux->Release();
For a more in-depth discussion see the Video Capture section of the DirectShow documentation on MSDN.
Playing media
Media, whether video or audio, are now prevalent and accessible from handheld devices over 3G networks. The iPhone provides video and audio playback and streaming support and has high-level interfaces for enabling all of these options. Moving your application to Windows, you now have a number of options to choose from for media playback, from high-level, embedded player to low-level access to the streams, allowing you to make changes to the experience. Depending on your needs, you may want to consider your technology choice if video or audio streaming or playback are critical. Using WPF you have rapid and immediate access to host Windows Media Player in your application. Under C++, you can embed Windows Media Player with a bit of scaffolding, but you also have finer control.
Playing a Video
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Video Playback |
One of the nice things about iPhone media is that it’s dead simple to launch and play a video.
iPhone Objective C
MPMoviePlayerController *mp = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
if (mp)
{
// save the movie player object
self.moviePlayer = mp;
[mp release];
// Apply the user specified settings to the movie player object
[self setMoviePlayerUserSettings];
// Play the movie!
[self.moviePlayer play];
}
If you have the ability to build your app in C# using WPF, then you can easily add a MediaElement to your layout and set the URL for the media source in the XAML or code-behind (as shown here).
Windows XAML
<MediaElement Name="myMediaElement" Width="450" Height="250" LoadedBehavior="Manual" UnloadedBehavior="Stop" Stretch="Fill" MediaOpened="Element_MediaOpened" MediaEnded="Element_MediaEnded"/>
Windows C#
private void PlayContentUrl( Uri movieURL )
{
myMediaElement.Source = movieURL;
myMediaElement.Play();
}
The experience is a little different on Windows, and you should consider this in your design. For the iPhone, on OS 3.1 and earlier, the MPMoviePlayerController by default launches in full screen mode. With the MediaElement in WPF, the player is embedded in your application, surrounded by your controls.
If your Windows app is in C++ you can embed a Windows Media Player ActiveX control into your application and control that, but there are several steps involved. You can read a brief summary of each of the steps in the MSDN article, “Hosting the Windows Media Player Control in a Windows Application.”
Playing Audio
|
Quick Reference |
iPhone API |
Windows API |
|---|---|---|
|
Audio Playback |
If your iPhone app incorporates sound, then you are probably familiar with the extensive tools available on the iPhone for working with audio content. While the Media Player Framework lets you play content from the user’s iPod library, adding sounds to your application could be as simple as the AV Foundation framework.
iPhone Objective C
self._player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (self._player)
{
[self._player play]
}
If you want to play audio files or streams in your Windows WPF app, you can embed the MediaElement, as per the video playing discussion above; this will display a media player. If you want to play sounds directly in your .NET application, without visual elements, you can use the SoundPlayer class if your sound files are .wav format.
Windows C#
private void playSound() {
SoundPlayer player = new SoundPlayer();
player.SoundsLocation = pathToSoundFile;
player.Play();
}
For other file format in WPF you’ll need to embed Windows Media Player, which you can do with the MediaPlayer class. This is distinct from the MediaElement as it has no visual representation. The API is similar, however.
If you are writing your Windows app in C++ you could embed Windows Media Player, following the video discussion above, giving you a visual element for playback. If you want background sound you can use the PlaySound function.
Windows C++
#include <msystem.h>
void MyClass::playSound() {
PlaySound(fileUrl, NULL, SND_FILENAME | SND_ASYNC);
}
Note that this requires a sound that can be played by the wave-form device on the netbook, so probably you are safest playing .wav files.