注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

分享,态度 ·~~

—— 十年太长,五年;如果可以回到五年前,你最想对那时候的自己说什么?

 
 
 

日志

 
 

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序  

2013-04-05 16:18:13|  分类: Windows Phone |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序 - 乂乂 - 分享,态度 ·~~

有许多文档介绍将应用程序从 iOS 移植到 Windows Phone,但是在本文中,我要从为这两种平台从头开始编写新应用程序的前提开始讲起。 我不会对这两种平台户的孰优孰劣做出价值评判。 相反,我对编写应用程序报以务实的态度,并描述在编写应用程序时这两种平台的异同之处。

作为 Windows Phone 团队的成员,我对 Windows Phone 平台充满热忱,但是本文的重点不是说一种平台优于另一种平台,而是说平台是不同的,因此需要一些不同的编程方法。 尽管您可以使用 MonoTouch 系统在 C# 中编写 iOS 应用程序,那是小众环境。 在本文中,我使用标准 Xcode 和 Objective-C 编写 iOS 应用程序,用 Visual Studio 和 C# 编写 Windows Phone 应用程序。

目标用户体验

我的目标是在应用程序的两个平台版本上实现相同的用户体验,同时确保每个版本忠实于目标平台的模型和理念。 为了说明我的意思,请考虑应用程序的 Windows Phone 版本用垂直滚动的列表框实施主用户界面,而 iOS 版本则用水平 ScrollViewer 实施主用户界面。 很明显,这些差别仅仅是软件方面的,也就是说,我可以在 iOS 中编写一个垂直滚动的列表,在 Windows Phone 中编写一个水平滚动的列表。 强制使用这些偏好设置会不太忠实于各自平台的设计理念,然而,我想避免这种“不自然的行为”。

应用程序 SeaVan 显示了美国西雅图与加拿大不列颠哥伦比亚省温哥华之间的四条陆地边境通道,以及每条边境通道的等候时间。 此应用程序通过 HTTP 从美国和加拿大的政府 网站提取数据,并通过按钮手动刷新数据,或通过计时器自动刷新数据。

图 1显示了这两种实施。 您会注意到一个差别在于 Windows Phone 版本是有主题感知功能,并使用当前的强调文字颜色。 相反,iOS 版本没有主题或强调文字颜色概念。

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序 - 乂乂 - 分享,态度 ·~~ 

图 1 应用程序 SeaVan 在 iPhone 和 Windows Phone 设备上的主用户界面屏幕

Windows Phone 设备有严格的线性页面导航模型。 所有重要屏幕用户界面均显示为页面,用户通过页面堆栈向前向后行进。 您可以在 iPhone 上实现相同的线性导航,但是 iPhone 并不受这种模型的约束,所以您可以自由地应用任何您喜欢的屏幕模型。 在 iOS 版本的 SeaVan 中,“关于”等辅助屏幕是模式视图控制器。 从技术的角度来看,这些大致相当于 Windows Phone 的模式弹出窗口。

图 2 显示了一般化用户界面的示意图,内部用户界面元素为白色,外部用户界面元素(Windows Phone 中的启动器/选择器,iOS 中的共享应用程序)为橙色。 设置用户界面(淡绿色)则是反常现象,我将在下文描述。

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序 - 乂乂 - 分享,态度 ·~~

图 2 一般化应用程序用户界面

另一个用户界面差别是 Windows Phone 使用 ApplicationBar 作为标准化用户界面元素。 在 SeaVan 中,此栏是用户找到按钮以调用应用程序中辅助功能的地方——“关于”页面及“设置”页面,以及手动刷新数据的地方。 在 iOS 中没有类似于 ApplicationBar 的元素,所以在 iOS 版本的 SeaVan 中,一个简单的工具栏提供对等的用户体验。

相反,iOS 版有 PageControl——屏幕底部的黑色栏,有四个位置点指示符。 用户可以通过滑擦或点击 PageControl,在四条边境通道上水平滚动。 在 Windows Phone 版 SeaVan 中,没有类似于 PageControl 的元素。 相反,Windows Phone 版 SeaVan 的用户直接滑擦屏幕,在边境通道中滚动。 使用 PageControl 的一个结果是易于配置,所以每个页面都可以停靠并且完全可见。 使用 Windows Phone 的滚动列表框,并没有标准的支持,所以用户最终只看到两条边境通道的一部分。 ApplicationBar 和 PageControl 都是举例说明,与使用标准行为相比,我尚未尝试在两种平台上提供更为一致的用户体验。

架构决策

在这两种平台均鼓励使用 Model-View-ViewModel (MVVM) 架构。 一个区别在于 Visual Studio 生成的代码包括引用应用程序对象中的主 viewmodel。 Xcode 不会这样做,您可以自由地在您选择的任何地方将 viewmodel 插入应用程序。 在两种平台上,将 viewmodel 插入应用程序对象中是合情合理的。

一个更加重要的差别是模型数据流经 viewmodel 到达视图的机制。 在 Windows Phone 中,这可以通过数据绑定来实现,数据绑定可让您在 XAML 中指定用户界面元素如何与 viewmodel 数据进行关联,以及运行时会处理实际传播的数值。 在 iOS 中,尽管有第三方库提供类似的行为(基于键值观察器模式),但是在标准 iOS 库中并没有与数据绑定等同的元素。 相反,应用程序必须在 viewmodel 与视图之间手动传播数据值。图 3 显示了 SeaVan 的一般化架构和组件,viewmodels 为粉红色,视图为蓝色。

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序 - 乂乂 - 分享,态度 ·~~ 

图 3 SeaVan 的一般化架构

Objective-C 和 C#

很明显,Objective-C 和 C# 的详细比较不是一篇短文可以说清楚的,但是图 4提供了关键构造的大致映射。

图 4 Objective-C 和 C# 中的关键构造

Objective-C概念C# 对应部分
@interface Foo : Bar {}类声明,包括继承class Foo : Bar {}

@implementation Foo

@end

类实施class Foo : Bar {}
Foo* f = [[Foo alloc] init]类实例化和初始化Foo f = new Foo();
-(void) doSomething {}实例方法声明void doSomething() {}
+(void) doOther {}类方法声明static void doOther() {}

[myObject doSomething];

or

myObject.doSomething;

向对象发送消息(在对象上调用方法)myObject.doSomething();
[self doSomething]向当前对象发送消息(在当前对象上调用方法)this.doSomething();
-(id)init {}初始化程序(构造函数)Foo() {}
-(id)initWithName:(NSString*)n price:(int)p {}带有参数的初始化程序(构造函数)Foo(String n, int p) {}
@property NSString *name;属性声明public String Name { get; set; }
@interface Foo : NSObject <UIAlertViewDelegate>Foo 将 NSObject 划分为子类并实施 UIAlertViewDelegate 协议(大致相当于 C# 接口)class Foo : IAnother

核心应用程序组件

为了启动 SeaVan 应用程序,我在 Xcode 中创建一个新的单一视图应用程序,并在 Visual Studio 中创建一个 Windows Phone 应用程序。 这两个工具都用一套启动器文件生成一个项目,包括代表应用程序对象的类及主页面或视图。

iOS 命名约定是在类名称中使用两个字母的前缀,所以所有自定义 SeaVan 类的前缀是“SV”。iOS 应用程序以通常的 C 主方法启动,这种方法会创建一个应用程序委托。 在 SeaVan 中,这是 SVAppDelegate 类的实例。 应用程序委托等同于 Windows Phone 中的应用程序对象。 我在 Xcode 中,开启自动引用计数 (ARC),创建了此项目。 这会在主程序的所有代码周围添加一个 @autoreleasepool 范围声明,如下所示:

  1.  
  2. int main(int argc, char *argv[])
  3. {
  4.   @autoreleasepool {
  5.     return UIApplicationMain(
  6.     argc, argv, nil, 
  7.     NSStringFromClass([SVAppDelegate class]));
  8.   }
  9. }
  10.         

现在系统将自动引用计数我创建的对象,并在计数归零时自动释放这些对象。 @autoreleasepool 会处理好大多数常见的 C/C++ 内存管理问题,并提供非常接近于 C# 的编码体验。

在 SVAppDelegate 的接口声明中,我将其指定为 <UIApplicationDelegate>。 这意味着它响应标准应用程序委托消息,例如 application:didFinishLaunchingWith?Options。 我还声明了 SVContentController 属性。 在 SeaVan 中,这对应于 Windows Phone 中的标准 MainPage 类。 最后一个属性是 SVBorderCrossings 指针——这是我的主要 viewmodel,里面保存一系列 SVBorderCrossing 项目,每个项目代表一个边境通道:

  1.  
  2. @interface SVAppDelegate : UIResponder <UIApplicationDelegate>{}
  3. @property SVContentController *contentController;
  4. @property SVBorderCrossings *border;
  5. @end
  6.         

当主窗口启动时,应用程序委托初始化,系统向其发送带有选择器 didFinishLaunching?WithOptions 的应用程序消息。 与 Windows Phone 相比,逻辑对等物则是应用程序启动或激活的事件处理程序。 在此,我加载了一个名为 SVContent 的 Xcode Interface Builder (XIB) 文件,并用于初始化我的主窗口。 在 Windows Phone 上,与 XIB 文件对应的是 XAML 文件。 XIB 文件实际上是 XML 文件,只不过通常用 Xcode 图形 XIB 编辑器进行编辑——类似于 Visual Studio 中的图形 XAML 编辑器。 我的 SVContentController 类与 SVContent.xib 文件相关联,就如同 Windows Phone MainPage 类与 MainPage.xaml 文件相关联一样。

最后,我实例化 SVBorderCrossings viewmodel 并调用其初始化程序。 在 iOS 中,通常在一条语句中分配并初始化,以避免使用未初始化的对象而造成的潜在陷阱:

  1.  
  2. - (BOOL)application:(UIApplication *)application
  3. didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
  4. {
  5.   [[NSBundle mainBundle] loadNibNamed:@"SVContent" 
  6.     owner:self options:nil];
  7.   [self.window addSubview:self.contentController.view];
  8.   border = [[SVBorderCrossings alloc] init];
  9.   return YES;
  10. }
  11.         

在 Windows Phone 中,XIB 加载操作的对应操作通常是地后台进行的。 编写时会使用 XAML 文件生成这部分的代码。 例如,如果您取消隐藏 Obj 文件夹中的隐藏文件,那么在 MainPage.g.cs 中您将会看到 InitializeComponent 方法,此方法加载对象的 XAML:

  1.  
  2. public partial class MainPage : Microsoft.Phone.Controls.PhoneApplicationPage
  3. {
  4.   public void InitializeComponent()
  5.   {
  6.     System.Windows.Application.LoadComponent(this,
  7.       new System.Uri("/SeaVan;component/MainPage.xaml",
  8.       System.UriKind.Relative));
  9.   }
  10. }
  11.         

SVContentController 类作为我的主页将托管一个滚动查看器,而这个查看器托管四个视图控制器。 每个视图控制器将最终使用来自四个西雅图至温哥华边境通道的数据进行填充。 我声明这个类为 <UIScrollViewDelegate> 并定义三个属性: 视图控制器的 UIScrollView、UIPageControl 和 NSMutableArray。 scrollView 和 pageControl 均声明为 IBOutlet 属性,这可让我将其连接 XIB 编辑器中的用户界面人工制品。 在 Windows Phone 中对等的是当 XAML 中一个元素用 x:Name 进行声明时,生成类域。 在 iOS 中,您也可以将 XIB 用户界面元素连接您的类中的 IBAction 属性,从而连接用户界面事件。 在 Silverlight 中与之对等的是您在 XAML 中添加一个 Click 处理程序以连接事件,并为这个类中的事件处理程序提供存根代码。 有趣的是,我的 SVContentController 不会对任何用户界面类划分子类。 相反,它会对基类 NSObject 划分子类。 它是作为 SeaVan 中的一个用户界面元素,因为它实施 <UIScrollViewDelegate> 协议,即响应 scrollView 消息:

  1.  
  2. @interface SVContentController : NSObject <UIScrollViewDelegate>{}
  3. @property IBOutlet UIScrollView *scrollView;
  4. @property IBOutlet UIPageControl *pageControl;
  5. @property NSMutableArray *viewControllers;
  6. @end
  7.         

在 SVContentController 实施中,要调用的第一种方法是 awakeFromNib(从 NSObject 继承)。 在此,我创建了 SVViewController 对象阵列,并将每个页面的视图添加到 scrollView:

  1.  
  2. - (void)awakeFromNib
  3. {   
  4.   self.viewControllers = [[NSMutableArray alloc] init];
  5.   for (unsigned i = 0; i < 4; i++)
  6.   {
  7.     SVViewController *controller = [[SVViewController alloc] init];
  8.     [controllers addObject:controller];
  9.     [scrollView addSubview:controller.view];
  10.   }
  11. }
  12.         

最后,当用户滑擦 scrollView 或点击页面控制时,我得到 scrollViewDidScroll 消息。 通过这种方法,当一半以上的上一页/下一页可见时,我便切换 PageControl 指示器。 然后我加载可见的页面,以及任何一侧的页面(以免用户开始滚动时出现闪烁)。 我做最后一件事是调用一种私有方法 updateViewFromData,此方法提取 viewmodel 数据并手动将数据设置到用户界面的每个域中:

  1.  
  2. - (void)scrollViewDidScroll:(UIScrollView *)sender
  3. {
  4.   CGFloat pageWidth = scrollView.frame.size.width;
  5.   int page = floor((scrollView.contentOffset.x - 
  6.     pageWidth / 2) / pageWidth) + 1;
  7.   pageControl.currentPage = page;
  8.   [self loadScrollViewWithPage:page - 1];
  9.   [self loadScrollViewWithPage:page];
  10.   [self loadScrollViewWithPage:page + 1];
  11.   [self updateViewFromData];
  12. }
  13.         

在 Windows Phone 中,对应的功能在 MainPage 中实施,即在 XAML 中实施。 我使用 ListBox 的 DataTemplate 中的 TextBlock 控件,显示通关时间。 ListBox 自动将每个数据集滚动到视图中,所以 Windows Phone 版 SeaVan 没有处理滚动手势的自定义代码。 对于 updateViewFromData 方法也没有对应的方法,因为此操作是通过数据绑定来处理的。

提取和分析 Web 数据

除了作为应用程序委托之外,SVAppDelegate 类还声明域和属性,以支持从美国和加拿大网站提取 和分析通关数据。 我对这两个网站的 HTTP 连接,声明两个 NSURLConnection 域。 我还声明两个 NSMutableData 域——即缓冲区,当每个区块数据进来时,我便使用缓冲区追加数据。 我更新这个类,以实施 <NSXMLParserDelegate> 协议,所以除了作为标准应用程序委托之外,还是 XML 分析器委托。 收到 XML 数据后,将首先调用这个类分析数据。 因为我知道我在处理两套完全不同的 XML 数据,所以我立即将工作转交给两个子分析器委托中的其中一个。 为此,我声明一对自定义 SVXMLParserUs/?SVXMLParserCa 域。 这个类还为自动刷新功能声明一个计时器。 对于每个计时器事件,我将调用 refreshData 方法,如 图 5 所示。

图 5 SVAppDelegate 的接口声明

  1.  
  2. @interface SVAppDelegate : 
  3.   UIResponder <UIApplicationDelegate, NSXMLParserDelegate>
  4. {
  5.   NSURLConnection *connectionUs;
  6.   NSURLConnection *connectionCa;
  7.   NSMutableData *rawDataUs;
  8.   NSMutableData *rawDataCa;
  9.   SVXMLParserUs *xmlParserUs;
  10.   SVXMLParserCa *xmlParserCa;
  11.   NSTimer *timer;
  12. }
  13. @property SVContentController *contentController;
  14. @property SVBorderCrossings *border;
  15. - (void)refreshData;
  16. @end
  17.         

refreshData 方法为每套传入的数据分配一个可变的数据缓冲区,并建立两个 HTTP 连接。 我在使用自定义 SVURLConnectionWithTag 类,这个类对 NSURLConnection 划分子类,因为 iOS 分析器委托模型必须从相同的对象开启这两个请求,所有数据将回到这个对象中。 所以我需要一种方法来区分传入的美国数据和 加拿大数据。 为了做到这一点,我在每个连接上附加一个标记,并在 NSMutableDictionary 中缓存这两个连接。 当我初始化每个连接时,我指定其自身为委托。 只要收到数据区块,便调用 connectionDidReceiveData 方法,我实施这种方法按照标记将数据追加到缓冲区(请看图 6)。

图 6 设置 HTTP 连接

  1.  
  2. static NSString *UrlCa = @"http://apps.cbp.gov/bwt/bwt.xml";
  3. static NSString *UrlUs = @"http://wsdot.wa.gov/traffic/rssfeeds/CanadianBorderTrafficData/Default.aspx";
  4. NSMutableDictionary *urlConnectionsByTag;
  5. - (void)refreshData
  6. {
  7.   rawDataUs = [[NSMutableData alloc] init];
  8.   NSURL *url = [NSURL URLWithString:UrlUs];
  9.   NSURLRequest *request = [NSURLRequest requestWithURL:url];   
  10.   connectionUs =
  11.   [[SVURLConnectionWithTag alloc]
  12.     initWithRequest:request
  13.     delegate:self
  14.     startImmediately:YES
  15.     tag:[NSNumber numberWithInt:ConnectionUs]];
  16.     // ...
  17.           Code omitted: set up the Canadian connection in the same way
  18. }
  19.         

我还必须实施 connectionDidFinishLoading。 收到所有数据时(两个连接的任何一个),我便设置此应用程序委托对象为第一个分析器。 分析器消息是一个阻止调用,所以当消息返回时,我可以调用内容控制器中的 updateViewFromData,根据已分析的数据更新用户界面:

  1.  
  2. - (void)connectionDidFinishLoading:(SVURLConnectionWithTag *)connection
  3. {
  4.   NSXMLParser *parser = 
  5.     [[NSXMLParser alloc] initWithData:
  6.   [urlConnectionsByTag objectForKey:connection.tag]];
  7.   [parser setDelegate:self];
  8.   [parser parse];
  9.   [_contentController updateViewFromData];
  10. }
  11.         

通常,有两类 XML 分析器:

  • Simple API for XML (SAX) 分析器,当分析器预排 XML 树时,便通知您的代码
  • 文档对象模型 (DOM) 分析器,读取整个文档并建立一个内存中的表示,您可以查询不同的元素

iOS 中的默认 NSXMLParser 是 SAX 分析器。 第三方 DOM 分析器可在 iOS 中使用,但是我想比较标准平台,而不诉诸于第三方库。 标准分析器轮流预排每个元素,但并不理解当前项目置于整个 XML 文档的什么位置。 为此,SeaVan 的父分析器处理其关注的最外面的块,然后交给子委托分析器以处理下一个内部块。

在分析器委托方法中,我进行一个简单测试,以区分美国 XML 与加拿大 XML,实例化相应的子分析器,然后设置这个子分析器为从这个点之后的当前分析器。 我还设置子的父分析器为自身,这样当子分析器到达 XML 末端时,可以将分析控制交还父分析器(请看图 7

图 7 分析器委托方法

  1.  
  2. - (void)connection:(SVURLConnectionWithTag *)connection didReceiveData:(NSData *)data
  3. {
  4.   [[urlConnectionsByTag objectForKey:connection.tag] appendData:data];
  5. }
  6. - (void)parser:(NSXMLParser *)parser
  7. didStartElement:(NSString *)elementName
  8.   namespaceURI:(NSString *)namespaceURI
  9.   qualifiedName:(NSString *)qName
  10.   attributes:(NSDictionary *)attributeDict
  11. {
  12.   if ([elementName isEqual:@"rss"]) // start of US data
  13.   {
  14.     xmlParserUs = [[SVXMLParserUs alloc] init];
  15.     [xmlParserUs setParentParserDelegate:self];
  16.     [parser setDelegate:xmlParserUs];
  17.   }
  18.   else if ([elementName isEqual:@"border_wait_time"]) // start of Canadian data
  19.   {
  20.     xmlParserCa = [[SVXMLParserCa alloc] init];
  21.     [xmlParserCa setParentParserDelegate:self];
  22.     [parser setDelegate:xmlParserCa];
  23.   }
  24. }
  25.         

对于对等的 Windows Phone 代码,我首先为美国网站建立一个 Web 请求, 然后为加拿大网站建立一个 Web 请求。 在此,我使用 WebClient,尽管 HttpWebRequest 通常更适合于达到最优性能和响应性。 我为 OpenReadCompleted 事件建立一个处理程序,然后异步打开请求:

  1.  
  2. public static void RefreshData()
  3. {
  4.   WebClient webClientUsa = new WebClient();
  5.   webClientUsa.OpenReadCompleted += webClientUs_OpenReadCompleted;
  6.   webClientUsa.OpenReadAsync(new Uri(UrlUs));
  7.   // ...
  8.           Code omitted: set up the Canadian WebClient in the same way
  9. }
  10.         

在每个请求的 OpenReadCompleted 事件处理程序中,我提取已经作为 Stream 对象返回的数据,然后交给帮助程序对象,以分析 XML。 因为我有两个独立的 Web 请求和两个独立的 OpenReadCompleted 事件处理程序,所以我无需对请求添加标记,或者进行任何测试,以确认任何特定传入的数据属于哪个请求。 我也不必处理每个传入的数据区块,以建立整个 XML 文档。 相反,我可以一边休息,一边等待,直至收到所有数据为止:

  1.  
  2.  private static void webClientUs_OpenReadCompleted(object sender, 
  3.   OpenReadCompletedEventArgs e)
  4. {
  5.   using (Stream result = e.Result)
  6.   {
  7.     CrossingXmlParser.ParseXmlUs(result);
  8.   }
  9. }
  10.         

对于 XML 的分析,与 iOS 相比,Silverlight 的标配包括一个 DOM 分析器,以 XDocument 类表示。 所以我不用分析器的层次结构,可以直接使用 XDocument 进行所有的分析工作:

  1.  
  2. internal static void ParseXmlUs(Stream result)
  3. {
  4.   XDocument xdoc = XDocument.Load(result);
  5.   XElement lastUpdateElement = 
  6.     xdoc.Descendants("last_update").First();
  7.   // ...
  8.           Etc.
  9.           }
  10.         

支持视图和服务

在 Windows Phone 中,应用程序对象是静态的,可用于应用程序中的任何其他组件。 同样,在 iOS 中,应用程序中有一个 UIApplication 委托类型。 为了方便起见,我定义了一个宏,可以在应用程序中任何地方使用,以便掌握应用程序委托,并适当地转换成特定的 SVAppDelegate 类型:

  1.  
  2. #define appDelegate ((SVAppDelegate *) [[UIApplication sharedApplication] delegate])
  3.         

例如,当用户点击刷新按钮时,这可让我调用应用程序委托的 refreshData 方法,这个按钮属于我的视图控制器:

  1.  
  2. - (IBAction)refreshClicked:(id)sender
  3. {
  4.   [appDelegate refreshData];
  5. }
  6.         

当用户点击关于按钮时,我想显示关于屏幕,如图 8 所示。 在 iOS 中,我实例化 SVAboutViewController,它有一个关联的 XIB,及一个滚动文本元素用于用户指南,在工具栏上还有三个附加的按钮。

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序 - 乂乂 - 分享,态度 ·~~ 

图 8 iOS 和 Windows Phone 中的 SeaVan 关于屏幕

为了显示这个视图控制器,我对其进行实例化,然后向当前对象(自身)发送一条 presentModalViewController 消息:

  1.  
  2. - (IBAction)aboutClicked:(id)sender
  3. {
  4.   SVAboutViewController *aboutView =
  5.     [[SVAboutViewController alloc] init];
  6.   [self presentModalViewController:aboutView animated:YES];
  7. }
  8.         

在 SVAboutViewController 类中,我实施一个取消按钮,以消除这个视图控制器,使控件还原到调用的视图控制器:

  1.  
  2. - (IBAction) cancelClicked:(id)sender
  3. {
  4.   [self dismissModalViewControllerAnimated:YES];
  5. }
  6.         

这两个平台均提供一种标准方法,让应用程序调用内置应用程序中的功能,例如电子邮件、电话及短信。 关键区别在于在内置功能返回之后,控件是否返回到应用程序,在 Windows Phone 中总是发生这种情况。 在 iOS 中,某些功能会出现这种情况,但其他功能则不会。

在 SVAboutViewController 中,当用户点击支持按钮时,我想为用户撰写一封电子邮件,以发送给开发团队。 MFMailComposeViewController——也是显示为模式视图——很好地满足了这个目的。 这个标准视图控制器还实施一个取消按钮,执行完全相同的工作,即消除自身并将控件还原到调用的视图:

  1.  
  2. - (IBAction)supportClicked:(id)sender
  3. {
  4.   if ([MFMailComposeViewController canSendMail])
  5.   {
  6.     MFMailComposeViewController *mailComposer =
  7.       [[MFMailComposeViewController alloc] init];
  8.     [mailComposer setToRecipients:
  9.       [NSArray arrayWithObject:@"tensecondapps@live.com"]];
  10.     [mailComposer setSubject:@"Feedback for SeaVan"];
  11.     [self presentModalViewController:mailComposer animated:YES];
  12. }
  13.         

在 iOS 中获得地图方向指引的标准方法是调用 Google 地图。 这种方法的不利之处是将用户引到 Safari 共享应用程序(内置应用程序),并且在编程上没有办法将控件返回到应用程序。 我想最大程度减少用户离开应用程序的可能性,所以我没有显示方向指引,而是使用一个自定义 SVMapViewController(托管标准 MKMapView 控件)来显示目标边境通道的地图:

  1.  
  2. - (IBAction)mapClicked:(id)sender
  3. {   
  4.   SVBorderCrossing *crossing =
  5.     [appDelegate.border.crossings
  6.     objectAtIndex:parentController.pageControl.currentPage];
  7.   CLLocationCoordinate2D target = crossing.coordinatesUs;
  8.   SVMapViewController *mapView =
  9.     [[SVMapViewController alloc]
  10.     initWithCoordinate:target title:crossing.portName];
  11.   [self presentModalViewController:mapView animated:YES];
  12. }
  13.         

为了允许用户输入评论,我可以编写一条指向 iTunes App Store 中该应用程序的链接。 (在以下代码中的九位数 ID 是应用程序的 App Store ID。) 然后我将其传递到 Safari 浏览器(一个共享应用程序)。 在此我没有选择,只能离开应用程序:

  1.  
  2. - (IBAction)appStoreClicked:(id)sender
  3. {
  4.   NSString *appStoreURL =
  5.     @"http://itunes.apple.com/us/app/id123456789?mt=8";
  6.   [[UIApplication sharedApplication]
  7.     openURL:[NSURL URLWithString:appStoreURL]];
  8. }
  9.         

在 Windows Phone 中,与关于按钮对等的是 ApplicationBar 上的按钮。 当用户点击这个按钮,我调用 NavigationService 导航到 AboutPage:

  1.  
  2. private void appBarAbout_Click(object sender, EventArgs e)
  3. {
  4.   NavigationService.Navigate(new Uri("/AboutPage.xaml"
  5.     UriKind.Relative));
  6. }
  7.         

正如在 iOS 版本一样,AboutPage 在滚动的文本中显示简单的用户指南。 没有取消按钮,因为用户可以点击设备上的返回按钮,从此页面返回。 我没有使用支持按钮和 App Store 按钮,但有 Hyperlink?Button 控件。 对于支持电子邮件,我可以使用指定 mailto: 协议的 NavigateUri,实施 此行为。 这足以调用 EmailComposeTask:

  1.  
  2.   <HyperlinkButton 
  3.   Content="tensecondapps@live.com" 
  4.   Margin="-12,0,0,0" HorizontalAlignment="Left"
  5.   NavigateUri="mailto:tensecondapps@live.com" 
  6.   TargetName="_blank" />
  7.         

我用代码中的 Click 处理程序建立 Review 链接,然后调用 MarketplaceReviewTask 启动器:

  1.  
  2. private void ratingLink_Click(object sender, 
  3.   RoutedEventArgs e)
  4. {
  5.   MarketplaceReviewTask reviewTask = 
  6.     new MarketplaceReviewTask();
  7.   reviewTask.Show();
  8. }
  9.         

回到 MainPage,我没有为地图/方向功能提供单独的按钮,而是在列表框上实施 SelectionChanged 事件,这样用户可以点击内容以调用此功能。 这种方法与 Windows Store 应用商店的应用程序保持一致,即用户可以直接与内容交互,而不用通过 chrome 元素进行间接交互。 在这个处理程序中,我启动 BingMapsDirectionsTask 启动器:

  1.  
  2. private void CrossingsList_SelectionChanged(
  3.   object sender, SelectionChangedEventArgs e)
  4. {
  5.   BorderCrossing crossing = (BorderCrossing)CrossingsList.SelectedItem;
  6.   BingMapsDirectionsTask directions = new BingMapsDirectionsTask();
  7.   directions.End =
  8.     new LabeledMapLocation(crossing.PortName, crossing.Coordinates);
  9.   directions.Show();
  10. }
  11.         

应用程序设置

在 iOS 平台上,应用程序的偏好设置是通过内置的设置应用程序来集中管理的,这个应用程序为用户提供用户界面以编辑内置及第三方应用程序的设置。 图 9 显示了 iOS 中的主设置用户界面和具体的 SeaVan 设置视图,以及 Windows Phone 的设置页面。 SeaVan 只有一个设置,一个用于自动刷新功能的切换开关。

MSDN Blog:为 Windows Phone 和 iOS 编写应用程序 - 乂乂 - 分享,态度 ·~~ 

图 9 iOS 上的标准设置和 SeaVan 相关设置及 Windows Phone 设置页

为了在应用程序整合设置,我使用 Xcode 创建一个特殊类型的资源,称为设置捆绑包。 然后我使用 Xcode 设置编辑器,配置设置值,无需任何代码。

在应用程序方法中,如图 10 所示,我确保设置是同步的,然后从商店中提取最新的值。 如果自动刷新设置值为 True,则启动计时器。 API 支持在应用程序中获取和设置数值,所以除了设置应用中的应用程序视图之外,我还可以选择在应用程序中提供一个设置视图。

图 10 应用程序方法

  1.  
  2.  - (BOOL)application:(UIApplication *)application
  3. didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
  4. {
  5.   NSUserDefaults *defaults =
  6.     [NSUserDefaults standardUserDefaults];
  7.   [defaults synchronize];
  8.   boolean_t isAutoRefreshOn =
  9.     [defaults boolForKey:@"autorefresh"];
  10.   if (isAutoRefreshOn)
  11.   {
  12.     [timer invalidate];
  13.     timer =
  14.       [NSTimer scheduledTimerWithTimeInterval:kRefreshIntervalInSeconds
  15.         target:self
  16.         selector:@selector(onTimer)
  17.         userInfo:nil
  18.         repeats:YES];
  19.   }
  20.   // ...
  21.           Code omitted for brevity
  22.   return YES;
  23. }
  24.         

在 Windows Phone 中,我无法对全局设置应用添加应用设置。 相反,我在应用中提供自己的设置用户界面。 在 SeaVan 中,如同 AboutPage 一样,SettingsPage 就是另一种页面。 我在 ApplicationBar 上提供一个按钮,以导航到这个页面:

  1.  
  2. private void appBarSettings_Click(object sender, 
  3.   EventArgs e)
  4. {
  5.   NavigationService.Navigate(new Uri("/SettingsPage.xaml"
  6.     UriKind.Relative));
  7. }
  8.         

在 SettingsPage.xaml 中,我定义了一个 ToggleSwitch,以用于自动刷新功能:

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

我没有选择,只能在应用中提供设置行为,但是我可以把这个变成对我有利,然后实施 AppSettings viewmodel,并通过数据绑定将其连接到视图,就像任何其他数据模型一样。 在 MainPage 类中,我根据设置的值启动计时器:

  1.  
  2. protected override void OnNavigatedTo(NavigationEventArgs e)
  3. {
  4.   if (App.AppSettings.AutoRefreshSetting)
  5.   {
  6.     timer.Tick += timer_Tick;
  7.     timer.Start();
  8.   }
  9. }
  10.         

版本说明和示例应用

平台版本:

  • Windows Phone SDK 7.1,及 Silverlight for Windows Phone Toolkit
  • iOS 5 和 Xcode 4

SeaVan 将发布到 Windows Phone Marketplace 和 iTunes App Store。

没那么困难

为 Windows Phone 和 iOS 编写应用程序并没那么困难: 相同点多于不同点。 这两个平台都使用 MVVM 处理应用对象和一个或多个页面/视图对象,而用户界面类与 XML(XAML 或 XIB)相关联,您可以用图形编辑器来编辑 XML。 在 iOS 中,您向对象发送消息,而在 Windows Phone 中您在对象上调用方法。 但是差别几乎是学术性的,如果您不喜欢“[message]”表示法,您甚至可以在 iOS 中使用圆点记法。 这两个平台都有事件/委托机制、实例及静态方法、私有和公共成员,以及具有 get 和 set 访问器的属性, 在两种平台上,您可以调用内置应用功能并支持用户设置。 很明显,您必须维护两个代码库,但是您的应用架构、主要组件设计和用户体验可以在两种平台中保持一致。 尝试一下 — 您可能会有意外惊喜哦!

Andrew Whitechapel  拥有 20 多年的开发经验,目前担任 Windows Phone 团队的项目经理,负责应用程序平台的核心部分。 他的最新著作是《深入解析 Windows Phone 7 开发 (Windows Phone 7 Development Internals)》(Microsoft Press, 2012)。

衷心感谢以下技术专家对本文的审阅: Chung Webster 和 Jeff 

【from http://msdn.microsoft.com/zh-cn/magazine/jj658972.aspx】
【本文链接:http://zwkufo.blog.163.com/blog/static/258825120133541813345/ 】
  评论这张
 
阅读(914)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017