MonoTouch-IOS-开发秘籍-全-

MonoTouch IOS 开发秘籍(全)

原文:zh.annas-archive.org/md5/e782db9cc6c1163c07b37906130bf0f1

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

技术正在迅速发展。便携式设备,如媒体播放器、智能手机和平板电脑,为人们的沟通、分享和消费数字内容的方式带来了巨大的进步和变化。开发者需要了解这些设备所运行的可用平台,如果他们想要成为“游戏的一部分”。

iOS,苹果为其便携式设备开发的操作系统,无疑是当今领先的便携式平台之一。如果没有 MonoTouch,.NET 开发者将不得不花费时间学习一门新的编程语言,以扩展他们的创造力到 iOS 生态系统。

本书通过一系列多个食谱和几乎相等数量的完整项目,将帮助你成为这个生态系统的成员,借助 MonoTouch 和 C#。当你阅读完它,你将成为一名合格的 iOS 开发者,准备好将你的创造力释放到当今最受欢迎的便携式平台之一。

本书涵盖的内容

第一章,开发工具,将带你了解你将需要用于使用 MonoTouch 进行 iOS 开发的全部 IDE 和 SDK。你将创建你的第一个 MonoTouch 项目,并学习如何在模拟器上进行调试。

第二章,用户界面:视图,介绍了视图的概念以及它们是如何成为完整 iOS 应用程序的一部分的。通过探索大量不同的视图组件,你将创建不同的应用程序,这将帮助你理解每个组件的工作方式。

第三章,用户界面:视图控制器,讨论了模型-视图-控制器(MVC)模式以及如何使用它来创建适合增强用户体验的应用程序。通过本章,你还将了解最有用的控制器,这些控制器将是你未来许多项目的组成部分,以及如何创建针对 iPad 的特定应用程序和通用应用程序。

第四章,数据管理,将带你了解一系列技术,这些技术将帮助你将数据管理集成到你的应用程序中。你将学习如何使用 SQLite 数据库、XML、LINQ-to-XML 和序列化,这些以前仅适用于.NET 桌面和 Web 项目。

第五章,显示数据,扩展了在 iOS 设备比桌面屏幕更小的屏幕上有效显示数据的可用组件。你将习惯于使用UITableView来显示数据列表,以及使用UIWebView来显示 HTML(以及更多)内容。

第六章,网络服务,讨论了创建在线通信以交换数据的应用程序。借助 MonoTouch,你不仅将学习如何在 iOS 应用程序中使用常见的.NET 和 WCF 网络服务,还将学习如何读取和解析 JSON 对象。

第七章,多媒体资源,将教会你如何通过设备的硬件创建应用程序来捕捉、重现和管理多媒体内容。你不仅将学会如何使用相机捕捉图像和视频,还将学习如何播放和录制音频。

第八章,集成 iOS 特性,将指导你如何将平台的原生应用程序和组件集成。你将学习如何在你的应用程序中提供电子邮件、短信和通讯录功能,以及如何使用原生日历来创建事件。

第九章,与设备硬件交互,讨论了创建能够完全感知其周围环境的应用程序,通过设备的传感器。你将学习如何根据设备方向调整用户界面,以及如何响应加速度计和陀螺仪事件。

第十章,位置服务和地图,是使用内置位置服务创建提供位置信息给用户的详细指南。你不仅将学习如何使用 GPS 硬件,还将学习如何显示地图和布局信息。

第十一章,图形和动画,介绍了 2D 图形和动画。你将学习如何动画化组件和创建简单的图形。到本章结束时,你将创建一个简单的手指绘画应用程序。

第十二章,多任务处理,将指导你如何在 iOS 应用程序中实现多任务处理,这有助于通过在幕后执行代码来增强用户体验。

第十三章,本地化,讨论了在应用程序中提供本地化内容。你将学习如何准备你的应用程序以面向全球用户。

第十四章,部署,不仅将指导你完成将完成的应用程序部署到设备上的必要步骤,还将指导你准备和分发它到 App Store。

第十五章,iOS 5 特性,讨论了最新 iOS 版本中引入的一些许多新特性,例如页面翻页内容导航、iPad 的分割键盘以及轻松样式化多个视图。

你需要这本书什么

这本书的最低要求是运行 Mac OS X Snow Leopard (10.6.) 或 Lion (10.7.) 的 Mac 电脑。几乎你将使用这本书帮助创建的所有项目都可以在 iOS 模拟器上运行。然而,一些项目将需要设备才能正确工作。你将在第一章,开发工具中找到所有适当的信息。

这本书面向谁

这本书对于没有 iOS 开发和 Objective-C 开发经验的 C# 和 .NET 开发者以及希望过渡到 MonoTouch 和 C# 语言的 Objective-C 开发者,以创建完整、引人入胜的 iPhone、iPod 和 iPad 应用程序并将它们部署到 App Store 的好处至关重要。

术语表

在这本书中,您将找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码单词显示如下:"Apple 提供了另一个基类,即 UIViewController,它负责管理视图。"

代码块设置如下:

public override void ViewDidLoad (){
base.ViewDidLoad();
this.myLabel.Text = "View loaded!";
}

当我们希望将您的注意力引到代码块的一个特定部分时,相关的行或项目将以粗体显示:

public override void ViewDidLoad (){
base.ViewDidLoad ();
UIButton.Appearance.BackgroundColor = UIColor.Gray;
UIButton.Appearance.SetTitleColor(UIColor.White, UIControlState.Normal);
this.buttonPresent.TouchUpInside += delegate(object sender, EventArgs e) {
this.PresentModalViewController(new ModalController(), true);
} ;
}

新术语和重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:"任何标记为 内容 的文件将在应用程序包中按原样复制"。

注意

警告或重要注意事项以如下方框显示。

小贴士

小技巧和技巧显示如下。

读者反馈

我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大价值的标题非常重要。

要发送一般反馈,只需发送电子邮件到 <feedback@packtpub.com>,并在邮件主题中提及书名。

如果您需要一本书并且希望我们出版,请通过 www.packtpub.com 上的 建议标题 表格或发送电子邮件到 <suggest@packtpub.com>

如果你在某个主题上有所专长,并且对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在您已经是 Packt Publishing 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。

下载示例代码

您可以从您在 www.PacktPub.com 的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.PacktPub.com/support 并注册,以便将文件直接通过电子邮件发送给您。

错误

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/support,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的现有勘误列表中,在“勘误”部分下。任何现有勘误都可以通过从www.packtpub.com/support选择您的标题来查看。

盗版

在互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt Publishing,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即向我们提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过< copyright@packtpub.com>与我们联系,并提供疑似盗版材料的链接。

我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。

问题和

如果你在本书的任何方面遇到问题,可以通过< questions@packtpub.com>联系我们,我们将尽力解决。

第一章. 开发工具

在本章中,我们将涵盖以下内容:

  • 安装先决条件

  • 使用 MonoDevelop 创建 iPhone 项目

  • 界面构建器

  • 创建 UI

  • 使用 outlets 访问 UI

  • 添加动作

  • 编译

  • 调试我们的应用程序

简介

专业人士最关心的一件重要事情是完成工作所需的工具。正如木匠需要凿子来刮木料,或者摄影师需要相机来捕捉光线一样,我们作为开发者需要某些工具,没有这些工具我们无法工作。

在本章中,我们将提供有关开发 iOS 应用程序所需的信息,iOS 是苹果公司移动设备的操作系统。我们将描述每个工具在开发周期中的作用,并逐步介绍每个工具的重要特性,这些特性对于开发我们的第一个应用程序至关重要。

开发 iOS 应用程序所需的工具如下:

  • 基于英特尔处理器的运行 Snow Leopard (10.6.) 或 Lion (10.7.) 操作系统的 Mac 计算机: 我们需要的必要程序无法安装在其他计算机平台上。

  • iOS SDK 版本 3.2 或更高: 要能够下载 iOS SDK,开发者必须注册为苹果开发者。iOS SDK 包括两个基本组件。

    • Xcode: 苹果公司使用 Objective-C 编程语言开发 iOS 和 Mac 原生应用程序的 IDE。

    • iOS 模拟器: 在计算机上调试 iOS 应用程序的一个基本程序,无需设备。请注意,许多 iOS 功能在模拟器上无法工作;因此,如果应用程序使用这些功能,则需要设备。

      在苹果开发者门户(developer.apple.com)上注册和 SDK 下载都是免费的。如果我们想在设备上运行和调试我们的应用程序或在 App Store 上分发它们,我们需要注册 iOS 开发者计划,该计划需要订阅费。

  • Mono for Mac: Mono 是微软 .NET 框架的开源实现。它提供了一套跨平台工具、库和编译器,用于在所有主流计算机操作系统(Linux、Mac 和 Windows)上开发 .NET 应用程序。我们需要从 Mono 的网站上获取的最新 Mac 安装程序。

  • MonoTouch: MonoTouch 是基于 Mono 的 SDK,它为 .NET 开发者提供了使用 C# 作为编程语言开发 iOS 应用程序的能力。MonoTouch 网站上提供免费评估版本(ios.xamarin.com),具有商业版本的所有功能,可以在 iOS 模拟器上调试和运行应用程序,没有过期限制。要在设备上部署应用程序或在 Apple 的 App Store 上分发,需要购买商业许可证。

  • MonoDevelop: MonoDevelop 是一个用于 .NET 开发的开源 IDE。它为开发者提供了许多功能,例如代码补全、数据库浏览、调试器等。Mac 版本提供了 iOS 项目模板和 MonoTouch 集成。

本章还将介绍如何使用 MonoDevelop 创建我们的第一个 iPhone 项目,使用 Interface Builder 构建其 UI,以及如何在我们的代码中访问应用程序的用户界面,包括 OutletsActions 的概念。

最后但同样重要的是,我们将学习如何编译我们的应用程序,可用的编译选项,以及如何在模拟器上进行调试。

安装先决条件

有关如何下载和安装使用 MonoTouch 进行开发的必要工具的信息。

准备工作

我们需要在我们的计算机上下载所有必要的组件。首先,需要在 developer.apple.com 上注册为苹果开发者。注册是免费且简单的,并提供访问所有必要开发资源的权限。通过电子邮件确认注册后,我们可以登录并从地址 https://developer.apple.com/devcenter/ios/index.action#downloads 下载 iOS SDK。在撰写本文时,Xcode 的最新版本是 4.2,iOS SDK 的最新版本是 5.0。

有时,当苹果推出其组件的测试版时,它们将通过其门户提供。尽管所有注册用户都可以下载和使用这些组件,但我们的已安装的 MonoTouch 版本可能无法正确与 iOS SDK 或 Xcode 的测试版兼容。因此,在从苹果开发者门户下载和安装新测试版时必须考虑这一点。

如何操作...

为了准备我们的计算机进行 iOS 开发,我们需要按照以下顺序下载和安装必要的组件:

  1. 在 OS X Snow Leopard 上的 Xcode 和 iOS SDK: 下载镜像文件后,将其挂载,然后在将弹出的窗口中双击 Xcode 和 iOS SDK 图标以开始安装。为了继续安装,必须阅读并接受将显示的两个许可协议。之后,您只需选择安装位置并点击 继续如何操作...

  2. 在 OS X Lion 上的 Xcode 和 iOS SDK: 要安装 Xcode 和 SDK,需要登录 Mac App Store。下载的文件基本上是 Xcode 和 SDK 的安装程序。下载完成后,运行 Install Xcode 应用程序,并按照安装说明进行操作。

  3. 下载和安装 Mono for Mac: Mono 的 Mac 版本可以通过 Mono 项目网站:www.mono-project.com 下载。

  4. 下载和安装 MonoTouch: 可以通过提供电子邮件地址从 ios.xamarin.com/DownloadTrial 下载最新的评估版本。

  5. 下载并安装 MonoDevelop 2.8+:尽管使用 MonoTouch 创建 iOS 应用程序不需要 MonoDevelop,但安装它会使开发变得更加容易。可以从 monodevelop.com/Download 下载。

如何工作...

现在我们已经准备好了所有东西,让我们看看每个组件需要什么。

如本章引言所述,iOS SDK 包含三个重要组件。第一个组件是 Xcode,它是 Apple 为 iOS 和 Mac 平台开发应用程序的 IDE。它针对 Objective-C 编程语言,这是在 iOS SDK 中编程的主要语言。由于 MonoTouch 是 C# 语言的 SDK,人们可能会 wonder 我们为什么需要 Xcode。除了提供各种调试 iOS 应用程序的工具外,Xcode 还为我们提供了三个重要的组件。第一个是一个设备信息窗口,称为 组织者,如图所示,这是在设备上部署我们的应用程序或通过 App Store 分发所必需的证书和配置文件。在组织者内部,我们可以查看我们应用程序的调试信息、崩溃日志,甚至从设备上获取截图!当然,这些只是 Xcode 提供的许多功能中的一部分,但它们超出了本书的讨论范围。

如何工作...

第二个组件是 界面构建器。这是一个用户界面设计器,以前是一个独立的应用程序。从 Xcode 4.0 开始,它被集成到 IDE 中。界面构建器提供了构建应用程序用户界面所需的所有必要功能。它也与 .NET 开发者所习惯的不同。

第三个组件是 iOS 模拟器。正如其名称所暗示的那样:一个设备模拟器,我们可以用它来运行我们的应用程序,而无需实际设备。iOS 模拟器的重要之处在于它可以选择模拟较旧的 iOS 版本(如果它们已安装在计算机上);包括 iPhone 和 iPad 的界面和设备方向。但是,模拟器缺少一些依赖于硬件的设备功能,例如指南针或加速度计。使用这些功能的应用程序必须在实际设备上进行测试和调试。

Mono 是 .NET 框架的开源实现。它已经存在一段时间了,并为 .NET 开发者提供了使用 .NET 语言编程应用程序的能力,同时针对所有主流操作系统:Linux、Mac 和 Windows。MonoTouch 和 MonoDevelop 都高度依赖于 Mono,使其成为一项必要的资产。

MonoDevelop 是一个开源 IDE,用于使用 Mono(以及 Windows 上的 .NET 框架)开发应用程序。它提供代码补全、数据库浏览器、GTK# 设计器、调试器,以及在我们的情况下,开发 iOS 应用程序所需的各种组件,以便轻松有效地开发。它与 MonoTouch 完美集成,将两者都归类为完整的 iOS 开发环境。

MonoTouch 是一个 SDK,允许 .NET 开发者使用 C# 编程语言为 iOS 开发应用程序。有一个常见的误解,尤其是在新开发者中:由于 MonoTouch 提供了使用 C# 编程的能力,因此可以在 Windows 计算机上安装并使用它。这是完全错误的,因为 MonoTouch 是围绕 iOS SDK 的库构建的,这些库只能在 Mac 计算机上安装。另一个误解是,使用 MonoTouch 开发的 ".NET 兼容" 应用程序需要在设备上安装某种类型的虚拟机才能运行,并且由于这个虚拟机,它们将运行得更慢。这也是错误的,因为 MonoTouch 的先进编译器通过将我们的 C# ".NET 驱动的" 代码编译成原生机器代码来处理这个问题。此外,在设备上安装虚拟机违反了苹果的指导方针。

还有更多...

使用 MonoTouch 开发的应用程序进入 App Store 的机会与其他使用原生 Objective-C 编程语言开发的应用程序一样!这意味着,如果一个应用程序不符合苹果关于应用程序接受度的严格政策,它将失败,无论它是用 Objective-C 还是 C# 编写的。MonoTouch 团队在创建一个让开发者只需担心代码的设计和最佳实践,而无需担心其他事情的 SDK 方面做得非常出色。2010 年 4 月,苹果对其应用程序提交政策进行了修改,实际上禁止了所有未使用公司开发工具创建的应用程序提交到 App Store。MonoTouch 就是其中之一。除了已经投资于 MonoTouch 的开发者中出现的担忧之外,使用它创建的应用程序通常会被 App Store 接受。2010 年 9 月,苹果修改了其政策,放宽了这一规定,为 C# 开发者带来了安慰。

有用链接

以下是一份包含安装所需工具和信息的链接列表:

更新

MonoDevelop 有一个检查可用更新的功能。每次程序启动时,它都会检查 MonoDevelop 本身、MonoTouch 和 Mono 框架的更新。它可以关闭,但并不推荐,因为它有助于保持与最新版本的同步。可以在MonoDevelop | 检查更新下找到。

参见

在本章中:

  • 编译

  • 调试我们的应用程序

第十四章,部署:

  • 在其他设备上调试

  • 为 App Store 准备我们的应用程序

使用 MonoDevelop 创建 iPhone 项目

在这个任务中,我们将讨论使用 MonoDevelop IDE 创建我们的第一个 iPhone 项目。

准备中...

现在我们已经安装了所有先决条件,我们将讨论如何使用 MonoDevelop 创建我们的第一个 iPhone 项目。

启动 MonoDevelop。它位于 Applications 文件夹中。MonoDevelop 的默认项目位置是文件夹 /Users/{yourusername}/Projects。如果硬盘上不存在,则在创建我们的第一个项目时创建。如果我们想更改文件夹,可以从菜单栏选择MonoDevelop | 首选项

在左侧面板中选择加载/保存,在默认解决方案位置字段中输入项目首选位置,然后点击确定

准备中...

如何操作...

  1. 启动 MonoDevelop 时,首先加载的是其起始页面。从菜单栏选择文件 | 新建 | 解决方案...。将显示一个窗口,提供给我们可用的项目选项。

  2. 在这个窗口中,在左侧面板中选择C# | MonoTouch | iPhone。iPhone 项目模板将在中间面板中显示。

  3. 选择iPhone 单视图应用程序。最后,为解决方案名称输入 MyFirstiPhoneProject 并点击前进。以下截图显示了新解决方案窗口:

如何操作...

  • 就这样!你已经创建了你的第一个 iPhone 项目!你可以构建并运行它。iOS 模拟器将启动,但屏幕上仍然只有一个空白浅灰色屏幕。

注意

如果由于某种原因左侧面板中没有显示 MonoTouch 部分,这意味着 MonoTouch 和/或 MonoDevelop 的安装出了问题。请参考前面的配方进行正确安装。

如果中间的模板与这个截图中的不同,那是因为你使用的 MonoTouch 和/或 MonoDevelop 版本与本书中使用的版本不同。

它是如何工作的...

让我们看看幕后发生了什么。

当 MonoDevelop 创建一个新的 iPhone 项目,或者更好的,iOS 项目时,它会创建一系列文件。解决方案的结构与创建 .NET/Mono 项目时相同,但有一些额外的文件。解决方案文件可以在 MonoDevelop 窗口的左侧的 Solution 面板上查看。如果 Solution 面板不可见,可以通过从菜单栏选择 View | Pads | Solution 来激活它。

这些文件是构成 iPhone 项目的必要文件:

如何工作...

MyFirstiPhoneProjectViewController.xib

这是包含应用程序视图的文件。XIB 文件基本上是具有特定结构的 XML 文件,可以从 Interface Builder 中读取。它们包含有关用户界面的各种信息,例如它包含的控件类型、它们的属性、出口等。

注意

如果双击 MyFirstiPhoneProjectViewController.xib 或任何具有 .xib 后缀的文件,MonoDevelop 将启动 Xcode,并在 Interface Builder 中打开 XIB 文件的内容。

当我们使用 Interface Builder 创建一个新的界面并保存时,它将以 XIB 格式保存。

MyFirstiPhoneProjectViewController.cs

这是实现视图功能的文件。创建时,文件的内容如下:

namespace MyFirstiPhoneProject{
public partial class MyFirstiPhoneProjectViewController : UIViewController{
public MyFirstiPhoneProjectViewController (string nibName, NSBundle bundle) : base (nibName, bundle){}
public override void DidReceiveMemoryWarning (){
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, and so on that aren't in use.
}
public override void ViewDidLoad (){
base.ViewDidLoad ();
//any additional setup after loading the view, typically from a nib.
}
public override void ViewDidUnload (){
base.ViewDidUnload ();
// Release any retained subviews of the main view.
// e.g. myOutlet = null;
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation){
// Return true for supported orientations
return (toInterfaceOrientation != UIInterfaceOrientation.PortraitUpsideDown);
}
}
}

此文件中的代码包含与将要加载的视图相对应的类,以及一些默认方法的重写。这些是我们创建视图控制器时将更频繁使用的方法。以下是每个方法的简要描述:

  • ViewDidLoad: 当控制器视图加载时,会调用此方法。这是我们用来初始化任何附加组件的方法。

  • ViewDidUnload: 当视图从内存中卸载时,会调用此方法。

  • DidReceiveMemoryWarning: 当应用程序收到内存警告时,会调用此方法。此方法负责卸载视图。

  • ShouldAutorotateToInterfaceOrientation: 当我们希望我们的应用程序支持多种方向时,我们会实现此方法。

MyFirstiPhoneProjectViewController.designer.cs

这是包含我们的主窗口类信息的文件,使用 C# 代码编写。MonoDevelop 为项目中添加的每个 XIB 创建一个 .designer.cs 文件。每次我们通过 Interface Builder 保存对 XIB 的更改时,该文件都会自动生成。这是由 MonoDevelop 负责的,以确保我们在界面中做出的更改能够立即反映到代码中。我们不得直接修改此文件,因为当相应的 XIB 使用 Interface Builder 保存时,这些更改将会丢失。另外,如果通过 Interface Builder 没有保存任何内容,如果手动修改它,很可能会导致编译错误。

创建新项目时,文件的内容如下:

namespace MyFirstiPhoneProject{
[Register ("MyFirstiPhoneProjectViewController")]
partial class MyFirstiPhoneProjectViewController{}
}

就像任何其他 .NET 项目一样,会创建一个与解决方案名称相同的命名空间:

namespace MyFirstiPhoneProject

此文件包含我们MyFirstiPhoneProjectViewController类的其他部分声明。它被RegisterAttribute装饰。

RegisterAttribute用于将类暴露给底层的 Objective-C 运行时。字符串参数声明了我们的类将以什么名称暴露给运行时。它可以是我们想要的任何名称,但始终将其设置为我们的 C#类名称是一个好习惯。该属性在 MonoTouch 的内部使用得非常频繁,因为它将所有本地的NSObject类与其 C#对应类绑定在一起。

注意

NSObject是一个根类或基类。在.NET 世界中,它相当于System.Object。两者之间的唯一区别是,所有.NET 对象都继承自System.Object,但在 Objective-C 中,大多数(而不是所有)Objective-C 对象继承自NSObject。所有继承自NSObject的本机对象的 C#对应类也继承自其 MonoTouch NSObject对应类。

AppDelegate.cs

此文件包含AppDelegate类。文件内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace MyFirstiPhoneProject{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to application events from iOS.
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate{
// class-level declarations
UIWindow window;
MyFirstiPhoneProjectViewController viewController;
// This method is invoked when the application has loaded and is ready to run. In this
// method, you should instantiate the window, load the UI into it, and then make the window visible.
// You have 17 seconds to return from this method, or iOS will terminate your application.
public override bool FinishedLaunching (UIApplication app, NSDictionary options){
window = new UIWindow (UIScreen.MainScreen.Bounds);
viewController = new MyFirstiPhoneProjectViewController ("MyFirstiPhoneProjectViewController", null);
window.RootViewController = viewController;
window.MakeKeyAndVisible ();
return true;
}
}
}

第一部分对.NET 开发者来说很熟悉,它包含适当的using指令,用于导入使用所需的命名空间。

using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;

前三个using指令使我们能够使用.NET/Mono 世界的特定和熟悉的命名空间与 MonoTouch 一起使用!

注意

虽然这三个命名空间(System, System.Collections.Generic, System.Linq)提供的功能几乎与它们知名的.NET/Mono 对应项相同,但它们被包含在专门为与 MonoTouch 一起使用而创建的程序集内,并且当然与它一起分发。使用.NET 或 Mono 编译的程序集不能直接用于 MonoTouch 项目。

MonoTouch.Foundation命名空间是对原生 Objective-C 基础框架的包装,其中包含提供基本功能的类。这些对象的名称与原生基础框架中找到的相同“NS”前缀相同。一些例子是NSObject, NSString, NSValue等等。除了NS-前缀对象外,MonoTouch.Foundation命名空间还包含用于绑定到原生对象的属性,例如我们之前看到的RegisterAttributeMonoTouch.UIKit命名空间是对原生 Objective-C UIKit框架的包装。正如其名称所暗示的,该命名空间包含提供界面功能的类、委托和事件。除了两个类DraggingEventArgsZoomingEventArgs外,所有对象的名称都共享相同的“UI”前缀。此时应该很清楚,这两个命名空间对于所有 MonoTouch 应用都是必不可少的,并且它们的对象将被频繁使用。

该类继承自UIApplicationDelegate类,使其成为我们应用的UIApplication Delegate对象。

注意

在 Objective-C 世界中,委托对象的概念与 C# 中的委托有所不同。这将在第二章用户界面:视图中详细解释。

AppDelegate 类包含两个字段和一个方法:

UIWindow window;
MyFirstiPhoneProjectViewController viewController;
public override bool FinishedLaunching (UIApplication app, NSDictionary options) {

UIWindow 对象定义了我们应用程序的主窗口,而 MyFirstiPhoneProjectViewController 是一个变量,它将持有应用程序的视图控制器。

注意

iOS 应用程序通常只有一个窗口,类型为 UIWindowUIWindow 的概念与 .NET System.Windows.Form 有所不同。UIWindow 是应用程序启动时首先显示的控制,所有后续视图都按层次添加在其下方。

如其名称所示,FinishedLaunching 方法在应用程序完成初始化过程时被调用。这是我们必须向用户展示用户界面的方法。此方法的实现必须轻量级,因为如果它不能在调用后及时返回,iOS 将终止应用程序。这是为了通过防止开发者在初始化时执行复杂和长时间运行的任务(例如连接到网络服务以接收数据)来为用户提供更快的用户界面加载时间。应用程序参数是应用程序的 UIApplication 对象,也可以通过静态属性 UIApplication.SharedApplication 访问。选项参数可能包含有关应用程序启动方式的信息,也可能不包含。目前我们不需要它。

如其名称所示,FinishedLaunching 方法在应用程序完成初始化过程时被调用。这是我们必须向用户展示用户界面的方法。此方法的实现必须轻量级,因为如果它不能在调用后及时返回,iOS 将终止应用程序。这是为了通过防止开发者在初始化时执行复杂和长时间运行的任务(例如连接到网络服务以接收数据)来为用户提供更快的用户界面加载时间。应用程序参数是应用程序的 UIApplication 对象,也可以通过静态属性 UIApplication.SharedApplication 访问。选项参数可能包含有关应用程序启动方式的信息,也可能不包含。目前我们不需要它。

此类型项目的 FinishedLaunching 方法的默认实现如下:

window = new UIWindow (UIScreen.MainScreen.Bounds);

UIWindow 对象使用屏幕大小进行初始化。

viewController = new MyFirstiPhoneProjectViewController ("MyFirstiPhoneProjectViewController", null);
window.RootViewController = viewController;

视图控制器被初始化并设置为窗口的根视图控制器。

window.MakeKeyAndVisible ();
return true;

窗口通过window.MakeKeyAndVisible()调用显示在屏幕上,并且该方法返回。此方法必须在FinishedLaunching方法内部调用,否则应用程序的用户界面将不会按预期向用户展示。最后但同样重要的是,return true行通过标记其执行完成来返回方法。

Main.cs

Main.cs文件中是程序运行时生命周期的开始:

namespace MyFirstiPhoneProject{
public class Application{
// This is the main entry point of the application.
static void Main (string[] args){
// if you want to use a different Application Delegate class from "AppDelegate",
// you can specify it here.
UIApplication.Main (args, null, "AppDelegate");
}
}
}

类似于在.NET System.Windows.Forms应用程序中的以下调用,UIApplication.Main方法启动消息循环,或运行循环,负责通过AppDelegate类将通知派发到应用程序,其中包含我们可以重写的处理程序。

// In a .NET application
Application.Run(new Form1());

事件处理程序,如FinishedLaunchingReceiveMemoryWarningDidEnterBackground,只是这些通知中的一些。除了通知派发机制之外,UIApplication对象包含所有存在的UIWindow对象的列表;通常是其中一个。iOS 应用程序必须有一个UIApplication对象,或者从它继承的类,并且该对象必须有一个相应的UIApplicationDelegate对象。这是我们之前看到的AppDelegate类实现。

Info.plist

这个文件基本上是应用程序的设置文件。它具有一个简单的属性值结构,定义了 iOS 应用程序的各种设置,例如它支持的朝向、图标、支持的 iOS 版本、它可以安装的设备等等。如果我们在这个文件上双击 MonoDevelop,它将在嵌入的编辑器中打开,该编辑器专门设计用于.plist文件。这是新项目中我们的文件看起来像:

Info.plist

Info.plist是一个 XML 文件。虽然我们可以在文本编辑器中手动编辑该文件,例如,但不建议这样做。嵌入的编辑器是编辑的最佳方式。

更多内容...

MonoDevelop 为开发 iOS 应用程序提供了许多不同的项目模板。以下是一个列表,描述了每个项目模板的用途:

  • 空项目: 它是一个没有任何视图的空项目。

  • 实用应用程序: 实用应用程序是一种特殊的 iOS 应用程序,它提供一屏功能,在许多情况下还提供另一屏进行配置。

  • 主从应用: 这种类型的项目创建了一个支持在多个屏幕间导航的模板。它包含两个视图控制器。

  • 单视图应用: 这种模板类型是我们在这个菜谱中使用的。

  • 标签页应用: 这是一个添加标签栏控制器以在类似标签的界面中管理两个视图控制器的模板。

  • OpenGL 应用: 这是一个用于创建由 OpenGL 驱动的应用程序或游戏的模板。

这些模板适用于 iPhone、iPad 和通用(iPhone 和 iPad)项目。它们也适用于 Interface Builder 的Storyboarding应用程序设计。

注意

除非另有说明,所有针对 iPhone 的项目模板也适用于 iPod Touch。

MonoTouch 组件列表

MonoTouch 支持的组件可以在以下链接中找到:ios.xamarin.com/Documentation/Assemblies

相关内容

在本章中:

  • 创建 UI

  • 通过出口访问 UI

在这本书中:

第二章,用户界面:视图:

  • 添加和自定义视图

界面构建器

苹果用户界面设计师简介。

如何做...

如果您已安装 iOS SDK,那么您已经在计算机上安装了带有 Interface Builder 的 Xcode。

  1. 前往 MonoDevelop 并打开我们之前创建的项目MyFirstiPhoneProject

  2. 在左侧的解决方案面板上,双击MyFirstiPhoneProjectViewController.xib。MonoDevelop 会启动 Xcode,并在 Interface Builder 中加载文件!

  3. 在工具栏的右侧,在 Xcode 窗口的顶部,选择适当的编辑和查看选项,如下所示:

如何做...

  • 以下截图展示了打开 XIB 文件时的 Interface Builder 外观:

如何做...

它是如何工作的...

现在我们已经加载了带有我们应用程序视图控制器的 Interface Builder,让我们熟悉一下它。

用户界面设计师直接连接到 Xcode 项目。当我们添加一个对象时,Xcode 会自动生成代码来反映我们所做的更改。MonoDevelop 会为我们处理这件事,因为当我们双击 XIB 文件时,它会自动创建一个临时 Xcode 项目,以便我们可以在用户界面中进行我们想要的更改。因此,我们除了设计我们应用程序的用户界面之外,没有更多的事情要做:

界面构建器分为三个区域。下面简要描述每个区域。

  1. 导航区域:在这个区域,我们可以看到 Xcode 项目中的文件。

  2. 编辑区域:这个区域是我们设计用户界面的地方。

  3. 实用区域:这个区域包含检查器面板。检查器是我们配置每个对象的地方,而面板是我们查找对象的地方。

编辑区域分为两个部分。左侧的是设计师,而右侧的是辅助编辑器。在辅助编辑器内部,我们可以看到与设计师中选定的项目对应的底层 Objective-C 源代码文件。尽管我们不需要编辑 Objective-C 源代码,但我们稍后需要辅助编辑器。

更多内容...

我们在 Interface Builder 中看到了XIB文件的样子。但是,关于这些文件还有更多内容。我们之前提到,XIB文件是 Interface Builder 可读取的带有适当信息的 XML 文件。问题是,当项目编译时,编译器也会编译XIB文件,将其转换为二进制等效文件:NIB文件。XIBNIB文件包含完全相同的信息。它们之间的唯一区别是,XIB文件是可读的,而NIB文件则不是。例如,当我们编译创建的项目时,MyFirstiPhoneProjectViewController.xib文件将在输出文件夹中变为MyFirstiPhoneProjectViewController.nib。除了二进制转换外,编译器还会对NIB文件进行压缩。因此,NIB文件的大小将比XIB文件小得多。

关于XIB文件,还有更多内容。开发者如何管理项目中的XIB文件对于应用程序的性能和稳定性非常重要。最好有多个、较小的XIB文件,而不是一个或两个大的文件。这可以通过将用户界面分割成多个XIB文件来实现。这可能看起来有点困难,但正如我们将在本书后面看到的,这实际上非常简单。我们需要许多较小的XIB文件而不是少数几个大的文件,这是因为 iOS 管理内存的方式。当应用程序启动时,iOS 会将NIB文件作为一个整体加载到内存中,然后,其中的所有对象都会被实例化。因此,保留那些不一定总是会被使用的对象在NIB文件中是一种内存浪费。此外,请记住,你正在为移动设备开发,其可用资源与桌面计算机相比并不匹配,无论其能力如何。

更多信息

你可能已经注意到,在检查器面板的属性选项卡中,有一个名为模拟度量的部分。该部分下的选项帮助我们直接在设计器中看到我们的界面在设备的状态栏、工具栏或导航栏下的样子。尽管这些选项保存在XIB文件中,但它们与实际运行时的应用程序无关。例如,如果我们将状态栏选项设置为,这并不意味着我们的应用程序将没有状态栏启动。

注意

状态栏是显示在设备屏幕顶部的一部分,向用户显示某些信息,如当前时间、电池状态、iPhone 上的运营商名称等。

相关内容

在本章:

  • 创建 UI

  • 通过输出访问 UI

  • 添加动作

在本书中:

第二章, 用户界面:视图:

  • 添加和自定义视图

第三章: 用户界面:视图控制器:

  • 视图控制器和视图

创建 UI

在这个菜谱中,我们将学习如何在用户界面中添加和管理控件。

准备工作

让我们在界面中添加一些控件。首先,在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序项目。将项目命名为ButtonInput。当它打开时,双击解决方案面板中的ButtonInputViewController.xib以使用 Interface Builder 打开它。

如何操作...

现在我们有一个新项目,Interface Builder 已打开ButtonInputViewController.xib文件,我们将向其中添加一些控件。

添加一个标签

  1. 如果尚未选择,转到面板并从下拉列表中选择对象。选择标签对象。

  2. 标签拖放到设计器中视图的灰色空间,位于上半部分。

  3. 从左侧和右侧选择并调整标签的大小,使其吸附到当您接近视图边缘时出现的虚线上。

  4. 再次,选择标签后,转到检查器面板,选择属性选项卡,然后在布局部分,点击中间的对齐按钮。恭喜你,你已经在应用程序的主视图中添加了一个标签

添加一个按钮

我们将执行类似的步骤来在我们的界面中添加按钮。

  1. 再次,在面板中,在对象部分,选择按钮对象。它位于标签对象旁边。

  2. 将其拖放到视图的下半部分。将其中心与之前添加的标签的中心对齐。会出现一条虚线,当两个控件的中心几乎对齐时,按钮会自动吸附到它上。

  3. 按钮调整到与标签相同的宽度。由于标签具有透明背景,您无法确切地看到它的宽度,因此当您调整大小并出现三条虚线时,您就会知道按钮的宽度相同。

  4. 现在,让我们向按钮添加一些文本。选择它并转到检查器面板。在属性选项卡中,在标题字段中输入请点击此处!

  5. 添加按钮后,通过菜单栏中的文件 | 保存保存文档。主视图现在应该看起来像以下截图(在此处显示已调整大小):

添加一个按钮

它是如何工作的...

如您所见,尽管 Interface Builder 的一些概念似乎很难,但它使用起来相当简单。它还提供了很多反馈。当我们拖动对象时,光标上会出现一个绿色的圆形十字,表示我们可以将对象放在那里。此外,当我们调整控件大小时,我们会在其旁边看到其尺寸。

您也可以通过修改检查器面板中大小选项卡中的值来调整控件的大小和位置。大小选项卡中的另一个有用功能是自动调整大小自动调整大小为控件提供布局选项,当我们的应用程序需要支持不同的设备方向时,这可以非常有用。您可以选择一个控件,然后点击自动调整大小部分中左边的正方形外部或内部的线条。旁边的图像会动画显示,给您一个印象,当布局改变时控件将如何表现。

更多内容...

现在,让我们尝试在 iOS 模拟器上运行应用程序。回到 MonoDevelop,如果尚未选择,请选择调试 | iPhoneSimulator的项目配置。现在代码中不需要做任何事情;只需点击运行按钮。它是配置组合框右侧的第三个按钮,带有双齿轮图标。当编译完成后,iOS 模拟器将自动启动并运行我们刚刚创建的应用程序!您甚至可以用鼠标点击按钮来“点击”它,并看到它的响应。当然,我们的应用程序目前没有任何其他功能。

设置按钮标题

通过双击并输入首选标题,可以轻松地设置按钮标签的标题。这样做,并观察 Interface Builder 如何显示要执行的操作。

参见

在本章中:

  • 编译

  • 调试我们的应用程序

在本书中:

第二章, 用户界面:视图:

  • 使用按钮接收用户输入

  • 使用标签显示文本

通过出口访问 UI

在本食谱中,我们将讨论出口的概念以及它们与 MonoTouch 的用法。

准备工作

在上一个任务中,我们学习了如何添加控件来形成我们应用程序的基本界面。在本任务中,我们将讨论如何在代码中访问和使用这些控件。启动 MonoDevelop,并打开我们之前创建的项目ButtonInput。通过在解决方案面板中双击它来在 Interface Builder 中打开项目的ButtonInputViewController.xib

如何操作...

  1. 打开辅助编辑器Ctrl-drag从标签到 Objective-C 源文件,如图下所示:如何操作...

    • 当您释放鼠标时,会出现一个上下文窗口,类似于以下截图:

    如何操作...

  2. 在上下文窗口的名称字段中输入labelStatus,,然后点击连接

  3. 对按钮也做同样的操作,命名为buttonTap。通过在菜单栏中选择文件 | 保存或按键盘上的Option - S来保存 Interface Builder 文档。

  4. 回到 MonoDevelop,在ButtonInputViewController类的ViewDidLoad方法中输入以下代码:

    // Create and hook a handler to our button's TouchUpInside event
    // through its outlet
    this.buttonTap.TouchUpInside += delegate( object sender, EventArgs e) {
    this.labelStatus.Text = "Button tapped!";
    };
    
    
  5. 这段代码片段为按钮的 TouchUpInside 事件添加了一个处理程序。这个事件类似于 System.Windows.Forms 中按钮控制的 Clicked 事件。它还展示了匿名方法的使用,这仅仅显示了 MonoTouch 如何为.NET 开发者提供 C#功能。就是这样!我们的应用程序现在已经准备好了,带有功能性的控件。

  6. 在模拟器上编译并运行它。当你点击按钮时,你会看到标签文本的变化。

它是如何工作的...

出口机制基本上是一种将 Interface Builder 对象与代码连接起来的方式。它们是必要的,因为这是我们访问使用 Interface Builder 创建的用户界面对象的唯一方式。这就是 Interface Builder 的工作方式,它不仅仅是 MonoTouch 的解决方案。一个对象的出口提供了一个变量,这样我们就能在项目中使用它。MonoTouch 使开发者的生活变得更加容易,因为当我们创建 Interface Builder 中的出口并将它们连接时,MonoDevelop 会在后台自动生成与这些出口相关的代码。这就是ButtonInputViewController.designer.cs添加的内容,为我们提供了访问我们创建的控件:

[Outlet]
MonoTouch.UIKit.UILabel labelStatus { get; set; }
[Outlet]
MonoTouch.UIKit.UIButton buttonTap { get; set; }

这些属性为我们提供了访问控制的功能。它们被装饰了OutletAttribute。你可以看到属性的名称与我们为出口输入的确切名称完全相同。这非常重要,因为我们只需要为出口提供一次名称,就不必担心在代码的不同部分重复相同的命名约定。注意,控制变量的类型与我们在用户界面中拖放的控制类型完全相同。这些信息存储在XIB文件中,MonoDevelop 会相应地读取这些信息。

还有更多...

要删除出口,你首先必须断开连接。例如,要删除buttonTap出口,在按钮上Ctrl - 点击。在出现的面板中,点击出口旁边的小(x)。这将断开出口的连接。

还有更多...

之后,从 Objective-C 源文件中删除以下代码:

@property (retain, nonatomic) IBOutlet UIButton *buttonTap;

当你保存文档时,出口将从 MonoDevelop 项目中移除。

通过代码添加出口

添加出口的另一种方式是在你的 C#类中创建一个属性,并用OutletAttribute装饰它:

[Outlet]
UIButton ButtonTap { get; set; }

当你在 Interface Builder 中打开XIB文件时,出口已经被添加到用户界面中。然而,你仍然需要将其连接到相应的控制。最简单的方法是在控制上Ctrl - 点击,然后从新引用出口拖动到设计区域左侧的文件所有者对象:

通过代码添加出口

当你释放光标时,从出现的迷你上下文菜单中选择ButtonTap出口。

注意

注意,是 MonoDevelop 监控在 Interface Builder 中所做的更改,而不是反过来。当在 MonoDevelop 项目中做更改时,请确保始终从 MonoDevelop 内部打开 XIB 文件。

参见

在这一章中:

  • 界面构建器

  • 创建 UI

  • 添加动作

在这本书中:

第二章,用户界面:视图:

  • 添加和自定义视图

添加动作

在这个菜谱中,我们讨论了 动作 的概念及其与 MonoTouch 的使用。

准备工作

在这个任务中,我们将讨论如何使用用户界面控件中的动作。在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序项目,并将其命名为 ButtonInputAction。在 Interface Builder 中打开 ButtonInputActionViewController.xib,并添加与上一个任务中 ButtonInput 项目相同的控件、出口和连接。现在不要在项目中添加任何代码。

如何做...

向界面对象添加动作类似于添加出口。

  1. 在 Interface Builder 中,Ctrl-drag 从按钮到源代码文件。在将显示的上下文窗口中,将 连接 字段从 出口 更改为 动作

  2. 名称 字段中输入 OnButtonTap,并在 事件 字段中选择 触摸抬起,如果尚未选择。

  3. 点击 连接 按钮,并保存文档。

  4. ButtonInputActionViewController 类中,添加以下方法:

    partial void OnButtonTap(NSObject sender){
    this.labelStatus.Text = "Button tapped!";
    }
    
    
  5. 应用程序已准备就绪!在模拟器中编译并运行。

  6. 点击按钮,你会看到标签中的文本发生变化,就像我们在上一个应用程序中创建的那样。

它是如何工作的...

Objective-C 中的动作相当于 C# 中的控制事件。它们负责传递各种对象的通告信号。在这个例子中,我们不是在按钮的 TouchUpInside 事件上连接一个处理程序,而是为它添加了一个动作。你可能已经注意到了,我们添加的作为动作处理程序的方法被声明为 partial。这是因为 MonoDevelop 已经为我们声明了一个部分方法声明。这是我们在 Interface Builder 中保存文档时产生的代码:

[Action ("OnButtonTap:")]
partial void OnButtonTap (MonoTouch.Foundation.NSObject sender);

方法的部分声明用 ActionAttribute 标记。这是来自 MonoTouch.Foundation 命名空间的其他属性之一,它允许我们将方法公开为 Objective-C 动作。你看到传递给属性的字符串参数与我们输入到 Interface Builder 中的动作名称完全相同,并在其后面附加了一个冒号(:)。

注意

Objective-C 中的冒号表示参数的存在。例如,doSomethingdoSomething: 不同。它们的区别在于第一个不接受任何参数,而第二个接受一个参数。

动作名称末尾的冒号表示有一个参数;在这种情况下,参数是MonoTouch.UIKit.NSObject sender。这是我们在模拟器中点击按钮时应用程序的外观:

它是如何工作的...

还有更多...

之前的示例只是为了展示如何在 MonoTouch 项目中实现动作。用动作替换事件基本上取决于开发者。

参考信息

在本章中:

  • 界面构建器

  • 创建用户界面

  • 通过出口访问用户界面

编译

在本食谱中,我们将讨论如何使用 MonoDevelop 编译项目。

准备工作

MonoDevelop 提供了许多不同的编译选项。在本任务中,我们将讨论这些选项。我们将使用本章中较早创建的ButtonInput项目进行工作。

如何操作...

  1. 在 MonoDevelop 中加载项目后,转到项目 | ButtonInput 选项

  2. 在出现的窗口中,从左侧面板的构建部分选择iPhone 构建。将项目配置设置为调试,平台设置为iPhoneSimulator。在链接器行为字段中,从下拉菜单中选择链接所有程序集。在SDK 版本字段中,如果尚未选择,请选择默认

  3. 现在,转到左侧面板上的iPhone 应用程序

  4. 摘要选项卡中,在应用程序名称字段中输入Button Input,在版本字段中输入1.0。在部署目标下拉菜单中选择3.0版本。以下截图显示了iPhone 应用程序选项窗口:如何操作...

  5. 点击确定按钮,然后在菜单栏中点击构建 | 构建全部来编译项目。

它是如何工作的...

我们为项目设置了一些选项。让我们看看这些选项为编译定制提供了什么。

iPhone 构建选项

我们设置的第一个选项是关于链接器。链接器是由 MonoTouch 团队开发的一个工具,并在 SDK 中提供。每次编译 MonoTouch 项目时,编译器不仅编译项目,还会编译它需要的所有 MonoTouch 框架的程序集,这样最终的应用程序才能在设备(或模拟器)上运行。这意味着每个应用程序都附带其自己的 MonoTouch 框架编译版本。这样做意味着最终的应用程序包大小相当大。这就是链接器的作用所在。它所做的就是删除所有未使用的代码的程序集,这样编译器就只会编译应用程序需要和使用的代码。这导致应用程序包的大小大大减小:在移动应用程序中这是一个宝贵的资产。特别是,由于苹果通过蜂窝网络对每个文件有 20MB 的下载限制。链接器选项如下:

我们首先设置的选项是关于链接器的。链接器是由 MonoTouch 团队开发的一个工具,并在 SDK 中提供。每次编译 MonoTouch 项目时,编译器不仅编译项目,还会编译它需要的所有 MonoTouch 框架的程序集,以便最终应用程序能够在设备(或模拟器)上运行。这意味着每个应用程序都附带其自己的编译版本的 MonoTouch 框架。这样做意味着最终的应用程序包大小相当大。这就是链接器发挥作用的地方。它所做的就是精简所有未使用的代码,以便编译器只编译应用程序需要和使用的代码。这导致应用程序包的大小大大减小:在移动应用程序中这是一笔宝贵的财富。特别是,由于苹果通过蜂窝网络对每个文件有 20 MB 的下载限制。链接器选项如下:

  • 不链接:当在模拟器上调试时使用此选项。链接器被关闭,所有程序集都按原样编译。这提供了更快的编译时间。

  • 仅链接 SDK 程序集:在这里,链接器仅精简 MonoTouch 框架的程序集。项目程序集保持完整。此选项也有效地减小了最终的应用程序大小。

  • 链接所有程序集:在这里,链接器在所有程序集上被激活。这会稍微减少一些大小。当在代码中使用反射序列化时,需要小心使用此选项。代码中通过反射使用的类型和方法对链接器是透明的。如果代码中存在这种情况,请使用PreserveAttribute对这些类型或方法进行装饰。此属性基本上是通知链接器不要参与精简过程。

在 SDK 版本字段中,我们设置了编译应用程序将使用的iOS SDK 版本。将其设置为默认将自动选择系统上安装的最高 SDK 版本。

iPhone 应用程序选项

在项目选项中“构建”部分的iPhone 应用程序窗口中,我们设置了三个选项。第一个选项是应用程序名称。这是将在模拟器、设备和 App Store 上显示的应用程序包的名称。正如我们所看到的,我们通常可以在名称中添加空格。第二个选项版本定义了应用程序的版本。这是当应用程序最终通过 App Store 分发时将显示的版本。第三个选项部署目标是应用程序可以安装的最小 iOS 版本。

还有更多...

还有另外两个选项窗口。这些是iPhone 包签名iPhone IPA选项。它们将在第十四章的配方中详细讨论,第十四章。部署。

链接器使用

注意

当为模拟器编译时,不建议开启链接器。这是因为编译器不会在iPhoneSimulator平台上编译 MonoTouch 组件;因此它们是直接使用的。开启链接器只会导致编译完成所需的时间更长,对减少最终应用程序包的大小没有影响。

参见

在本书中:

第十四章, 部署:

  • 为应用商店准备我们的应用程序

调试我们的应用程序

在本食谱中,我们将讨论在模拟器上调试 MonoTouch 应用程序的信息。

准备工作

MonoTouch 与 MonoDevelop 结合使用,为在模拟器或设备上调试应用程序提供调试器。在本任务中,我们将了解如何使用调试器调试 MonoTouch 应用程序。打开 MonoDevelop,并加载ButtonInput项目。确保通过在菜单栏上检查视图 | 工具栏 | 调试器来激活调试器工具栏。此外,将项目配置设置为调试 | iPhoneSimulator

如何操作...

  1. MonoDevelop 支持断点。要激活一行上的断点,请点击行号左侧的空间来设置它。在Main.cs文件的以下行设置断点:

    this.labelStatus.Text = "Button tapped!";
    
    
  2. 这就是 MonoDevelop 中的断点看起来像:如何操作...

  3. 通过点击从左侧开始的第二个带有双齿轮图标的按钮,或通过点击菜单栏上的运行 | 调试来编译和调试项目。MonoDevelop 将显示一个消息框,显示消息,等待调试器连接。当模拟器打开且应用程序加载时,请关注应用程序输出面板中提供的信息。点击应用程序按钮。执行将暂停,MonoDevelop 将以黄色突出显示断点。将鼠标移至断点行中的labelStatus变量上。然后 MonoDevelop 将显示一个窗口,其中包含所有已评估变量的成员:如何操作...

  4. 要停止调试,请点击工具栏上的停止按钮,按钮上有一个红色的圆圈中白色的(X)标记。

它是如何工作的...

MonoTouch 与 MonoDevelop 结合使用,使用一个名为Soft Debugger的调试器。之所以称为这个名字,是因为它依赖于运行时和 MonoDevelop 的组合,以提供一个统一的调试平台。当调试过程开始时,MonoDevelop 开始监听来自应用程序的调试信息。在模拟器和设备上调试也是如此。当应用程序执行时,它开始将信息发送回 MonoDevelop,然后 MonoDevelop 在应用程序输出面板中显示这些信息,该面板会自动激活。调试时的典型应用程序输出是加载的组件信息、开始执行的所有线程,以及如果有,可用的断点。

更多内容...

Console.WriteLine() 方法也可以用于调试目的。调试器会处理这个问题,并将方法的输出重定向到 MonoDevelop 的 应用程序输出 窗格。

调试时的应用程序性能

当为了调试目的进行编译时,编译器会产生更大、更慢的代码。这是因为它生成了额外的代码,这些代码是提供适当调试信息所需的。这就是为什么在调试应用程序时,应用程序的执行速度比简单运行情况慢得多。在生成应用程序的发布副本之前,请记住使用 Release | iPhone 项目配置 编译它,以避免运行时执行缓慢。

FinishedLaunching 方法中的断点

不在 FinishedLaunching 方法中编写复杂代码的另一个原因是,在大多数情况下,你将无法对其进行调试。如果你在 FinishedLaunching 中设置断点,应用程序执行将暂停,但当时间限制达到时,iOS 将终止应用程序。

参见

在本书中:

第十四章, 部署:

  • 创建配置文件

  • 在其他设备上进行调试

第二章:用户界面:视图

在本章中,我们将涵盖以下内容:

  • 添加和自定义视图

  • 使用按钮接收用户输入

  • 使用标签显示文本

  • 显示图像

  • 显示和编辑文本

  • 使用键盘

  • 显示进度

  • 显示比屏幕更大的内容

  • 在分页的内容中导航

  • 显示工具栏

  • 创建自定义视图

简介

应用程序的用户界面对于为用户提供与设备(无论是计算机、移动电话还是平板电脑)进行简单通信的方式至关重要。在移动设备上,用户界面不仅至关重要,而且是与用户交互的唯一方式。开发者在为移动设备开发时必须应对各种限制和约束。处理能力不匹配桌面 CPU,屏幕较小,这使得每次选择要显示的信息类型的过程变得更加困难。

在本章中,我们将讨论 iOS 应用程序 UI 的关键组件。我们将了解如何使用和自定义这些组件以创建丰富的应用程序用户界面,并讨论它们与桌面等价物的相似之处和不同之处。以下是这些组件的列表:

  • UIView: 它是一个可定制的容器,是大多数 iOS 用户界面控件的基础对象

  • UIButton: 它是.NET 世界中的Button的等价物

  • UILabel: 它是.NET 世界中的Label的等价物

  • UIImageView: 它是一个允许我们使用图像显示和创建基本动画的视图

  • UITextView: 它是一个允许我们显示可编辑文本的视图

  • UITextField: 它类似于.NET 的TextBox控件

  • UIProgressView: 它显示已知长度的进度

  • UIScrollView: 它提供了显示可滚动内容的能力

  • UIPageControl: 它为内容提供导航功能,内容分为不同的页面或屏幕

  • UIToolbar: 它在屏幕底部提供一个接受可自定义按钮的工具栏

我们还将讨论如何以编程方式创建这些组件的实例并有效地使用它们。

添加和自定义视图

在这个任务中,我们将讨论如何使用Interface Builder添加和自定义UIView

准备工作

使用 Interface Builder 添加视图是一个简单的任务。让我们首先在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目。将项目命名为FirstViewApp,并使用 Interface Builder 打开FirstViewAppViewController.xib文件。

如何操作...

  1. 要向项目中添加视图,请从垫拖动一个UIView对象到主视图上。确保它适合整个窗口区域。

  2. 要使UIView可访问,为其创建一个出口,并将其命名为subView

    注意

    关于出口的概念及其使用方法将在第一章中详细讨论,通过出口访问 UI。

  3. 选择我们刚刚添加的视图,并转到检查器面板。选择属性选项卡,并在背景下拉列表中选择浅灰色

  4. 现在,选择大小选项卡,并将视图的高度减少60点。

  5. 保存文档。

  6. 在模拟器上编译并运行应用程序。结果应该看起来像以下图像:

如何做...

  • 模拟器屏幕的灰色部分是我们刚刚添加的视图。

如何工作...

我们已经成功创建了一个包含一个视图的应用程序。当然,这个应用程序不提供任何功能。它只是为了展示如何添加视图并显示它。

视图是 iOS 应用程序界面的基本组件。每个视觉 UI 对象都继承自UIView类。这个概念与.NET 中的Form有所不同。视图管理内容绘制,接受其他视图作为子视图,提供自动调整大小功能,可以接受自身及其子视图的触摸事件,并且许多属性甚至可以动画化。甚至UIWindow也继承自UIView。iOS 开发者将最频繁地使用这个类或其继承者。

当使用 Interface Builder 添加的视图在运行时首次实例化时,它会通过检查器窗口的大小选项卡设置其Frame属性。Frame属性的类型是RectangleF,它定义了视图在其父视图的坐标系中的位置,在我们的例子中是主窗口,以及其大小以点为单位。

注意

在 Objective-C 中,UIViewFrame属性是CGRect类型。这个类型在 MonoTouch 中没有绑定,而是使用了更熟悉的System.Drawing.RectangleF

父视图是一个视图的父视图,而子视图是其子视图。具有相同父视图的视图被称为兄弟视图。

iOS 的默认坐标系起源于左上角,并向底部和右侧延伸。坐标系的原点始终相同,并且不能通过编程方式更改。

iOS 的坐标系在以下图像中显示:

如何工作...

当设置Frame属性时,它会调整Bounds属性。Bounds属性定义了视图在其自身坐标系中的位置以及其大小以点为单位。它也是RectangleF类型。Bounds属性的默认位置是(0,0),其大小始终与视图的Frame值相同。这两个属性的尺寸相互关联,因此当您更改Frame的大小,Bounds的大小也会相应地更改,反之亦然。您可以将Bounds属性更改为显示视图的不同部分。视图的Frame可以在位置和位置上超出屏幕。也就是说,具有值(x = -50, y = -50, width = 1500, height = 1500)的视图框架是完全可接受的。

还有更多...

另一点需要注意的是,UIView 类继承自 UIResponderUIResponder 类负责响应用和处理事件。当一个视图被添加到另一个视图中时,它成为其响应链的一部分。UIView 类公开了 UIResponder 的属性和方法,我们现在感兴趣描述的是以下两个:

  1. IsFirstResponder 属性:它返回一个布尔值,指示视图是否是第一个响应者。基本上,它表示视图是否有焦点。

  2. ResignFirstResponder(): 它会导致视图失去焦点。

以编程方式添加视图

如果我们想以编程方式添加视图,我们将使用 UIView.AddSubview(UIView) 方法:

this.View.AddSubview(this.subView);

AddSubview() 方法将其参数(类型为 UIView)添加到调用者的子视图列表中,并将其 Superview 参数设置为调用者。除非使用 AddSubview() 方法将其添加到父视图中,否则视图将不会显示。此外,如果视图已经有一个父视图,并且使用其 AddSubview() 方法添加到另一个视图中,则其 Superview 将更改为新调用者的 Superview。这意味着视图每次只能有一个父视图。

注意

当使用 Interface Builder 添加视图作为子视图时,不需要使用 AddSubview() 方法来显示子视图。但是,当以编程方式添加视图时,必须调用 AddSubview() 方法。

以编程方式从父视图中移除视图,请调用其 RemoveFromSuperview() 方法。如果对没有父视图的视图调用此方法,则没有任何作用。当我们想要重用要删除的视图时,必须注意。我们必须保留对其的引用,否则在方法调用后它可能会被释放。

视图内容布局

UIView 的另一个重要属性是 ContentModeContentMode 接受枚举类型 UIViewContentMode 的值。此属性设置 UIView 如何显示其内容。此属性的默认值是 UIViewContentMode.ScaleToFill,它将内容缩放到与视图大小完全匹配,如果需要则进行扭曲。

参见

在本章中:

  • 创建自定义视图

在本书中:

第一章,通过输出口访问 UI。

  • 创建 UI

  • 通过输出口访问 UI

第三章,用户界面:视图控制器:

  • 视图控制器视图

使用按钮接收用户输入

在本食谱中,我们将学习如何使用按钮接收和响应用户输入。

准备工作

我们在 第一章 中使用了按钮,讨论了如何使用 Interface Builder 向用户界面添加控件。在这个任务中,我们将更详细地描述 UIButton 类。在 MonoDevelop 中打开我们在上一个任务中创建的 FirstViewApp 项目。将主视图的高度增加到在 Interface Builder 中覆盖整个屏幕,并保存文档。

如何做到这一点...

我们将在界面中程序化地添加一个按钮,当它被轻触时,将改变视图的背景颜色。

  1. 打开 FirstViewAppViewController.cs 文件,并在类中输入以下代码:

    UIButton buttonChangeColor;
    private void CreateButton (){
    RectangleF viewFrame = this.subView.Frame;
    RectangleF buttonFrame = new RectangleF (10f, viewFrame.Bottom - 200f, viewFrame.Width - 20f, 50f);
    this.buttonChangeColor = UIButton.FromType (UIButtonType.RoundedRect);
    this.buttonChangeColor.Frame = buttonFrame;
    this.buttonChangeColor.SetTitle ("Tap to change view color", UIControlState.Normal);
    this.buttonChangeColor.SetTitle ("Changing color...", UIControlState.Highlighted);
    this.buttonChangeColor.TouchUpInside += this.buttonChangeColor_TouchUpInside;
    this.subView.AddSubview (this.buttonChangeColor);
    }
    bool isRed;
    private void buttonChangeColor_TouchUpInside (object sender, EventArgs e){
    if (this.isRed) {
    this.subView.BackgroundColor = UIColor.LightGray;
    this.isRed = false;
    } else {
    this.subView.BackgroundColor = UIColor.Red;
    this.isRed = true;
    }
    }
    
    
  2. ViewDidLoad() 方法中,添加以下行:

    this.CreateButton ();
    
    
  3. 在模拟器上编译并运行应用程序。当按钮被轻触时,结果应类似于以下截图:

如何做到这一点...

它是如何工作的...

在这个任务中,我们向用户界面添加了一个按钮,该按钮会改变父视图的背景颜色。此外,我们没有使用任何 Interface Builder 就完成了这个任务。

让我们看看代码是如何工作的。

  1. 我们创建一个将保存按钮对象的字段:

    // A button to change the view's background color
    UIButton buttonChangeColor;
    
    
  2. CreateButton() 方法中,我们创建按钮并设置一些属性。

    RectangleF viewFrame = this.subView.Frame;
    RectangleF buttonFrame = new RectangleF (10f, viewFrame.Bottom - 200f, viewFrame.Width - 20f, 50f);
    
    
  3. 首先,我们将视图的 Frame 分配给一个名为 viewFrame 的新变量。然后,我们创建一个名为 buttonFrame 的新 RectangleF 对象,它将被分配给按钮的 Frame 属性。现在我们为按钮有了框架,我们可以初始化它,如下面的代码片段所示:

    //Create the button.
    this.buttonChangeColor = UIButton.FromType (UIButtonType.RoundedRect);
    this.buttonChangeColor.Frame = buttonFrame;
    
    
  4. 按钮是通过静态方法 UIButton.FromType(UIButtonType) 初始化的。此方法接受一个 UIButtonType 类型的参数,并返回 iOS SDK 中包含的预定义按钮类型。这里使用的 UIButtonType.RoundedRect 按钮枚举值是 iOS 按钮的默认类型,具有圆角。在 buttonChangeColor 对象初始化后,我们将它的 Frame 设置为我们之前创建的 RectangleF 值。

  5. 现在我们已经为按钮提供了初始化代码,我们将设置其标题(没错,不止一个):

    // Set the button's titles
    this.buttonChangeColor.SetTitle ("Tap to change view color", UIControlState.Normal);
    this.buttonChangeColor.SetTitle ("Changing color...", UIControlState.Highlighted);
    
    
  6. 我们调用了 UIButton.SetTitle(string, UIControlState) 方法两次。此方法负责为每个给定的按钮状态设置按钮的标题。字符串参数是实际要显示的标题。第二个参数是表示应用于控件的不同控件状态的 UIControlState 类型的枚举。这些控件状态是:

    • Normal: 启用控件的默认空闲状态

    • Highlighted: 控件在触摸事件发生时的状态

    • Disabled: 控件被禁用且不接受任何事件

    • Selected: 控件已被选中。在大多数情况下,此状态不适用。然而,当需要选择状态时,例如在 UISegmentedControl 对象中,它是有用的。

    • Application: 用于应用程序用途的附加控件状态值

    • Reserved: 用于内部框架使用

      因此,使用 UIButton.SetTitle(string, UIControlState) 方法,我们设置了按钮在其默认状态下显示的标题以及在被点击时显示的标题。

  7. 之后,我们设置按钮的 TouchUpInside 事件处理程序,并将其添加为 subView: 的子视图

    this.buttonChangeColor.TouchUpInside += this.buttonChangeColor_TouchUpInside;
    this.subView.AddSubview (this.buttonChangeColor);
    
    
  8. buttonChangeColor_TouchUpInside 事件中,我们根据我们声明的布尔字段更改视图的背景颜色:

    if (this.isRed) {
    
    this.subView.BackgroundColor = UIColor.LightGray;
    this.isRed = false;
    } else {
    this.subView.BackgroundColor = UIColor.Red;
    this.isRed = true;
    }
    
    

这是通过将视图的 BackgroundColor 属性设置为所需的 UIColor 类实例来完成的,如之前突出显示的代码所示。UIColor 对象是一个具有许多不同静态方法和属性的类,允许我们创建不同的颜色对象。

当你在模拟器上编译并运行应用程序时,请注意当你点击按钮时视图的颜色变化,以及当鼠标光标(或设备上的手指)接触按钮时按钮标题的变化。

更多...

在这个任务中,我们使用了 UIButton.FromType(UIButtonType) 静态方法来初始化按钮。以下是 UIButtonType 枚举标志的简要描述:

  • Custom: 它是一个无边框的透明按钮。在创建具有图像背景的自定义按钮时使用此标志。按钮的标题不是透明的。

  • RoundedRect: 它是具有圆角按钮的默认类型。

  • DetailDisclosure: 它是一个圆形的蓝色按钮,用于显示与项目相关的附加信息。

  • InfoLight: 它是一个带有字母(i)的浅色按钮,代表信息显示。

  • InfoDark: 它与 InfoLight 相同,以深色显示。

  • ContactAdd: 它是一个带有白色加号(+)的圆形蓝色按钮。通常用于显示要添加到项目中的联系信息。

创建自定义按钮

要创建具有类型 UIButtonType.Custom 的自定义按钮,请使用 UIButton 类的 SetBackgroundImage()SetImage() 方法。它们都接受一个 UIImage 和一个 UIControlState 参数,因此可以为不同的控件状态设置不同的图像。在设置按钮的图像时,无论是创建自定义按钮还是不是,务必相应地设置 UIButton.ContentMode 属性。

SetImageSetBackgroundImage 方法提供的功能也可以在 Interface Builder 中 Inspector 面板的 Attributes 选项卡的 ImageBackground 字段中完成。从下拉列表框中选择要设置所需图像的状态,并设置图像文件的路径,如下面的屏幕截图所示:

创建自定义按钮

参见

在本章中:

  • 添加和自定义视图

  • 显示图像

  • 创建自定义视图

在本书中:

第一章, 开发工具:

  • 创建 UI,

  • 通过 Outlets 访问 UI

使用标签显示文本

在这个菜谱中,我们将学习如何使用标签向用户显示信息文本。

准备工作

在这个任务中,我们将更详细地描述UILabel类。再次强调,所有的工作都将在没有 Interface Builder 的帮助下完成。打开我们在上一个菜谱中修改的FirstViewApp项目。

如何做到这一点...

我们将程序化地创建一个标签,该标签将显示一些静态的指导文本。

  1. 打开文件FirstViewAppViewcontroller.cs,并在类中输入以下代码:

    UILabel labelInfo;
    private void CreateLabel (){
    RectangleF viewFrame = this.subView.Frame;
    RectangleF labelFrame = new RectangleF (10f, viewFrame.Y + 20f, viewFrame.Width - 20f, 100f);
    this.labelInfo = new UILabel (labelFrame);
    this.labelInfo.Lines = 3;
    this.labelInfo.TextAlignment = UITextAlignment.Center;
    this.labelInfo.BackgroundColor = UIColor.Clear;
    this.labelInfo.TextColor = UIColor.White;
    this.labelInfo.ShadowColor = UIColor.DarkGray;
    this.labelInfo.ShadowOffset = new SizeF (1f, 1f);
    this.labelInfo.Text = "Tap the button below to change the background color." + " Notice the button's title change while it is being tapped!";
    //this.labelInfo.AdjustsFontSizeToFitWidth = true;
    this.subView.AddSubview (this.labelInfo);
    }
    
    
  2. 然后在FinishedLaunching()方法中添加以下行:

    this.CreateLabel();
    
    
  3. 在模拟器上编译并运行应用程序。输出应该看起来像以下截图:

如何做到这一点...

它是如何工作的...

我们已经成功创建了一个标签,并在其中放入了一些信息文本。让我们逐步查看代码,看看实际上发生了什么。

  1. 我们首先在FirstViewAppViewController类中创建一个labelInfo字段,用于存储我们的UILabel对象。

    // A label that displays some text
    UILabel labelInfo;
    
    
  2. 然后我们创建了一个名为CreateLabel()的方法,该方法将实例化和定制标签。就像我们在上一个任务中创建的按钮一样,我们的标签需要一个框架。因此,我们再次创建一个,这取决于视图的Frame属性:

    //Create the appropriate rectangles for the label's frame
    RectangleF viewFrame = this.subView.Frame;
    RectangleF labelFrame = new RectangleF (10f, viewFrame.Y + 20f, viewFrame.Width - 20f, 100f);
    
    
  3. 我们只需将其高度设置为高于按钮的高度,即100点。现在我们已经为标签有了Frame,我们初始化它:

    //Create the label
    this.labelInfo = new UILabel (labelFrame);
    this.labelInfo.Lines = 3;
    this.labelInfo.TextAlignment = UITextAlignment.Center;
    this.labelInfo.BackgroundColor = UIColor.Clear;
    
    
  4. 构造函数被使用,这样框架属性将在初始化时立即设置。行属性决定了标签文本上的文本将被分成多少行。文本对齐属性接受枚举类型UITextAlignment的值,它包含常用的文本对齐标志:居中、左对齐和右对齐。为了使标签的框架完全不可见,以便只有我们的文本可见,我们将BackgroundColor属性设置为UIColor.Clear颜色。

  5. 下一个部分非常有趣。除了能够设置label的字体颜色外,我们还可以为显示的文本设置阴影:

    // Set text color and shadow
    this.labelInfo.TextColor = UIColor.White;
    this.labelInfo.ShadowColor = UIColor.DarkGray;
    this.labelInfo.ShadowOffset = new SizeF (1f, 1f);
    
    
  6. TextColor属性接受UIColor值。要为标签的文本设置阴影,将UIColor设置到ShadowColor属性。然后,将SizeF结构设置到ShadowOffset属性。此属性决定了阴影的确切位置。SizeF的宽度参数定义了阴影的水平位置,而高度参数定义了垂直位置。负值是可以接受的。宽度参数的负值意味着阴影将定位在文本的左侧,而高度参数的负值意味着阴影将定位在文本上方。我们在前面的代码中设置的值意味着阴影将显示在文本右侧 1 点,下方 1 点。

  7. 我们已经准备好了label如何渲染其文本。要分配label将显示的文本,设置其Text属性:

    // Set text to be displayed
    this.labelInfo.Text = "Tap the button below to change the background color." + " Notice the button's title change while it is being tapped!";
    
    
  8. 如您所见,我们为 label 定义了一个相当长的字符串以供显示。最后,将 label 添加到要显示的视图中:

    this.mainView.AddSubview (this.labelInfo);
    
    

此代码给出了本任务 如何做 部分所示的结果。

还有更多...

在前面的代码中,还有一行注释掉的代码:

//this.labelInfo.AdjustsFontSizeToFitWidth = true;

AdjustsFontSizeToFitWidth 属性接受一个布尔值。如果设置为 true,则指示 label 自动更改字体大小,以便它能够适应 label 的宽度。如果 label 支持多行,则将此属性设置为 true 完全没有效果。所以,为了看看这个属性是如何工作的,取消注释它并将 Lines 属性设置为 1。结果将类似于以下内容:

还有更多...

如您所见,label 上的文本需要放大镜才能阅读,所以在这里它不能正常工作。然而,当屏幕上 label 的宽度有限且我们希望文本适应该空间时,AdjustsFontSizeToFitWidth 属性非常有用。为了防止这种情况,将 MinimumFontSize 属性设置为所需的值。正如其名称所暗示的,字体的大小不会小于此属性的值。

UILabel 字体

label 中设置显示文本的字体很简单。使用 UIFont.FromName(string, float) 静态方法设置其 Font 属性。string 参数表示要设置的字体名称,可以包括字体家族和样式,而 float 参数确定其大小。例如,要将 label 的字体设置为 Helvetica Bold,请调用以下方法:

this.labelInfo.Font = UIFont.FromName("Helvetica-Bold", 17f);

如果找不到字体名称,FromName 静态方法将返回 null。对此必须小心,因为当 UILabel.Font 属性被设置为 null 时,将会抛出异常。可以通过调用 UIFont.FontNamesForFamilyName(string) 方法来确定字体家族的可用样式,该方法返回一个包含所有可用字体的 string[] 数组。如果将 Helvetica 字体家族传递给此方法,它将返回一个包含以下项的 string[] 数组:

  • Helvetica-BoldOblique

    Helvetica

    Helvetica-Bold

    Helvetica-Oblique

参见

在本章中:

  • 显示和编辑文本

在本书中:

第一章, 使用连接访问 UI:

  • 创建 UI

第十一章,图形和动画:

  • 显示闪烁的文本

显示图片

在本食谱中,我们将学习如何使用 UIImageView 类在屏幕上显示图片。

准备工作

在这个任务中,我们将了解如何在项目中打包和显示图片。显示需要使用一个图片文件。这里使用的图片文件名为 Toroni.jpg。在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序 项目,并将其命名为 ImageViewerApp

如何做...

  1. 在 Interface Builder 中打开 ImageViewerAppViewController.xib 文件。

  2. 在其视图中添加一个UIImageView对象。将UIImageView对象与名为imageDisplay的出口连接。

  3. 保存文档。

  4. 返回到 MonoDevelop,在ImageViewerAppViewController类中,输入以下代码:

    public override ViewDidLoad(){
    base.ViewDidLoad();
    this.imageDisplay.ContentMode = UIViewContentMode.ScaleAspectFit;
    this.imageDisplay.Image = UIImage.FromFile("Toroni.jpg");
    }
    
    
  5. 解决方案面板中右键单击项目,然后选择添加 | 添加文件。选择要显示的图像文件,然后单击打开

  6. 右键单击您刚刚添加的图像文件,然后单击构建操作 | 内容

  7. 最后,在模拟器上编译并运行应用程序。您添加到项目中的图像应显示在屏幕上,如下面的图像所示:

如何操作...

如何工作...

UIImageView类基本上是一个用于显示图像的自定义视图。在项目中添加图像时,其构建操作必须在解决方案面板中设置为内容,否则图像将不会复制到应用程序包中。

当显示图像时,ContentMode属性非常重要。它设置UIView(在这种情况下为UIImageView)对象显示图像的方式。我们将其设置为UIViewContentMode.ScaleAspectFit,这样它将被调整大小以适应UIImageView的区域,同时保持宽高比不变。如果ContentMode字段保留为默认的Scale To Fill值,输出将类似于以下图像:

如何工作...

要设置UIImageView应显示的图像,我们使用UIImage对象设置其Image属性:

this.imageDisplay.Image = UIImage.FromFile("Toroni.jpg");

ContentMode属性接受一个名为UIViewContentMode的枚举类型。提供的值如下:

  • 填充以适应:这是基本UIView对象的默认值。它将内容缩放到适合视图的大小,必要时更改宽高比。

  • 缩放以适应视图:将内容缩放到适合视图的大小,同时保持其宽高比。视图内容剩余区域变为透明。

  • 填充以适应视图:将内容缩放到填充视图的大小,同时保持其宽高比。内容的一些部分可能被省略。

  • 重绘:当视图的边界发生变化时,其内容不会被重绘。此值会导致内容被重绘。在 CPU 周期方面,绘制内容是一项昂贵的操作,因此在使用此值之前请三思,特别是对于大量内容。

  • 中心:将内容放置在视图的中心,保持其宽高比。

  • 顶部、底部、左侧、右侧、左上角、右上角、左下角右下角:根据相应的值在视图中对齐内容。

还有更多...

UIImage类是表示图像信息的对象。它支持的文件格式列在以下表中:

文件格式 文件扩展名
可移植网络图形 (PNG) .png
联合图像专家小组 (JPEG) .jpg, .jpeg
标记图像文件格式 (TIFF) .tiff, .tif
图像交换格式 .gif
Windows 位图格式 .bmp
Windows 图标格式 .ico
Windows 光标 .cur
XWindow 位图 .xbm

注意

UIImageView 类不支持动画 GIF 图像文件。当动画 GIF 设置为 UIImageViewImage 属性时,只会显示其第一帧。

使用不同屏幕尺寸的图像

为背景创建图像为开发者提供了制作丰富和优雅的用户界面的能力。创建视图背景的首选图像文件格式是 PNG。但是,自从 iPhone 4 发布以来,屏幕分辨率提高了。为了在应用程序中支持两种屏幕分辨率,iOS SDK 提供了一个简单的解决方案。只需将图像保存为高分辨率,并在扩展名之前添加一个 @2x 后缀到文件名,例如,名为 Default.png 的文件的高分辨率版本将被命名为 Default@2x.png。另外,使用这两个文件不需要额外的代码。

只需使用 UIImage.FromBundle(string) 静态方法,传递不带扩展名的文件名,iOS 会根据应用程序运行的设备加载适当的文件。

this.imageDisplay = UIImage.FromBundle("Default");

注意

上述内容仅适用于 PNG 图像文件。

参见

在本章:

  • 添加和自定义视图

在本书中:

第七章,多媒体资源:

  • 加载图像

显示和编辑文本

在这个菜谱中,我们将学习如何显示具有编辑功能的简单文本块。

准备工作

在这个任务中,我们将讨论 UITextView 的用法以及如何使用它显示可编辑文本。在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序 项目,并将其命名为 TextViewApp

如何操作...

  1. 在 Interface Builder 中打开 TextViewAppViewController.xib

  2. 在其视图的顶部附近添加一个 UIButton,在其下方添加一个 UITextView。将这两个对象连接到它们的出口。

  3. 保存文档。

  4. 在 MonoDevelop 中,在 TextViewAppViewController 类中输入以下 ViewDidLoad 方法:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.buttonFinished.Enabled = false;
    this.buttonFinished.TouchUpInside += delegate {
    this.myTextView.ResignFirstResponder();
    } ;
    this.myTextView.Delegate = new MyTextViewDelegate(this);
    }
    Add the following nested class:
    private class MyTextViewDelegate : UITextViewDelegate{
    public MyTextViewDelegate (TextViewAppViewController parentController){
    this.parentController = parentController;
    }
    private TextViewAppViewController parentController;
    public override void EditingStarted (UITextView textView){
    this.parentController.buttonFinished.Enabled = true;
    }
    public override void EditingEnded (UITextView textView){
    this.parentController.buttonFinished.Enabled = false;
    }
    public override void Changed (UITextView textView){
    Console.WriteLine ("Text changed!");
    }
    }//end void MyTextViewDelegate
    
    
  5. 在模拟器上编译并运行应用程序。

  6. 在文本视图中轻触某处,键盘就会出现。输入一些文本,然后轻触 完成 按钮以隐藏键盘。

它是如何工作的...

UITextView 类提供了一个显示可编辑文本块的对象。为了响应用户文本视图中的事件,我们实现了一个继承自 UITextViewDelegate 的类,该类将充当文本视图的代理:

private class MyTextViewDelegate : UITextViewDelegate{
public MyTextViewDelegate (TextViewAppViewController parentController){
this.parentController = parentController;
}
private TextViewAppViewController parentController;

我们声明了一个接受 TextViewAppViewController 对象的构造函数,这样我们就可以有一个控制器实例来访问我们的控件。

然后,我们重写了 UITextViewDelegate 类的三个方法:

public override void EditingStarted (UITextView textView){
this.parentController.buttonFinished.Enabled = true;
}
public override void EditingEnded (UITextView textView){
this.parentController.buttonFinished.Enabled = false;
}
public override void Changed (UITextView textView){
Console.WriteLine ("Text changed!");
}

这些方法是在触发相应事件时将被调用的处理程序。当点击文本时,EditingStarted() 方法被调用。我们在其中启用 完成 按钮。当我们向文本视图中输入一些文本时,Changed() 方法被调用,我们可以在 MonoDevelop 的 应用程序输出 面板中看到 Console.WriteLine() 方法的输出。最后,当我们点击 完成 按钮时,键盘隐藏,并调用允许我们禁用按钮的 EditingEnded() 方法。

ViewDidLoad 方法中,我们为按钮的 TouchUpInside 事件分配一个处理程序:

this.buttonFinished.TouchUpInside += delegate {
this.myTextView.ResignFirstResponder ();
};

我们在它里面调用文本视图的 ResignFirstResponder() 方法,这样当按钮被点击时,文本视图将失去焦点,导致键盘隐藏。然后,我们将我们创建的委托的新实例分配给文本视图的 Delegate 属性,传递 TextViewAppViewController 对象的实例:

this.myTextView.Delegate = new MyTextViewDelegate (this);

还有更多...

Objective-C 中的委托与 C# 中的委托有所不同。尽管在两个世界中它们最常用的用途都是提供对某种形式的事件通知机制的访问,但在 Objective-C 中,这种机制要复杂一些。C# 委托类似于 C 或 C++ 编程语言中的函数指针。它是一个包含对特定签名方法引用的对象。另一方面,Objective-C 委托是一种符合特定协议的对象。它基本上是一个封装了一个或多个方法(以及/或其它成员)作为事件处理器的对象。

注意

Objective-C 协议类似于 C# 中的接口。

代理对象的概念一开始可能看起来有些复杂,但并不难理解。关于事件通知机制,MonoTouch 通过为大多数对象提供事件来简化了 .NET 开发者的工作。

注意

UITextView 类适合显示不带格式的简单文本块。对于显示格式化文本,请使用 UIWebView 类。

相关内容

在本章中:

  • 使用键盘

在本书中:

第五章, 显示数据:

显示和格式化文本

使用键盘

在这个配方中,我们将讨论虚拟键盘使用的一些重要方面。

准备工作

在前两个任务中,我们讨论了可用的文本输入类型。在这个任务中,我们将讨论我们可以做的一些事情,甚至必须做的事情,以有效地使用键盘。在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序 项目,并将其命名为 KeyboardApp

如何做...

按照以下步骤创建项目:

  1. 在 Interface Builder 中打开 KeyboardAppViewController.xib 文件。

  2. 在视图的下半部分添加一个 UITextField 对象,并将其连接到一个出口。

  3. 保存文档。

  4. 回到 MonoDevelop,在 KeyboardAppViewController 类中输入以下代码:

    private NSObject kbdWillShow, kbdDidHide;
    public override void ViewDidLoad(){
    base.ViewDidLoad();
    this.emailField.KeyboardType = UIKeyboardType.EmailAddress;
    this.emailField.ReturnKeyType = UIReturnKeyType.Done;
    this.kbdWillShow = NSNotificationCenter.DefaultCenter. AddObserver (UIKeyboard.WillShowNotification, delegate(NSNotification ntf) {
    RectangleF kbdBounds = UIKeyboard.FrameEndFromNotification (ntf);
    RectangleF textFrame = this.emailField.Frame;
    textFrame.Y -= kbdBounds.Height;
    this.emailField.Frame = textFrame;
    } );
    this.kbdDidHide = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.DidHideNotification, delegate(NSNotification ntf){
    RectangleF kbdBounds = UIKeyboard.FrameEndFromNotification (ntf);
    RectangleF textFrame = this.emailField.Frame;
    textFrame.Y += kbdBounds.Height;
    this.emailField.Frame = textFrame;
    } );
    this.emailField.ShouldReturn = delegate(UITextField textField) {
    return textField.ResignFirstResponder ();
    } ;
    }
    
    
  5. 在模拟器上编译并运行应用程序。

  6. 点击文本字段,并观察它向上移动以避免被键盘遮挡。

  7. 点击键盘上的完成按钮,并观察当键盘隐藏时文本字段返回到其原始位置。

它是如何工作的...

iOS 中有各种类型的键盘。由于屏幕尺寸有限,不能一次性显示所有键,因此根据我们需要用户提供的文本输入设置适当的键盘类型是一种良好的做法。在这个项目中,我们将键盘设置为电子邮件地址类型。我们还通过将其设置为完成来定制了返回键的类型。

this.emailField.KeyboardType = UIKeyboardType.EmailAddress;
this.emailField.ReturnKeyType = UIReturnKeyType.Done;

当键盘显示时,开发者的责任是确保它不会遮挡必要的 UI 元素。在这种情况下,因为我们为用户提供了一些文本输入的能力,我们必须确保文本字段是可见的,以便用户可以看到正在输入的内容。为此,我们在默认的通知中心添加了两个观察者。

// Add observers for the keyboard
this.kbdWillShow = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillShowNotification, delegate(NSNotification ntf) {

NSNotificationCenter为各种通知提供了一个通知机制。

我们通过NSNotificationCenter.DefaultCenter静态属性访问运行时的默认通知中心。使用AddObserver()方法添加了一个观察者,它接受两个参数。第一个参数是一个NSString值,它通知通知中心要观察哪种类型的通知。UIKeyboard类包含预定义的静态属性,具有我们需要的键盘通知类型。UIKeyboard.WillShowNotification被传递,表示观察者将在键盘准备好出现时进行观察并通知。第二个参数是Action<NSNotification>类型,表示当通知发生时将被执行的处理程序。在anonymous()方法内部,我们调用UIKeyboard.FrameEndNotification(NSNotification)方法,该方法返回键盘的边界:

//Get the keyboard's bounds
RectangleF kbdBounds = UIKeyboard.FrameEndNotification (ntf);

然后,我们将文本字段的框架存储在一个变量中,并减少其Y位置值,以便文本字段向上移动:

// Get the text field's frame
RectangleF textFrame = this.emailField.Frame;
// Change the y position of the text field frame
textFrame.Y -= kbdBounds.Height;

当新的框架设置为emailField时,它将移动到新的位置:

this.emailField.Frame = textFrame;

第二个观察者是为了将文本字段移回其原始位置。它与第一个观察者几乎相同,除了两个区别。传递了UIKeyboard.DidHideNotification字符串,指示观察者在键盘关闭后触发处理程序,并将文本字段框架的Y值增加,使文本字段返回到其原始位置。

还有更多...

类中NSObject类型的两个字段包含有关我们添加的观察者的信息。为了移除我们在此处添加的两个观察者,请添加以下代码:

NSNotificationCenter.DefaultCenter.RemoveObserver (this.kbdWillShow);
NSNotificationCenter.DefaultCenter.RemoveObserver (this.kbdDidHide);

注意

在开发使用键盘并支持多种界面方向的应用程序时,必须小心。例如,如果键盘以纵向模式出现,而用户切换到横向模式,则键盘的边界和文本字段的框架都将不同,必须相应地进行调整。

参见

在本章中:

  • 显示和编辑文本

在本书中:

第九章,与设备硬件交互:

  • 旋转设备

  • 调整用户界面

显示进度

在本食谱中,我们将讨论如何显示已知长度的进度。

准备中

在这个任务中,我们将讨论 UIProgressView 控件。此控件提供的功能类似于 .NET 中的 ProgressBar。在 MonoDevelop 中创建一个 iPhone 单视图应用程序 项目,并将其命名为 ProgressApp

如何做...

使用 UIProgressView 类的步骤如下。请注意,在本食谱中,我们将以编程方式添加所有控件,而不使用 Interface Builder:

  1. ProgressAppViewController 类文件中添加以下 using 指令:

    using System.Drawing;
    using System.Threading;
    
    
  2. 添加以下字段:

    UILabel labelStatus;
    UIButton buttonStartProgress;
    UIProgressView progressView;
    float incrementBy = 0f;
    
    
  3. ViewDidLoad 覆盖中输入以下代码:

    // Initialize the label
    this.labelStatus = new UILabel (new RectangleF (60f, 60f, 200f, 50f));
    this.labelStatus.AdjustsFontSizeToFitWidth = true;
    // Initialize the button
    this.buttonStartProgress = UIButton.FromType (UIButtonType.RoundedRect);
    this.buttonStartProgress.Frame = new RectangleF (60f, 400f, 200f, 40f);
    this.buttonStartProgress.SetTitle ("Tap to start progress!", UIControlState.Normal);
    this.buttonStartProgress.TouchUpInside += delegate {
    // Disable the button
    this.buttonStartProgress.Enabled = false;
    this.progressView.Progress = 0f;
    // Start a progress
    new Action (this.StartProgress).BeginInvoke (null, null);
    } ;
    // Initialize the progress view
    this.progressView = new UIProgressView (new RectangleF (60f, 200f, 200f, 50f));
    // Set the progress view's initial value
    this.progressView.Progress = 0f;
    // Set the progress increment value
    // for 10 items
    this.incrementBy = 1f / 10f;
    this.View.AddSubview(this.labelStatus);
    this.View.AddSubview(this.buttonStartProgress);
    this.View.AddSubview(this.progressView);
    
    
  4. 在类中添加以下方法:

    private void StartProgress (){
    while (this.progressView.Progress < 1){
    Thread.Sleep (1000);
    this.BeginInvokeOnMainThread (delegate {
    // Advance the progress
    this.progressView.Progress += this.incrementBy;
    // Set the label text
    this.labelStatus.Text = String.Format ("Current value: {0}", Math.Round ((double)this.progressView.Progress, 2));
    if (this.progressView.Progress == 1){
    this.labelStatus.Text = "Progress completed!";
    this.buttonStartProgress.Enabled = true;
    }
    } );
    }
    }
    
    
  5. 在模拟器上编译并运行应用程序。

  6. 点击按钮并观察进度条填充。

它是如何工作的...

UIProgressView 的当前值由其 Progress 属性表示。其可接受值范围始终是从 01。因此,在初始化时,我们将它设置为 0 以确保条形不会填充任何内容:

this.progressView.Progress = 0f;

由于 UIProgressView 有一个特定的范围,我们需要根据我们需要处理的项的数量分配我们想要增加的值,在这种情况下是 10:

this.incrementBy = 1f / 10f;

在按钮的 TouchUpInside 处理程序中,我们禁用按钮并通过异步调用一个方法来开始我们的进度:

this.buttonStartProgress.TouchUpInside += delegate {
// Disable the button
this.buttonStartProgress.Enabled = false;
this.progressView.Progress = 0;
// Start a progress
new Action (this.StartProgress).BeginInvoke (null, null);
};

StartProgress() 方法中,我们启动一个循环,该循环将处理需要完成的工作。由于工作在异步方法中执行,当我们想要更改控件时,必须在主 UI 线程上通过调用 UIApplicationDelegateBeginInvokeOnMainThread() 方法来完成,该方法接受 NSAction 类型的参数。NSAction 可以接受匿名方法:

this.BeginInvokeOnMainThread (delegate {
// Advance the progress
this.progressView.Progress += this.incrementBy;
// Set the label text
this.labelStatus.Text = String.Format ("Current value: {0}", Math.Round ((double)this.progressView.Progress, 2));
if (this.progressView.Progress == 1){
this.labelStatus.Text = "Progress completed!";
this.buttonStartProgress.Enabled = true;
}
});

更多...

UIProgressView 支持除了默认样式之外的一种样式。将它的 Style 属性设置为 UIProgressViewStyle.Bar,这样条形就会像在接收新电子邮件时 Mail 应用程序中的条形一样显示。

参见

在本章中:

  • 使用按钮接收用户输入

显示屏幕外的内容

在本食谱中,我们将学习如何显示超出屏幕边界的内 容。

准备中

在这个任务中,我们将讨论 UIScrollView 控件。创建一个 iPhone 单视图应用程序 项目,并将其命名为 ScrollApp

如何做...

创建项目的步骤如下:

  1. 在 Interface Builder 中打开 ScrollAppViewController.xib 文件。

  2. 在其视图中添加一个 UIScrollView 对象,并将其连接到一个出口。然后保存文档。

  3. 回到 MonoDevelop,在 ScrollAppViewController 类中添加以下代码:

    // Image view
    UIImageView imgView;
    public override void ViewDidLoad(){
    base.ViewDidLoad();
    this.imgView = new UIImageView (UIImage.FromFile ("Kastoria.jpg"));
    this.scrollContent.ContentSize = this.imgView.Image.Size;
    this.scrollContent.ContentOffset = new PointF (200f, 50f);
    this.scrollContent.PagingEnabled = true;
    this.scrollContent.MinimumZoomScale = 0.5f;
    this.scrollContent.MaximumZoomScale = 2f;
    this.scrollContent.ViewForZoomingInScrollView = delegate(UIScrollView scroll) {
    return this.imgView;
    } ;
    this.scrollContent.ZoomScale = 1f;
    this.scrollContent.IndicatorStyle = UIScrollViewIndicatorStyle.White;
    this.scrollContent.AddSubview (this.imgView);
    }
    
    
  4. 最后,将图像添加到项目中,并将其 Build Action 设置为 Content。一个比模拟器屏幕更大的图像,大小为 320x480 像素,是首选的。

  5. 在模拟器上编译并运行应用程序。在图像上点击并拖动以显示不同的部分。通过按下 Alt + 鼠标点击,可以模拟捏合缩放功能。

它是如何工作的...

UIScrollView 能够管理超出屏幕大小的内容。滚动视图将显示的内容大小必须在它的 ContentSize 属性中设置:

this.scrollContent.ContentSize = this.imgView.Image.Size;

ContentOffset 属性定义了内容在滚动视图边界内的位置:

this.scrollContent.ContentOffset = new PointF (200f, 50f);

这意味着图像的 (x=200, y=50) 点将在 UIScrollView 的原点 (x=0, y=0) 处显示。为了提供内容的缩放功能,我们首先设置 MinimumZoomScaleMaximumZoomScale 属性,这些属性设置了内容的缩放比例的最小值和最大值。值为 2 表示内容将是原来大小的两倍,而值为 0.5 表示内容将是原来大小的一半。

this.scrollContent.MinimumZoomScale = 0.5f;
this.scrollContent.MaximumZoomScale = 2f;

对于实际的缩放操作,我们需要设置 ViewForZoomingInScrollView 属性,它接受一个 UIScrollViewGetZoomView 类型的代理并返回一个 UIView。在这里,我们返回创建的图像视图,但也可以使用另一个更高分辨率的图像视图来提供在缩放时更好的图像质量。在分配代理后,设置初始缩放比例:

this.scrollContent.ViewForZoomingInScrollView = delegate(UIScrollView scroll) {
return this.imgView;
};this.scrollContent.ZoomScale = 1f;

最后,设置滚动视图的指示器样式:

this.scrollContent.IndicatorStyle = UIScrollViewIndicatorStyle.White;

指示器是当滚动或缩放时出现的两条线:一条位于滚动视图的垂直右侧,另一条位于水平底部,它们告知用户内容的位置。与滚动条类似。

还有更多...

为了给用户提供更令人愉悦的滚动和缩放效果,UIScrollView 暴露了 Bounce 属性。默认情况下,它被设置为 true,但我们可以选择将其设置为 false 来禁用它。当内容在水平或垂直方向上达到边界时,弹跳内容会立即给用户反馈,告知他们已经达到了边界。此外,可以单独设置 AlwaysBounceHorizontalAlwaysBounceVertical 属性。设置其中一个或两个这些属性将使滚动视图在相应的方向上始终弹跳内容,即使内容的大小等于或小于滚动视图的边界。因此,实际上并不需要滚动。

UIScrollView 事件

UIScrollView 类公开了一些非常有用的事件:

  • Scrolled: 当内容正在滚动时发生此事件

  • DecelerationStarted: 当用户开始滚动内容时发生此事件

  • DecelerationEnded: 当用户完成滚动并且内容停止移动时发生此事件

注意

如果已将处理程序分配给Scrolled事件,并且设置了ContentOffset属性,则将触发事件。

参见

在本章中:

  • 显示图像

  • 显示和编辑文本

  • 在分页的内容中导航

在分页的内容中导航

在这个菜谱中,我们将学习如何使用UIPageControl类来实现页面导航。

准备工作

UIPageControl为 iOS 应用程序中的多个页面或屏幕提供了一个简单的视觉表示,以点表示。与当前页面相对应的点会被突出显示。它通常与UIScrollView结合使用。在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目,并将其命名为PageNavApp。在项目中添加三个图像文件,并将它们的构建操作设置为内容

如何做到...

创建此项目的步骤如下:

  1. 在 Interface Builder 中打开PageNavAppViewController.xib文件。

  2. 在视图底部添加一个UIPageControl,在其上方添加UIScrollView。调整滚动视图的大小以占用视图剩余的所有空间,并保存文档。

  3. 回到 MonoDevelop 中,在PageNavAppViewController类中输入以下代码:

    UIImageView page1;
    UIImageView page2;
    UIImageView page3;
    public override void ViewDidLoad(){
    base.ViewDidLoad();
    this.scrollView.DecelerationEnded += this.scrollView_DecelerationEnded;
    this.pageControl.ValueChanged += this.pageControl_ValueChanged;
    this.scrollView.Scrolled += delegate {
    Console.WriteLine ("Scrolled!");
    } ;
    this.scrollView.PagingEnabled = true;
    RectangleF pageFrame = this.scrollView.Frame;
    this.scrollView.ContentSize = new SizeF (pageFrame.Width * 3, pageFrame.Height);
    this.page1 = new UIImageView (pageFrame);
    this.page1.ContentMode = UIViewContentMode.ScaleAspectFit;
    this.page1.Image = UIImage.FromFile ("Parga01.jpg");
    pageFrame.X += this.scrollView.Frame.Width;
    this.page2 = new UIImageView (pageFrame);
    this.page2.ContentMode = UIViewContentMode.ScaleAspectFit;
    this.page2.Image = UIImage.FromFile ("Parga02.jpg");
    pageFrame.X += this.scrollView.Frame.Width;
    this.page3 = new UIImageView (pageFrame);
    this.page3.ContentMode = UIViewContentMode.ScaleAspectFit;
    this.page3.Image = UIImage.FromFile ("Parga03.jpg");
    this.scrollView.AddSubview (this.page1);
    this.scrollView.AddSubview (this.page2);
    this.scrollView.AddSubview (this.page3);
    }
    
    
  4. 在类中添加以下方法:

    private void scrollView_DecelerationEnded (object sender, EventArgs e){
    float x1 = this.page1.Frame.X;
    float x2 = this.page2.Frame.X;
    float x = this.scrollView.ContentOffset.X;
    if (x == x1){
    this.pageControl.CurrentPage = 0;
    } else if (x == x2){
    this.pageControl.CurrentPage = 1;
    } else{
    this.pageControl.CurrentPage = 2;
    }
    }
    private void pageControl_ValueChanged (object sender, EventArgs e){
    PointF contentOffset = this.scrollView.ContentOffset;
    switch (this.pageControl.CurrentPage){
    case 0:
    contentOffset.X = this.page1.Frame.X;
    this.scrollView.SetContentOffset (contentOffset, true);
    break;
    case 1:
    contentOffset.X = this.page2.Frame.X;
    this.scrollView.SetContentOffset (contentOffset, true);
    break;
    case 2:
    contentOffset.X = this.page3.Frame.X;
    this.scrollView.SetContentOffset (contentOffset, true);
    break;
    default:
    // do nothing
    break;
    }
    }
    
    
  5. 在模拟器上编译并运行应用程序。在滚动视图中滚动以更改页面。同样,在页面控制上轻触或滚动也可以更改页面。

它是如何工作的...

我们需要做的第一件事是将UIScrollView.PagingEnabled属性设置为true,如下所示:

this.scrollView.PagingEnabled = true;

此属性指示滚动视图以滚动视图边界的倍数进行滚动,从而提供分页功能。之后,准备将在不同页面中显示的图像视图。在这里,我们注意调整每个图像视图的框架,以便它们彼此相邻:

this.page1 = new UIImageView (pageFrame);
//…
pageFrame.X += this.scrollView.Frame.Width;
//…
pageFrame.X += this.scrollView.Frame.Width;

我们为两个事件附加了处理程序。第一个是UIScrollView.DecelerationEnded事件,当用户滚动滚动视图时,它将调整页面控制器的当前页面。当前页面由滚动视图的ContentOffset属性确定:

float x = this.scrollView.ContentOffset.X;
if (x == x1) {
this.pageControl.CurrentPage = 0;
// etc.

我们附加处理器的第二个事件是UIPageControl.ValueChanged事件。在这个处理程序中,我们确保当用户在页面控制上轻触或拖动时,内容会滚动。滚动是在将ContentOffset属性设置为所需图像视图的Frame.X属性时执行的,使用UIScrollView.SetContentOffset(PointF, bool)方法:

case 0:
contentOffset.X = this.page1.Frame.X;
this.scrollView.SetContentOffset (contentOffset, true);
break;
// etc.

SetContentOffset()方法的第二个参数指示滚动视图在滚动时进行动画。

更多...

在这个菜谱中,使用了不同的UIImageView对象。可以根据我们想要显示的内容类型使用任何类型的UIView对象。

正确使用 UIPageControl

用户期望在点击或拖动页面控件时滚动到其他页面。仅用于显示页面索引而不作为完全活动的控件是不好的做法。

参见

在本章中:

  • 显示图像

  • 显示比屏幕更大的内容

显示工具栏

在这个菜谱中,我们将学习如何在应用程序中添加和使用工具栏。

准备工作

UIToolbar 类提供了一个包含各种按钮的工具栏。它是位于视图底部的栏。在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目,并将其命名为 ToolbarApp

如何做...

创建此项目的步骤如下:

  1. 在 Interface Builder 中打开 ToolbarAppViewController.xib 文件,并在其视图底部添加一个 UIToolbar 对象。

  2. 选择默认包含的按钮,并在 Attributes Inspector 面板中将它的 Identifier 设置为 Save

  3. 向工具栏添加一个 Flexible Space Bar Button Item 对象。

  4. 在工具栏上添加另一个按钮,位于上一个对象的右侧,并将其 Identifier 设置为 Reply

  5. 在视图中添加一个 UILabel 对象,并将所有控件(除了灵活空间项)连接到 outlets。

  6. 保存文档。

  7. 回到 MonoDevelop,在 ToolBarAppViewController 类中输入以下代码:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.barSave.Clicked += delegate {
    this.labelStatus.Text = "Button Save tapped!";
    } ;
    this.barReply.Clicked += delegate {
    this.labelStatus.Text = "Button Reply tapped!";
    } ;
    }
    
    
  8. 在模拟器上编译并运行应用程序。点击工具栏的两个按钮,并查看状态字符串在标签上显示。

它是如何工作的...

工具栏包含类型为 UIBarButtonItem 的项。这些项是特殊类型的按钮和间隔。UIBarButtonItem 可以有自定义类型,或者任何在 Interface Builder 的 Identifier 属性中列出的预定义类型。当项是按钮并且它提供的行为包含在这些标识符中时,建议使用它们。这些标识符中的每一个基本上为按钮提供了一种特定的图标,根据其预期用途,用户对它们相当熟悉,因为它们被大多数 iOS 应用程序所使用。注意,我们添加到工具栏中的 Flexible Space Bar Button Item 也是一个 UIBarButtonItem,具有特定的标识符。它的目的是在需要时保持两个按钮之间的距离,例如,当设备在横屏方向旋转并且工具栏调整大小以适应新宽度时。

注意

仅旋转设备不会使 UIToolbar 调整大小。这种行为将在 第三章 中讨论,用户界面:视图控制器

这种类型的按钮在 Interface Builder 中显示,但在运行时并不显示。

在代码中,我们为按钮的 Clicked 事件添加了处理程序,其目的是相当熟悉的:

this.barSave.Clicked += delegate {
this.labelStatus.Text = "Button Save tapped!";
};

当用户点击按钮时,它会被触发。

更多内容...

UIBarButtonItem 类有一个 Style 属性,它决定了按钮的样式。它只能在按钮项的标识符设置为 Custom 时使用。

以编程方式设置 UIToolbar 的项

要将工具栏项设置为 UIToolbar,请使用其 SetItems() 方法的重载之一。在 UIToolbar 中设置两个工具栏项的示例如下:

UIBarButtonItem barSave = new UIBarButtonItem(UIBarButtonSystemItem.Save);
UIBarButtonItem barReply = new UIBarButtonItem(UIBarButtonSystemItem.Reply);
this.toolBar.SetItems(new UIBarButtonItem[] { barSave, barReply }, true);

相关内容

在本书中:

第三章,用户界面:视图控制器:

  • 在不同视图控制器之间导航

创建自定义视图

在本食谱中,我们将学习如何覆盖 UIView 类及其派生类,以创建自定义视图。

准备工作

到目前为止,我们已经讨论了创建 iOS 应用程序时可以使用的许多视图。然而,将会有很多情况需要我们实现自己的自定义视图。在本任务中,我们将看到如何创建自定义视图并使用它。

注意

创建自定义视图在我们需要捕捉触摸或实现其他自定义行为,例如绘图时非常有用。

在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目,并将其命名为 CustomViewApp

如何实现...

完成此项目的步骤如下:

  1. 在项目中添加一个新的 C# 类文件,并将其命名为 MyView

  2. 使用以下代码实现:

    using System;
    using MonoTouch.UIKit;
    using MonoTouch.Foundation;
    using System.Drawing;
    namespace CustomViewApp{
    [Register("MyView")]
    public class MyView : UIView{
    private UILabel labelStatus;
    public MyView (IntPtr handle) : base(handle){
    this.Initialize ();
    }
    public MyView (RectangleF frame) : base(frame){
    this.Initialize ();
    }
    private void Initialize (){
    this.BackgroundColor = UIColor.LightGray;
    this.labelStatus = new UILabel (new RectangleF (0f, 400f, this.Frame.Width, 60f));
    this.labelStatus.TextAlignment = UITextAlignment.Center;
    this.labelStatus.BackgroundColor = UIColor.DarkGray;
    this.AddSubview (this.labelStatus);
    }
    public override void TouchesMoved (NSSet touches, UIEvent evt){
    base.TouchesMoved (touches, evt);
    UITouch touch = (UITouch)touches.AnyObject;
    PointF touchLocation = touch.LocationInView (this);
    this.labelStatus.Text = String.Format ("X: {0} - Y: {1}", touchLocation.X, touchLocation.Y);
    }
    }
    }
    
    
  3. 在 Interface Builder 中打开 CustomViewAppViewController.xib 文件,并在主视图中添加一个 UIView 对象。

  4. 将其连接到出口,并在身份检查器中设置其字段为 MyView

  5. 保存文档。

  6. 在模拟器上编译并运行应用程序。在视图中轻触并拖动,观察触摸坐标在屏幕底部的标签中显示。

它是如何工作的...

创建自定义视图时要注意的第一件事是从 UIView 类派生它们,并用 Register 属性装饰它们:

[Register("MyView")]
public class MyView : UIView

Register 属性基本上将我们的类暴露给 Objective-C 世界。请注意,我们传递给它的参数名称必须与我们在身份检查器中的字段中输入的名称相同。创建以下构造函数以覆盖基类的 UIView(IntPtr) 非常重要。

public MyView (IntPtr handle) : base(handle) {}

当视图通过原生代码初始化时,始终调用此构造函数。如果我们不覆盖它,当运行时尝试在发出内存警告时重新创建它时,对象初始化时将发生异常。本例中使用的另一个构造函数仅作为指导,说明如果视图是程序初始化的,可能会使用什么:

public MyView (RectangleF frame) : base(frame) {}

这两个构造函数都调用了 Initialize() 方法,该方法执行我们需要的初始化,例如创建将使用的标签,设置背景颜色等。

然后,重写了 TouchesMoved 方法。这是当用户在视图中拖动手指时执行的方法。在方法内部,我们从方法的 NSSet 参数中检索 UITouch 对象:

UITouch touch = (UITouch)touches.AnyObject;

注意

NSSet 对象是一个无特定顺序的数据集合。它类似于数组。它的 AnyObject 参数从集合中返回一个对象。

UITouch 对象包含有关用户触摸的信息。我们从其中检索触摸的当前位置:

PointF touchLocation = touch.LocationInView (this);

它的 LocationInView 方法接受一个类型为 UIView 的参数,该参数声明了位置将在哪个视图的坐标系中计算。在这种情况下,我们感兴趣的是 MyView 的坐标。

还有更多...

如果我们想要初始化我们通过编程创建的自定义视图,我们会输入以下代码:

MyView myView = new MyView(new RectangleF(0f, 0f, 320f, 480f));

参见

在本章中:

  • 添加和自定义视图

在本书中:

第三章,用户界面:视图控制器:

  • 视图控制器和视图

第三章。用户界面:视图控制器

在本章中,我们将涵盖:

  • 使用视图控制器加载视图

  • 在不同的视图控制器之间导航

  • 在标签页中提供控制器

  • 创建表格控制器

  • 模态视图控制器

  • 创建自定义视图控制器

  • 高效使用视图控制器

  • 组合不同的视图控制器

  • iPad 视图控制器

  • 为不同设备创建用户界面

简介

到目前为止,我们已经讨论了视图及其使用方法。在大多数现实世界应用场景中,仅使用视图是不够的。Apple 提供了另一个基类,即 UIViewController,它负责管理视图。视图控制器可以响应设备通知,例如当设备旋转时,或者可以提供不同的方式来显示和关闭多个视图,甚至其他视图控制器。

我们还将了解如何使用最常用的视图控制器来创建管理多个视图的应用程序。

这些视图控制器是:

  • UIViewController: 这是所有视图控制器的基类

  • UINavigationController: 这是一个提供多种在不同视图控制器之间导航方式的视图控制器

  • UITabBarController: 这是一个以标签界面显示多个视图控制器的视图控制器

  • UITableViewController: 这是一个用于以列表形式显示数据的视图控制器

  • 针对 iPad 的视图控制器: 这些是仅适用于 iPad 设备的视图控制器

此外,我们还将讨论组合不同的控制器,如何创建自定义控制器并使用它们,以及我们将创建一个可以在 iPhone 和 iPad 上部署的应用程序。

使用视图控制器加载视图

在本食谱中,我们将学习如何使用 UIViewController 类来管理视图。

准备工作

在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 ViewControllerApp

如何操作...

  1. 向项目中添加一个新文件。

  2. 右键点击 Solution 面板中的项目,并选择 Add | New File

  3. 在出现的对话框中,从 MonoTouch 部分选择 iPhone View 并带有 Controller,将其命名为 MainViewController,然后点击 New 按钮。MonoDevelop 将创建一个新的 XIB 文件,并自动打开 MainViewController.cs 源文件。此文件包含一个覆盖 UIViewController 的类,我们可以在其中实现与我们的视图控制器相关的任何代码。

  4. 在 Interface Builder 中打开 MainViewController.xib 文件。

  5. 在视图中添加一个 UILabel

  6. MainViewController 类中创建并连接一个出口,并将其命名为 myLabel

  7. 在标签中输入文本 View in controller!

  8. 保存 XIB 文档。

  9. 在 MonoDevelop 中返回,并在 FinishedLaunching() 方法中输入以下代码:

    MainViewController mainController = new MainViewController ();
    window.RootViewController = mainController;
    
    
  10. 在模拟器上编译并运行应用程序。

它是如何工作的...

当我们在项目中添加一个新的iPhone View with Controller文件时,在这种情况下是MainViewController,MonoDevelop 基本上创建并添加了三个文件:

  1. MainViewController.xib:这是包含控制器的 XIB 文件。

  2. MainViewController.cs:这是实现我们控制器类的 C#源文件。

  3. MainViewController.designer.cs:这是自动生成的源文件,反映了我们在 Interface Builder 中对控制器所做的更改。

注意,我们不需要为视图添加一个出口,因为这是由 MonoDevelop 处理的。我们通过其类初始化控制器:

MainViewController mainController = new MainViewController ();

然后,我们通过控制器中的View显示其视图,将其设置为应用程序窗口的RootViewController

window.RootViewController = mainController;

更多内容...

我们刚刚创建的项目仅展示了我们如何添加一个带有视图的控制器。注意,我们在MainViewController类内部创建了标签的出口,该类在XIB文件中充当文件的所有者对象。为了为MainViewController提供一些功能,在MainViewController.cs文件中的MainViewController类中添加以下方法:

public override void ViewDidLoad (){
base.ViewDidLoad();
this.myLabel.Text = "View loaded!";
}

此方法覆盖了UIViewController.ViewDidLoad()方法,该方法在控制器加载其视图时执行。

要覆盖的UIViewController方法

UIViewController类包含的方法是我们覆盖以使用其功能的方法。其中一些方法包括:

  • ViewDidUnload():当视图被卸载时调用

  • ViewWillAppear():当视图即将在屏幕上显示时调用

  • ViewDidAppear():当视图已显示时调用

  • ViewWillDisappear():当视图即将消失时调用,例如,当另一个控制器即将显示时ViewDidDisappear():当视图消失时调用

参见

在本章中:

  • 在不同的视图控制器之间导航

在本书中:

第一章,开发工具:

  • 使用 MonoDevelop 创建 iPhone 项目

  • 通过出口访问 UI

在不同的视图控制器之间导航

在本食谱中,我们将学习如何使用UINavigationController类在多个视图控制器之间导航。

准备工作

UINavigationController是一个提供具有多个视图控制器的分层导航功能的控制器。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为NavigationControllerApp

如何做到这一点...

  1. 在项目中添加三个新的iPhone View with Controller文件,并分别命名为RootViewController, ViewController1ViewController2

  2. AppDelegate类中添加以下字段:

    UINavigationController navController;
    
    
  3. 在同一类中,在FinishedLaunching方法中,在window.MakeKeyAndVisible()行之上添加以下代码:

    RootViewController rootController = new RootViewController();
    this.navController = new UINavigationController(rootController);
    window.RootViewController = this.navController;
    
    
  4. 在 Interface Builder 中打开RootViewController.xib文件,并添加两个按钮及其相应的出口。分别设置它们的标题为第一个视图第二个视图

  5. 保存文档。

  6. 打开ViewController1.xibViewController2.xib,并在每个视图中添加一个标题为返回根视图的按钮。不要忘记连接按钮与出口并保存文档。

  7. RootViewController类中输入以下代码:

    public override void ViewDidLoad (){
    this.buttonFirstView.TouchUpInside += delegate {
    ViewController1 cont1 = new ViewController1 ();
    cont1.Title = "Controller #1";
    this.NavigationController.PushViewController (cont1, true);
    };
    this.buttonSecondView.TouchUpInside += delegate {
    ViewController2 cont2 = new ViewController2 ();
    cont2.Title = "Controller #2";
    this.NavigationController.PushViewController (cont2, true);
    };
    }
    
    
  8. ViewController1ViewController2类中输入以下内容:

    public override void ViewDidLoad (){
    this.buttonPop.TouchUpInside += delegate {
    this.NavigationController.PopToRootViewController (true);
    };
    }
    
    
  9. 在模拟器上编译并运行应用程序。点击每个按钮以查看和导航到可用的视图。

它是如何工作的...

UINavigationController保留了一个控制器堆栈。UIViewController类有一个名为NavigationController的属性。在正常情况下,这个属性返回 null。但是,如果控制器被推入导航控制器的堆栈中,它将返回正在推入的导航控制器的实例。因此,这样在任何控制器层次结构中的任何一点,都可以提供对导航控制器的访问。要显示一个控制器,我们调用UINavigationController.PushViewController(UIViewController, bool)方法:

this.NavigationController.PushViewController (cont1, true);

注意,RootViewController是导航堆栈中最顶层或根控制器。导航控制器必须至少有一个将充当其根控制器的视图控制器。我们在创建UINavigationController类的实例时设置它:

this.navController = new UINavigationController(rootController);

要返回根控制器,我们在当前控制器中调用PopToRootViewController(bool)方法:

this.NavigationController.PopToRootViewController (true);

两个方法中的bool参数用于在控制器之间进行带有动画的转换。

还有更多...

在这个简单的示例中,我们通过按钮提供了返回根控制器的导航。注意顶部栏中有一个箭头形状的按钮。那个栏被称为导航栏,其类型为UINavigationBar。箭头形状的按钮被称为返回按钮,其类型为UIBarButtonItem。当返回按钮存在时,它总是导航到导航堆栈中的上一个控制器。

管理导航栏按钮

要更改、添加和隐藏导航栏的按钮,我们可以使用当前显示的视图控制器的NavigationItem属性的以下方法:

  • SetLeftBarButtonItem: 它在导航栏的左侧添加一个自定义按钮,替换默认的返回按钮

  • SetRightBarButtonItem: 它在导航栏的右侧添加一个自定义按钮

  • SetHidesBackButton: 它设置默认返回按钮的可见性

要移除或隐藏导航栏上的自定义左侧或右侧按钮,请调用适当的方法,传递null而不是UIBarButtonItem对象。

参见

在本章中:

  • 模态视图控制器

  • 高效使用视图控制器

  • 结合不同的视图控制器

在本书中:

第十一章,图形和动画:

  • 使用动画推送视图控制器

在标签中提供控制器

在这个示例中,我们将学习如何在标签界面中显示多个视图控制器。

准备工作

UITabBarController 提供了一种在相同层次结构级别上显示不同视图控制器的方法,这些控制器被划分为类似标签的界面。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 TabControllerApp

如何做...

向项目中添加两个 iPhone View with Controller 文件。将它们命名为 MainControllerSettingsController

  1. 在 Interface Builder 中打开两个控制器,并设置它们视图的不同背景颜色,然后保存文档。

  2. AppDelegate 类中添加以下字段:

    UITabBarController tabController;
    
    
  3. FinishedLaunching() 方法中输入以下代码,在 window.MakeKeyAndVisible() 行之上:

    MainController mainController = new MainController();
    SettingsController settingsController = new SettingsController();
    mainController.TabBarItem.Title = "Main";
    settingsController.TabBarItem.Title = "Settings";
    this.tabController = new UITabBarController();
    this.tabController.SetViewControllers(new UIViewController[] {
    mainController,
    settingsController
    } , false);
    window.RootViewController = this.tabController;
    this.tabController.ViewControllerSelected += delegate(object sender, UITabBarSelectionEventArgs e) {
    Console.WriteLine("Selected {0} controller.", e.ViewController.TabBarItem.Title);
    } ;
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 点击屏幕底部的每个标签,并查看它们各自显示的视图。控制台输出显示在 MonoDevelop 的 应用程序输出 窗格中。以下截图显示了模拟器的屏幕,其中 设置 标签被选中:

![如何做...](https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_03_01 .jpg)

它是如何工作的...

UITabBarController 显示它管理的每个控制器的一个标签页。该标签页的类型为 UITabBarItem,它可以接受文本和图像。UITabBarController 类包含有关其包含的控制器的信息。我们可以通过 ViewControllerSelected 事件确定用户选择了哪个控制器:

this.tabBarController.ViewControllerSelected += new EventHandler<UITabBarSelectionEventArgs> (delegate(object sender, UITabBarSelectionEventArgs e) {

UITabBarSelectionEventArgs 对象在其 ViewController 属性中持有所选控制器的实例。通过访问 UIViewController.TabBarItem 属性,我们可以确定哪个控制器被选中:

Console.WriteLine ("Controller {0} selected.", e.ViewController.TabBarItem.Title);

在这个示例中,我们输出其 Title 属性。

注意

就像 UIViewController 类的 NavigationController 属性,其中它返回它所属的 UINavigationController 实例一样,TabBarItem 属性只有在控制器是 UITabBarController 的部分时才会持有实例。在其他情况下,它将返回 null

当我们初始化标签控制器时,我们通过 SetViewControllers 方法设置它将包含的控制器,传递一个视图控制器对象的数组:

this.tabController.SetViewControllers(new UIViewController[] {
mainController,
settingsController
});

还有更多...

控制器可以接受我们想要的任意数量的控制器,但如果添加六个或更多,则四个将带有标签显示,而第五个预定义的更多标签将代表所有剩余的控制器。这是为了保持界面易于用户访问,通过保持标签到适合人类手指的特定大小。当我们在一个标签栏控制器界面中添加超过六个控制器时,默认情况下,对象在更多标签的顶部提供一个编辑按钮,允许用户重新排列控制器的顺序。如果我们想排除某些控制器从这个功能中,我们必须从 CustomizableViewControllers 数组中移除它。

有用的 UITabBarController 属性

UITabBarController 类的一些其他有用属性如下:

  • ViewControllers: 返回一个包含所有由标签控制器持有的控制器的数组

  • SelectedIndex: 返回选中标签的零基索引

  • SelectedViewController: 返回当前选中的控制器

关于标签栏界面的重要说明

虽然我们可以在 UITabBarController 中添加任何类型的控制器,但我们不能在另一个控制器中添加 UITabBarController,例如 UINavigationController。然而,我们可以在 UITabBarController 中添加 UINavigationController。这是因为标签栏界面是为了实现不同的控制器作为不同的应用程序模式,而不是层次屏幕。

相关内容

在本章中:

  • 高效使用视图控制器

  • 组合不同的视图控制器

创建表格控制器

在本食谱中,我们将学习如何创建并将 UITableViewController 添加到项目中。

准备工作

使用 UITableViewController 来显示 UITableViewUITableView 提供了一个以列表形式显示数据的界面。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 TableControllerApp

如何操作...

  1. 将项目添加一个iPhone 视图控制器,并将其命名为 TableController

  2. AppDelegate 类的 FinishedLaunching 方法中添加以下代码:

    TableController tableController = new TableController();
    window.RootViewController = tableController;
    
    
  3. TableController 类的继承从 UIViewController 改为 UITableViewController:

    public partial class TableController : UITableViewController
    
    
  4. 在 Interface Builder 中打开 TableController.xib,选择并按退格键删除其视图。

  5. UITableView 拖放到其位置。

  6. 右键单击 UITableView 以显示输出口面板。

  7. 按照以下截图所示,从新引用输出口拖动到文件所有者对象:如何操作...

  8. 当你释放按钮时,从出现的文件所有者对象的小面板中选择视图。这连接了我们刚刚添加的 UITableView文件所有者对象的 view 输出口。

  9. 保存文档。

它是如何工作的...

当我们在 Interface Builder 文档中添加一个 UITableView 时,其视图会显示一些预定义的数据。这些数据仅在设计时出现,而不是在运行时。

UITableViewController 包含一个 UITableView 类型的视图。这个视图负责显示数据,并且可以通过多种方式自定义。

还有更多...

除了 View 属性外,我们还可以通过 TableView 属性访问 UITableViewController 的视图。这两个属性返回相同的对象。

UITableViewController 特定属性

UITableViewController 有一个额外的属性:ClearsSelectionOnViewWillAppear。当它设置为 true 时,控制器将在视图出现时自动清除所选行。

如何使用 UITableView 填充数据将在 第五章 中详细讨论,显示数据。

参见

在本章中:

  • 模态视图控制器

在这本书中:

第五章,显示数据:

  • 在表格中显示数据

模态视图控制器

在这个菜谱中,我们将讨论如何以模态方式显示视图控制器。

准备工作

模态视图控制器 是任何显示在其他视图或控制器之上的控制器。这个概念类似于将 WinForm 作为对话框显示,它控制界面并阻止访问应用程序的其他窗口,除非它被关闭。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 ModalControllerApp

如何做到这一点...

  1. 将两个带有控制器的视图添加到项目中,并分别命名为 MainControllerModalController

  2. 在 Interface Builder 中打开 MainController.xib 文件,并在其视图中添加一个标题为 Present 的按钮。

  3. 创建并连接按钮的适当出口。保存文档并打开 ModalController.xib 文件。

  4. 在其视图中添加一个标题为 Dismiss 的按钮,并为它创建适当的出口。将其视图的背景颜色设置为非白色。

  5. 保存文档并在 MainController 类中输入以下代码:

    public override void ViewDidLoad (){
    this.buttonPresent.TouchUpInside += delegate {
    ModalController modal = new ModalController ();
    this.PresentModalViewController (modal, true);
    };
    }
    
    
  6. 类似地,覆盖 ModalController 类中的 ViewDidLoad() 方法,并在其中输入以下代码:

    this.buttonDismiss.TouchUpInside += delegate {
    this.DismissModalViewControllerAnimated (true);
    };
    
    
  7. 最后,在 FinishedLaunching() 方法中添加代码以显示主控制器:

    MainController mainController = new MainController ();
    window.RootViewController = mainController;
    
    
  8. 在模拟器上编译并运行应用程序。

  9. 点击 Present 按钮并观察模态控制器在主控制器之上显示。

  10. 点击 Dismiss 按钮以隐藏它。

它是如何工作的...

每个控制器对象都有两个处理模态显示和关闭控制器的方法。在我们的示例中,我们调用 PresentModalViewController (UIViewController, bool) 方法来显示控制器:

this.buttonPresent.TouchUpInside += delegate {
ModalController modal = new ModalController ();
this.PresentModalViewController (modal, true);
};

其第一个参数表示我们想要以模态方式显示的控制器,第二个参数确定我们是否想要动画化显示。要关闭控制器,我们调用其 DismissModalViewControllerAnimated(bool) 方法:

this.DismissModalViewControllerAnimated (true);

它只接受一个参数,用于切换消失动画。

更多内容...

我们可以使用控制器的 ModalTransitionStyle 属性定义模态视图控制器呈现的动画类型。在呈现模态控制器之前输入以下代码行:

modal.ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal;

主控制器将翻转以显示模态控制器,给人一种它附着在其后面的印象。

访问模态控制器

每个以模态方式呈现另一个控制器的控制器都通过 ModalController 属性提供对其“子”控制器的访问。如果您需要访问此属性,请确保在调用 DismissModalViewControllerAnimated() 方法之前进行操作。

有多少模态控制器?

理论上,我们可以呈现无限数量的模态控制器。当然,对此有两个限制:

  1. 内存不是无限的:视图控制器会消耗内存,因此我们呈现的视图控制器越多,性能越差。

  2. 糟糕的用户体验:以模态方式呈现许多控制器会让用户感到不适。

参见

在本章中:

  • 在不同视图控制器之间导航

  • 在标签页中提供控制器

在本书中:

第十一章,图形和动画:

  • 使用动画推送视图控制器

创建自定义视图控制器

在本食谱中,我们将学习如何创建 UIViewController 的子类,并使用它来从 XIB 文件中派生视图控制器。

准备工作

在本任务中,我们将看到如何创建一个自定义视图控制器,它将充当基控制器,为其继承者提供共同的功能。我们将添加到我们的基控制器以与继承类共享的功能是,在 MonoDevelop 的 应用程序输出 面板上输出当前的触摸位置。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 CustomControllerApp

如何实现...

  1. 在项目中添加一个新的空 C# 类,并将其命名为 BaseController。

  2. 在 BaseController.cs 文件中输入以下代码:

    using System;
    using System.Drawing;
    using MonoTouch.Foundation;
    using MonoTouch.UIKit;
    public class BaseController : UIViewController{
    //Constructors
    public BaseController (string nibName, NSBundle bundle) : base(nibName, bundle){}
    public override void TouchesMoved (NSSet touches, UIEvent evt){
    base.TouchesMoved (touches, evt);
    // Capture the position of touches
    UITouch touch = (UITouch)touches.AnyObject;
    PointF locationInView = touch.LocationInView (this.View);
    Console.WriteLine ("Touch position: {0}", locationInView);
    }
    }
    
    
  3. 现在,将一个 带有控制器的 iPhone 视图 文件添加到项目中,并将其命名为 DerivedController

  4. 在其类定义中将继承自 UIViewController 的类更改为 BaseControllerpublic partial class DerivedController : BaseController

  5. 最后,将派生控制器的视图添加到主窗口:

    DerivedController derivedController = new DerivedController();
    window. RootViewController = derivedController;
    
    
  6. 在模拟器上编译并运行应用程序。

  7. 在白色表面上点击并拖动鼠标指针,观察 MonoDevelop 的 应用程序输出 面板显示指针在模拟器屏幕上的当前位置。

它是如何工作的...

我们在这里所做的是创建一个可以用于多个 MonoTouch 项目的基控制器类。我们添加到这个控制器中的功能是响应用户触摸。任何继承它的控制器都将继承相同的功能。我们添加到创建BaseController类的代码相当简单。我们实现的构造函数仅仅是 MonoDevelop 在项目中添加新视图控制器时在类实现中创建的构造函数的副本。这里只有一处细微的修改:

public BaseController (string nibName, NSBundle bundle) : base(nibName, bundle){}

这是当通过派生对象的DerivedController()构造函数使用new关键字初始化DerivedController类时将被调用的基构造函数。

derivedController = new DerivedController();

还有更多...

派生控制器也可以添加到另一个XIB文件中,并通过出口直接在代码中使用。

从 XIB 继承视图控制器

如果我们想要创建一个从包含在XIB文件中的控制器派生的基控制器,过程是类似的。

参见

在本章中:

  • 使用控制器加载视图

  • 高效使用视图控制器

在本书中:

第二章,用户界面:视图:

  • 添加和自定义视图

高效使用视图控制器

在这个食谱中,我们将学习关于高效使用视图控制器的基本指南。

准备工作

打开我们在本章前面“在标签中提供控制器”食谱中创建的项目TabControllerApp

如何做到...

  1. 在 Interface Builder 中打开MainController.xib文件,并添加一个UIButton和一个UILabel。通过出口将它们连接起来。

  2. MainController类中输入以下代码:

    private Dictionary<int, string> cacheList;
    private Dictionary<int, string> cacheList;
    public override void DidReceiveMemoryWarning (){
    // Releases the view if it doesn't have a superview.
    base.DidReceiveMemoryWarning ();
    Console.WriteLine("Will clear cache in DidReceiveMemoryWarning...");
    // Release any cached data, images, and so on that aren't in use.
    this.cacheList.Clear();
    }
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    //any additional setup after loading the view, typically from a nib.
    this.cacheList = new Dictionary<int, string>() {
    { 0, "One" },
    { 1, "Two" },
    { 2, "Three" }
    } ;
    this.btnShowData.TouchUpInside += ButtonShowData_TouchUpInside;
    }
    public override void ViewDidUnload (){
    base.ViewDidUnload ();
    // Release any retained subviews of the main view.
    // e.g. myOutlet = null;
    this.lblOutput = null;
    this.btnShowData.TouchUpInside -= ButtonShowData_TouchUpInside;
    this.btnShowData = null;
    }
    private void ButtonShowData_TouchUpInside (object sender, EventArgs e){
    foreach (KeyValuePair<int, string> eachItem in this.cacheList){
    this.lblOutput.Text += string.Format("Key: {0} - Value: {1}", eachItem.Key, eachItem.Value);
    }//end foreach
    }
    }
    
    
  3. 在模拟器上编译并运行应用程序。

  4. 标签页上轻触按钮以显示我们列表的内容。

  5. 切换到设置标签。

  6. 在模拟器的菜单栏中点击硬件 | 模拟内存警告

  7. 在 MainDevelop 的应用程序输出中查看输出,并切换回标签。

它是如何工作的...

此项目不提供任何有用的功能。其主要目的是展示如何正确使用视图控制器。

当 iOS 需要更多内存来执行各种操作时,它会发出内存警告。当发生内存警告时,所有由控制器处理且未使用的 UI 对象都会从内存中清除,以释放更多内存。

模拟器提供了一个方法,让开发者可以通过从菜单栏中选择我们之前选择的硬件 | 模拟内存警告操作来重新创建这样的场景。

由于我们处于设置标签,MainController的内容已被从内存中清除。在DidReceiveMemoryWarning方法中,我们清理任何非 UI 对象,否则这些对象将保留在内存中:

this.cacheList.Clear();

接下来,在ViewDidUnload方法中,我们只需要释放任何由出口保留的 UI 对象。请注意,这就是我们从这些对象可能持有的事件中取消连接处理程序的地方:

this.lblOutput = null;
this.btnShowData.TouchUpInside -= ButtonShowData_TouchUpInside;
this.btnShowData = null;

当我们再次选择 标签时,ViewDidLoad 方法将被再次调用,在控制器视图及其包含的所有视图和输出加载完毕之后。

更多内容...

当发生内存警告时,与 UI 无直接关系的对象实例将保留在内存中。在极少数情况下,如果没有足够的内存来完成特定任务,操作系统可能会终止我们的应用程序,如果它占用了大部分可用内存。为了防止这种情况,我们需要小心清理所有不需要的对象和资源,为 iOS 释放更多内存。

注意

不要在 ViewDidUnload 方法中访问控制器的视图:

public override ViewDidUnload()
{
base.ViewDidUnload();
this.View = null; // Never do this.
}

这是因为即使我们请求视图控制器的 View 属性的返回值,也会导致视图重新加载,这在大多数情况下意味着不会释放内存。

相关内容

在本章中:

  • 在标签页中提供控制器

在本书中:

第一章,开发工具:

  • 界面构建器

第四章,

  • 创建文件

  • 创建 SQLite 数据库

结合不同的视图控制器

在这个菜谱中,我们将学习如何在 UITabBarController 中显示 UINavigationController

准备工作

在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 CombinedControllerApp

如何做到...

创建此项目的步骤如下:

  1. 将三个 iPhone 视图控制器文件添加到项目中,并分别命名为 MainController, SettingsControllerAfterMainController

  2. 在 Interface Builder 中 MainController 视图中添加一个 UIButton,并保存文档。

  3. MainController 类中输入以下代码:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.Title = "Main";
    this.buttonPush.TouchUpInside += delegate {
    this.NavigationController.PushViewController(new AfterMainController(), true);
    };
    }
    
    
  4. AppDelegate 类中添加以下字段:

    UINavigationController navController;
    UITabBarController tabController;
    
    
  5. AppDelegate 类的 FinishedLaunching 方法中添加以下代码:

    MainController mainController = new MainController();
    SettingsController settingsController = new SettingsController();
    this.tabController = new UITabBarController();
    this.navController = new UINavigationController(mainController);
    this.tabController.SetViewControllers(new UIViewController[] {
    this.navController,
    settingsController
    } , false);
    navController.TabBarItem.Title = "Main";
    settingsController.TabBarItem.Title = "Settings";
    window.RootViewController = this.tabController;
    
    
  6. 在模拟器上编译并运行应用程序。在 MainController 中轻触按钮,将 AfterMainController 推送到导航堆栈中,然后切换到 设置 标签。

工作原理...

完整的解决方案可以在 CombinedControllerApp 文件夹中找到。我们通过这个项目实现了提供三个不同屏幕的用户界面,这对用户来说不会造成困惑。

标签栏包含两个系统定义的项目,每个项目代表一个不同的视图控制器。我们在标签栏控制器中实现了第一个项目,使用导航控制器。这样,我们可以提供更多与特定部分的应用程序相关的屏幕(AfterMain),同时让应用程序的另一部分在任何时候都可以直接访问 (设置)

更多内容...

本项目结合了三个不同的控制器(一个 UITabBarController、一个 UINavigationController 和一个 UIViewController)的方式是完全可接受的。我们甚至可以用另一个导航控制器替换第二个标签项,为应用程序的另一个部分提供更多的屏幕,或者甚至添加另一个标签项。

然而,正如本章中 在标签中提供控制器 的配方所述,如果我们在一个 UINavigationController 内添加一个 UITabBarController,这是不可接受的。如果我们想在导航控制器内提供类似标签的行为,我们应该使用它的 UIToolbar 来实现。

参见

在本章中:

  • 在不同视图控制器之间导航

  • 在标签中提供控制器

  • 为不同设备创建用户界面

iPad 视图控制器

在本配方中,我们将讨论仅适用于 iPad 的控制器。

准备工作

创建一个新的 iPad 空项目,并将其命名为 iPadControllerApp

如何实现...

  1. 将两个带有控制器的 iPad 视图添加到项目中,并分别命名为 FirstControllerSecondController。为它们的背景视图设置不同的颜色。在 SecondController 中,在其视图顶部添加一个 UIToolbar,并将其连接到一个出口。

  2. AppDelegate 类中添加以下字段:

    UISplitViewController splitController;
    FirstController firstController;
    SecondController secondController;
    
    

    注意

    UISplitViewController 类仅适用于 iPad。

  3. FinishedLaunching 方法中添加以下代码:

    this.firstController = new FirstController();
    this.secondController = new SecondController();
    this.splitController = new UISplitViewController();
    this.splitController.ViewControllers = new UIViewController[] {
    this.firstController,
    this.secondController
    } ;
    this.splitController.Delegate = new SplitControllerDelegate(this.secondController);
    window.RootViewController = this.splitController;
    
    
  4. AppDelegate 中添加以下嵌套类:

    private class SplitControllerDelegate : UISplitViewControllerDelegate{
    public SplitControllerDelegate (SecondController controller){
    this.parentController = controller;
    }//end ctor
    private SecondController parentController;
    public override void WillHideViewController ( UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc){
    barButtonItem.Title = "First";
    this.parentController.SecToolbar.SetItems (new UIBarButtonItem[] { barButtonItem }, true);
    }
    public override void WillShowViewController ( UISplitViewController svc, UIViewController aViewController, UIBarButtonItem button){
    this.parentController.SecToolbar.SetItems (new UIBarButtonItem[0], true);
    }
    }
    
    
  5. SecondController 类中添加一个属性,它返回我们在 步骤 1 中创建的工具栏出口:

    public UIToolbar SecToolbar{
    get { return this.secToolbar; }
    }
    
    
  6. 最后,在模拟器中编译并运行应用程序。点击工具栏中的按钮,使 FirstController 出现。结果应该类似于以下截图:

如何实现...

它是如何工作的...

完整解决方案可以在 iPadControllerApp 文件夹中找到。有两个特定于 iPad 的控制器:UISplitViewControllerUIPopoverController。它们都被使用在这里,尽管 UIPopoverController 没有直接使用。

UISplitViewController 有助于充分利用 iPad 的更大屏幕。它提供了一种在相同屏幕区域内同时显示两个不同视图的方法。它是通过在纵向全屏显示一个控制器,而在弹出视图中显示另一个较小的控制器来实现的。弹出视图基本上是一个视图,它显示在其他控制器(及其视图)的顶部,就像一个模态视图控制器一样。

为了让用户能够访问我们项目中的两个控制器,我们实现了一个继承自 UISplitViewControllerDelegate 的类,并在 FinishedLaunching() 方法中将它分配给我们的分割控制器。我们创建的 Delegate 对象覆盖了两个方法。在第一个方法中,我们将一个按钮分配给工具栏:

public override void WillHideViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc){
barButtonItem.Title = "First";
this.parentController.SecToolbar.SetItems (new UIBarButtonItem[] { barButtonItem }, true);
}

UISplitViewController从横屏变为竖屏,并且其较小的控制器即将被隐藏时,WillHideViewController()方法会被执行。因此,为了显示它,我们在全屏控制器的工具栏上提供了一个按钮。当我们点击该按钮时,另一个控制器将以弹出窗口的形式出现。当方向从竖屏变为横屏时,较小的控制器出现在较大的控制器旁边,无需弹出窗口。因此,我们不再需要在工具栏上按钮,因此我们重写WillShowViewController以从工具栏中移除按钮。我们通过分配一个空的UIBarButtonItem[]数组来完成此操作:

public override void WillShowViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem button){
this.parentController.SecToolbar.SetItems (new UIBarButtonItem[0], true);
}

更多内容...

当设备旋转时,界面不会自动响应。为了指示视图控制器旋转其视图,我们在分割视图控制器的两个控制器中重写ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation)方法:

public bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation){
return true;
}

iPad 特定控制器使用

尽管所有其他控制器都可用于 iPhone 和 iPad,但这两个控制器不能在 iPhone 上使用。在这种情况下将发生异常。

参考内容

在本章中:

  • 为不同设备创建用户界面

在本书中:

第九章, 与设备硬件交互:

  • 旋转设备

为不同设备创建用户界面

在这个菜谱中,我们将学习如何创建一个同时支持 iPhone 和 iPad 的应用程序。

准备工作

在 MonoDevelop 中创建一个新的通用空项目,并将其命名为UniversalApp

如何操作...

  1. 向项目中添加一个新的iPhone 视图控制器,并将其命名为MainController

  2. 在 Interface Builder 中打开它,并在视图中为其添加一个标签和一个出口。

  3. 在标签中输入文本在 iPhone 上运行!

  4. 将视图的背景颜色改为白色以外的颜色。同样对标签进行操作,并保存文档。

  5. MainViewController类中添加以下代码:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad){
    this.View.Frame = new RectangleF (0f, 0f, 768f, 1024f);
    this.labelMessage.Text = "Running on an iPad!";
    }
    }
    
    
  6. AppDelegate类的FinishedLaunching()中添加以下代码:

    MainController mainController = new MainController();
    window.RootViewController = mainController;
    
    
  7. 在模拟器上编译并运行应用程序。

  8. 读取标签的消息,表明它正在 iPhone 上运行。在 MonoDevelop 中终止执行,并在菜单栏上点击运行 | 运行方式 | iPad 模拟器 x.x(其中x.x是系统上安装的相应 iOS 版本,这里5.0)。如何操作...

  9. 读取表明应用程序正在 iPad 上运行的消息!

它是如何工作的...

当我们在 MonoDevelop 中创建通用项目时,基本区别在于应用程序设置文件(Info.plist),其中声明应用程序支持 iPhone 和 iPad。

事实上,我们添加了一个 带有控制器的 iPhone 视图 并不会阻止我们为两种设备使用相同的控制器。记住,所有控制器都适用于所有设备,除了之前菜谱中讨论的那些。

ViewDidLoad 方法内部,我们通过检查 UIDevice.CurrentDevice 静态属性的 UserInterfaceIdiom 属性来确定应用程序正在运行在哪种设备上,并为视图提供一个大小与 iPad 屏幕尺寸 {768, 1024} 相匹配的框架。

if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)

更多内容...

这将确保根据应用程序运行在哪种设备上,对项目中包含的视图进行尺寸调整。但是,这并不能保证所有控件都将被正确地调整大小和定位。为了避免用户界面杂乱,我们必须确保调整我们控件和视图的 Autosizing 属性,以便它们可以在不同的屏幕上正确地调整大小和定位。

参见

本章内容:

  • 高效使用视图控制器

  • iPad 视图控制器

在本书中:

第一章,开发工具:

  • 使用 MonoDevelop 创建 iPhone 项目

  • 界面构建器

第四章:数据管理

在本章中,我们将涵盖以下主题:

  • 创建文件

  • 创建 SQLite 数据库

  • 插入和更新数据

  • 查询 SQLite 数据库

  • 使用现有的 SQLite 数据库

  • 使用序列化存储数据

  • 使用 XML 存储数据

  • 使用 LINQ to XML 管理 XML 数据

简介

几乎每个应用程序都需要在文件系统中具有永久数据存储。在本章中,我们将讨论不同的数据存储方式。我们将了解如何在 iPhone 应用程序中创建 SQLite 数据库并使用它来管理数据。此外,我们还将学习如何在项目中使用现有的数据库。

SQLite (www.sqlite.org ) 是一个自包含的事务型数据库系统。每个数据库都保存在一个独立的文件中,没有数据库服务器。在 iOS 中,SQLite 支持是原生的。

接下来,我们将看到如何序列化和将对象保存到文件系统中,以及如何使用 LINQ to XML 与 XML 文件一起使用。

创建文件

在这个配方中,我们将学习如何在 iOS 设备的文件系统中创建文件。

准备工作

在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目,命名为FileCreationApp。打开FileCreationAppViewController.xib文件,并在其视图中添加一个UILabel和一个UIButton

如何操作...

FileCreationAppViewController类中输入以下代码:

public override void ViewDidLoad(){
string filePath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "MyFile.txt");
using (StreamWriter sw = new StreamWriter (filePath)){
sw.WriteLine ("Some text in file!");
}
this.btnShow.TouchUpInside += delegate {
using (StreamReader sr = new StreamReader (filePath)){
this.labelMessage.Text = sr.ReadToEnd ();
}
}
};

工作原理...

从这段代码中可以看出,我们可以像在桌面应用程序中一样使用System.IO命名空间中的标准类。我们首先做的事情是为我们将要保存的文件设置一个路径。我们在以下行中这样做:

string filePath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "MyFile.txt");

在 iOS 中,我们无法访问整个文件系统,甚至在应用程序包内部也不行。如果我们尝试在无法访问的文件夹中写入,将会发生异常。因此,我们使用静态的Environment.GetFolderPath(SpecialFolder)方法来检索Personal特殊文件夹,这对应于我们的应用程序的Documents文件夹。注意Path.Combine(string, string)的使用,它将两个字符串组合并返回一个路径。之后,我们创建StreamWriter类的新实例,并使用其WriteLine(string)方法在文件中写入一些文本。

using (StreamWriter sw = new StreamWriter (filePath)){
sw.WriteLine ("Some text in file!");
}

要从文件中检索文本,我们创建StreamReader类的新实例,并使用其ReadLine方法读取文本:

using (StreamReader sr = new StreamReader (filePath)){
this.labelMessage.Text = sr.ReadToEnd ();
}

更多内容...

如果我们想要写入或读取二进制数据,我们可以使用FileStream类。

文档文件夹

应用程序的Documents文件夹仅与该应用程序相关。如果从设备中卸载应用程序,其内容也会被删除。我们在这个文件夹中既有读又有写权限。

参见

在本章中:

  • 使用序列化存储数据

创建 SQLite 数据库

在这个配方中,我们将学习如何创建 SQLite 数据库文件。

准备工作

在 MonoDevelop 中创建一个新的iPhone 单视图应用程序,命名为CreateSQLiteApp。在其视图中添加一个UILabel和一个UIButton

如何操作...

  1. 将项目引用添加到Mono.Data.Sqlite程序集,并在命名空间上添加相应的using指令:

    using Mono.Data.Sqlite;
    
    
  2. CreateSQLiteAppViewController类中输入以下方法:

    private void CreateSQLiteDatabase (string databaseFile){
    try{
    if (!File.Exists (databaseFile)){
    SqliteConnection.CreateFile (databaseFile);
    using (SqliteConnection sqlCon = new SqliteConnection (String.Format ("Data Source = {0};", databaseFile))){
    sqlCon.Open ();
    using (SqliteCommand sqlCom = new SqliteCommand (sqlCon)){
    sqlCom.CommandText = "CREATE TABLE Customers (ID INTEGER PRIMARY KEY, FirstName VARCHAR(20), LastName VARCHAR(20))";
    sqlCom.ExecuteNonQuery ();
    }
    sqlCon.Close ();
    }
    this.lblMessage.Text = "Database created!";
    } else {
    this.lblMessage.Text = "Database already exists!";
    }
    } catch (Exception ex) {
    this.lblMessage.Text = String.Format ("Sqlite error: {0}", ex.Message);
    }
    }
    
    
  3. 然后在ViewDidLoad方法中添加以下代码:

    string sqlitePath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "MyDB.db3");
    this.btnCreate.TouchUpInside += delegate {
    this.CreateSQLiteDatabase (sqlitePath);
    };
    
    

它是如何工作的...

iOS 为 SQLite 数据库提供了原生支持。

  1. 我们可以使用 Mono 的Mono.Data.Sqlite命名空间来访问 SQLite 数据库,如下所示:

    using Mono.Data.Sqlite;
    
    
  2. CreateSQLiteDatabase方法内部,我们首先检查文件是否已存在:

    if (!File.Exists (databaseFile))
    
    
  3. 然后,我们可以继续创建数据库。我们首先使用SqliteConnection.CreateFile(string)静态方法创建文件,如下所示:

    SqliteConnection.CreateFile (databaseFile);
    
    
  4. 我们通过初始化一个SqliteConnection对象并调用其Open()方法来连接新创建的文件。SQLite 数据库的连接字符串为Data Source =,后跟数据库的文件名:

    using (SqliteConnection sqlCon = new SqliteConnection (String.Format ("Data Source = {0};", databaseFile)))
    sqlCon.Open();
    
    
  5. 在数据库中创建一个表时,会初始化一个SqliteCommand对象。我们将一个标准的 SQL 字符串传递给其CommandText属性,并调用ExecuteNonQuery()方法来执行 SQL:

    sqlCom.CommandText = "CREATE TABLE Customers (ID INTEGER PRIMARY KEY, FirstName VARCHAR(20), LastName VARCHAR(20))";
    sqlCom.ExecuteNonQuery ();
    
    

更多内容...

注意 try-catch 块的使用。它用于在数据库创建过程中出现错误时向用户显示消息。

SQL 表创建

在这个任务中,我们为我们的数据库创建了一个简单的表,名为Customers。它包含三个字段。FirstNameLastName字段类型为VARCHAR(20),而ID字段类型为INTEGER,同时也是表的PRIMARY KEY

除了使用 SQL 命令创建表之外,我们还可以使用各种商业或免费的 GUI 工具创建 SQLite 数据库。在互联网上简单搜索就会得到各种结果。

参见

在本章中:

  • 查询 SQLite 数据库

  • 插入和更新数据

  • 使用现有的数据库

插入和更新数据

在这个菜谱中,我们将学习如何将数据写入数据库。

准备工作

对于这个任务,我们将扩展我们在上一个任务中创建的CreateSQLiteApp项目。

如何操作...

  1. 在视图中添加两个额外的按钮。

  2. CreateSQLiteAppViewController类内部,创建两个方法,这些方法将使用上一个任务中的代码连接到数据库文件。这里的区别在于SqliteCommand对象的使用:

    using (SqliteCommand sqlCom = new SqliteCommand (sqlCon)){
    // INSERT statement
    sqlCom.CommandText = "INSERT INTO Customers (FirstName, LastName) VALUES (@firstName, @lastName)";
    
    sqlCom.Parameters.Add (new SqliteParameter ("@firstName", "John"));
    sqlCom.Parameters.Add (new SqliteParameter ("@lastName", "Smith"));
    //UPDATE statement
    //sqlCom.CommandText = "UPDATE Customers SET FirstName = 'James' WHERE LastName = @lastName";
    
    //sqlCom.Parameters.Add (new SqliteParameter ("@lastName", "Smith"));
    sqlCom.ExecuteNonQuery ();
    }
    
    

它是如何工作的...

要在 SQLite 表中插入和更新数据,我们分别使用常见的INSERTUPDATE语句。代码中高亮的部分表示了 SQLite 参数的使用。这两个语句都在sqlCom.ExecuteNonQuery()行上对数据库进行执行。ExecuteNonQuery的返回值类型为int,表示受影响的表中的行数。所以,如果我们像以下示例中那样调用该方法,我们会得到输出1,表示影响了一行:

Console.WriteLine(sqlCom.ExecuteNonQuery());

更多内容...

由于我们已经使用了上一个任务中的项目,其中我们提供了创建数据库文件的代码,因此我们应该在我们的每个执行数据操作的方法的开始处添加以下代码:

if (!File.Exists (databaseFile)) {
this.lblMessage.Text = "Database file does not exist. Tap the appropriate button to create it.";
return;
}

这是为了确保当用户在没有任何数据库文件的情况下点击 插入更新 按钮时,不会出现异常。

SQLite 性能

虽然 SQLite 提供了出色的性能和可移植性,但它在很大程度上依赖于其宿主文件系统,无论它存储在哪个平台上。如果您想执行多个并发 INSERTUPDATE 语句,请考虑使用 SqliteTransaction。除了性能上的好处外,通过将多个语句批处理在一起,事务提供了一种在出现问题时回滚操作的方法。

参见

在本章中:

  • 创建文件

  • 创建 SQLite 数据库

  • 查询 SQLite 数据库

查询 SQLite 数据库

在本配方中,我们将学习如何从 SQLite 数据库中检索数据。

准备工作

再次使用 CreateSQLiteApp 项目。完成此任务后,该项目将是一个完整的 SQLite 管理应用程序。

如何操作...

  1. 在视图中添加另一个按钮。

  2. CreateSQLiteAppViewController 类内部,添加一个处理查询的方法。执行查询的部分如下:

    using (SqliteCommand sqlCom = new SqliteCommand (sqlCon)){
    sqlCom.CommandText = "SELECT * FROM Customers WHERE LastName = @lastName";
    sqlCom.Parameters.Add (new SqliteParameter ("@lastName", "Smith"));
    using (SqliteDataReader dbReader = sqlCom.ExecuteReader ()){
    if (dbReader.HasRows){
    while (dbReader.Read ()){
    this.lblMessage.Text += String.Format ("ID: {0}\n", Convert.ToString (dbReader["ID"]));
    this.lblMessage.Text += String.Format ("First name: {0}\n", Convert.ToString (dbReader["FirstName"]));
    this.lblMessage.Text += String.Format ("Last name: {0}\n", Convert.ToString (dbReader["LastName"]));
    }
    }
    }
    }
    
    

它是如何工作的...

要在 SQLite 数据库上执行查询,我们创建一个简单的 SELECT 语句并将其分配给 SqliteCommand 对象的 CommandText 属性:

sqlCom.CommandText = "SELECT * FROM Customers WHERE LastName = @lastName";

SELECT 关键字后面的星号 (*) 表示我们想要检索表中的所有字段。执行 SQL 查询的最简单方法是通过使用 SqliteDataReader 类:

using (SqliteDataReader dbReader = sqlCom.ExecuteReader ())

SqliteCommand.ExecuteReader() 方法在表上执行查询并创建一个 SqliteDataReader 对象。现在我们有了对象,我们首先检查表是否包含任何行:

if (dbReader.HasRows)

如果上述返回 true,我们开始通过传递 SqliteDataReader.Read() 方法在 while 循环中逐行前进:

while (dbReader.Read ())

我们现在可以通过传递每个字段的名称作为 SqliteDataReader 实例的索引来检索每个字段的值:

this.lblMessage.Text += String.Format ("ID: {0}\n", Convert.ToString (dbReader["ID"]));
//...

由于索引的 dbReader 变量返回的是 System.Object 类型的对象,我们使用 Convert.ToString(object) 静态方法将其转换为字符串。

更多内容...

SQLite 数据库不是线程安全的。如果我们想在执行查询的同时,另一个线程可能正在执行 INSERTUPDATE 语句,那么提供某种同步机制会更好。

查询性能

虽然运行 iOS 平台的设备在性能上优于一些较老的桌面计算机,但它们的资源仍然有限。在查询大型数据库中的数据时,请考虑使用 SQL WHERE 语句缩小结果,以获取特定时间所需的数据。此外,如果不需要排序,请避免使用 ORDER BY 语句。

参见

在本章中:

  • 创建 SQLite 数据库

  • 插入和更新数据

  • 使用现有的 SQLite 数据库

使用现有的 SQLite 数据库

在这个菜谱中,我们将讨论如何将现有的 SQLite 数据库文件包含到我们的项目中。

准备工作

使用某种前端创建数据库总是更容易。在这个任务中,我们将了解如何将现有的 SQLite 数据库与 iPhone 项目集成。创建一个新的 iPhone 单视图应用程序 项目,并将其命名为 SqliteIntegrationApp

如何做到这一点...

  1. 在 Interface Builder 中的视图上添加一个 UIButton 和一个 UILabel。将它们与适当的出口连接起来。确保标签足够高,可以显示六行,并在 检查器 选项卡中设置其 # Lines 字段。

  2. 在 MonoDevelop 中,在 解决方案 面板中右键单击项目,然后点击 添加文件...

  3. 在显示的对话框中,导航到数据库文件所在的路径并选择它。文件将被添加到项目中。

  4. 右键单击它,然后选择 构建操作 | 内容

  5. SqliteIntegrationAppViewController 类中输入以下方法:

    private string CopyDatabase (){
    string docPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
    string databaseFile = "CustomersDB.db3";
    string databasePath = Path.Combine (docPath, databaseFile);
    if (!File.Exists (databasePath)){
    File.Copy (databaseFile, databasePath);
    }
    return databasePath;
    }
    private void QueryData (object sender, EventArgs e)
    { //... }
    
    
  6. QueryData 方法的功能与之前任务中执行查询的方法相同。主要区别在于,在这个项目中,它将作为按钮的 TouchUpInside 事件处理程序。

  7. 添加一个类型为 string 的字段,用于存储数据库路径:

    private string database;
    
    
  8. 最后,在 ViewDidLoad 方法中:

    this.database = this.CopyDatabase ();
    this.buttonQuery.TouchUpInside += this.QueryData;
    
    
  9. 编译并运行应用程序。点击按钮,并观察数据库内容在标签中显示。

工作原理...

我们在这里所做的是将之前任务中讨论的各种实践相结合。当我们想在项目中添加各种文件时,我们设置它们的 构建操作内容 非常重要。任何标记为 内容 的文件都会在应用程序包中按原样复制。在这种情况下,文件是一个 SQLite 数据库,因此我们需要在运行时对它有写入权限。我们需要将其复制到应用程序的 Documents 文件夹中。这就是 CopyDatabase() 方法在这一点上所做的事情:

if (!File.Exists (databasePath)){
File.Copy (databaseFile, databasePath);
}

检查文件是否已经存在非常重要,这样它就不会在应用程序下次执行时被覆盖。

还有更多...

如果遇到 SqliteException,首先检查是否由于某种原因数据库文件没有被复制到正确的文件夹。

参见

在本章中:

  • 创建文件

  • 创建 SQLite 数据库

使用序列化存储数据

在这个菜谱中,我们将讨论使用 .NET 序列化来存储 C# 对象。

准备工作

在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序 项目。将其命名为 SerializationApp

如何做到这一点...

  1. 在视图上添加两个按钮和一个标签,在 Interface Builder 中。在 SerializationAppViewController.cs 文件中添加以下使用指令:

    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    
    
  2. AppDelegate 类中添加以下方法:

    private void Serialize (){
    
    CustomerData custData = new CustomerData ();
    custData.ID = 1;
    custData.FirstName = "John";custData.LastName = "Smith";
    using (MemoryStream ms = new MemoryStream ()){
    BinaryFormatter bf = new BinaryFormatter ();
    bf.Serialize (ms, custData);
    ms.Seek (0, SeekOrigin.Begin);
    this.objBuffer = new byte[ms.Length];
    ms.Read (this.objBuffer, 0, this.objBuffer.Length);
    }
    this.labelOutput.Text = "Customer data serialized.\n";
    }
    private void DeserializeAndDisplay (){
    if (null != this.objBuffer){
    CustomerData custData = null;
    using (MemoryStream ms = new MemoryStream (this.objBuffer)){
    ms.Seek (0, SeekOrigin.Begin);
    BinaryFormatter bf = new BinaryFormatter ();
    custData = (CustomerData)bf.Deserialize (ms);
    }
    this.labelOutput.Text += String.Format ("ID: {0}\n \tFirst name: {1}\n\tLast name: {2}", custData.ID, custData.FirstName, custData.LastName);
    } else {
    this.labelOutput.Text = "Buffer is null!";
    }
    }
    
    

完整代码可以在 SerializationApp 项目中找到。

工作原理...

使用 MonoTouch 进行序列化与桌面 C#应用程序中的操作相同。在突出显示的代码中指示的CustomerData类是一个简单的对象,它包含一个整数和两个字符串属性。

  1. 要序列化对象,我们首先初始化一个MemoryStream

    using (MemoryStream ms = new MemoryStream ())
    
    
  2. 然后我们使用BinaryFormatter类来序列化对象并将其存储到流中:

    BinaryFormatter bf = new BinaryFormatter ();
    bf.Serialize (ms, custData);
    
    
  3. 序列化后,我们将流的位置重置为其开始处,并将数据从流中读取到字节数组中:

    ms.Seek (0, SeekOrigin.Begin);
    this.objBuffer = new byte[ms.Length];
    ms.Read (this.objBuffer, 0, this.objBuffer.Length);
    
    
  4. 要从缓冲区反序列化对象,过程类似,但顺序相反。在MemoryStream初始化后,我们将流的位置重置为其开始处,并使用BinaryFormatterDeserialize方法。它返回一个System.Object类型的对象,因此我们需要将其转换为我们的对象类型:

    ms.Seek (0, SeekOrigin.Begin);
    BinaryFormatter bf = new BinaryFormatter ();
    custData = (CustomerData)bf.Deserialize (ms);
    
    

还有更多...

在使用 MonoTouch 序列化对象时,有一件重要的事情要记住,那就是用PreserveAttribute装饰将要序列化的对象。此属性指示链接器避免删除未使用的成员,保持对象完整。在这个任务中声明的CustomerData类如下:

[Preserve()]
[Serializable()]
public class CustomerData

可序列化对象

这是一个简单的示例,用于展示在 iOS 应用程序中使用二进制序列化的用法。可以通过继承ISerializable C#接口来定制用于序列化的对象。

当创建用于序列化的对象时,不要忘记用SerializableAttribute标记它。如果尝试序列化未标记此属性的对象,将会发生异常。

相关内容

在本章中:

  • 使用 XML 存储数据

在这本书中:

第一章,开发工具:

  • 编译

使用 XML 存储数据

在这个菜谱中,我们将讨论如何使用 XML 序列化存储数据。

准备工作

在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目,并将其命名为XMLDataApp

如何操作...

  1. 在视图中添加一个UIButton和一个UILabel。将上一个任务中的CustomerData类添加到项目中。在XMLDataAppViewController.cs文件中添加以下using指令:

    using System.Xml.Serialization;
    using System.Xml;
    using System.Text;
    
    
  2. XMLDataAppViewController类中添加以下方法:

    private void CreateXML (){
    CustomerData custData = new CustomerData ();
    custData.ID = 1;
    custData.FirstName = "John";
    custData.LastName = "Smith";
    this.sb = new StringBuilder ();
    XmlSerializer xmlSer = new XmlSerializer (typeof(CustomerData));
    xmlSer.Serialize (XmlWriter.Create (sb), custData);
    this.labelOutput.Text = sb.ToString ();
    }
    
    

完整的解决方案可以在XMLDataApp项目中找到。

工作原理...

在这个例子中,我们使用XmlSerializer类将对象序列化为 XML。StringBuilder对象将保存 XML 数据:

this.sb = new StringBuilder ();
XmlSerializer xmlSer = new XmlSerializer (typeof(CustomerData));

要初始化XmlSerializer,我们使用它的XmlSerializer(System.Type)构造函数,将我们想要序列化的对象的类型作为参数传递:(typeof(CustomerData))。然后我们调用它的Serialize(XmlWriter, object)方法,该方法将执行实际的序列化并将输出 XML 存储到StringBuilder中,通过XmlWriter类,如下所示:

xmlSer.Serialize (XmlWriter.Create (sb), custData);

当你编译并运行应用程序时,你将在标签中看到输出 XML。

还有更多...

使用 XML 存储数据有两个主要优势:

  1. 输出是纯文本,与二进制序列化不同,这使得它易于阅读。

  2. 数据可以传输或接收来自不同类型的应用程序、网站等,这些应用程序和网站不一定是用 C#编写的。

反序列化

要从 XML 反序列化我们的对象,我们将使用以下行:

custData = (CustomerData)xmlSer.Deserialize (new StringReader (sb.ToString ()));

相关内容

在本章中:

  • 使用序列化存储数据

  • 使用 LINQ to XML 管理 XML 数据

使用 LINQ to XML 管理 XML 数据

在这个配方中,我们将学习如何使用语言集成查询(LINQ)来管理 XML 数据。

准备工作

在这个任务中,我们将使用上一个任务中的项目XMLDataApp。在 MonoDevelop 中打开它。

如何操作...

  1. 在视图中添加另一个UIButton。在XMLDataAppViewController.cs文件中添加对System.Xml.Linq程序集的引用和以下using指令:

    using System.Linq.Xml;
    
    
  2. XMLDataAppViewController类中输入以下方法:

    private void ReadXML (){
    if (null != sb){
    XDocument xDoc = XDocument.Parse (this.sb.ToString ());
    var query = from s in xDoc.Descendants()
    select new CustomerData() {
    ID = Convert.ToInt32(s.Element("ID").Value),
    FirstName = s.Element("FirstName").Value,
    LastName = s.Element("LastName").Value
    };
    CustomerData custData = query.FirstOrDefault();
    if (null != custData){
    this.labelOutput.Text = String.Format("ID: {0}\n\t FirstName: {1}\n\tLastName: {2}", custData.ID, custData.FirstName, custData.LastName);
    }
    }
    }
    
    

它是如何工作的...

LINQ 在查询 XML 数据方面非常灵活且直观。

  1. 我们首先从我们的 XML 数据创建一个XDocument

    XDocument xDoc = XDocument.Parse (this.sb.ToString ());
    
    
  2. 然后,我们对其Descendants()方法创建一个查询,该方法返回一个IEnumerable<XElement>对象。每个XElement对象对应一个 XML 元素。

  3. 使用返回的每个XElement的信息,我们在select语句中构建我们的CustomerData对象:

    var query = from s in xDoc.Descendants()
    select new CustomerData() {
    ID = Convert.ToInt32(s.Element("ID").Value),
    FirstName = s.Element("FirstName").Value,
    LastName = s.Element("LastName").Value
    };
    
    
  4. 要检索创建的对象,我们在查询上调用扩展方法FirstOrDefault()

    CustomerData custData = query.FirstOrDefault();
    
    
更多内容...

在这个例子中,XML 只包含一个类型为CustomerData的对象。如果有更多,我们可以通过提供一个where语句来缩小结果:

var query = from s in xDoc.Descendants() where s.Element("FirstName").Value == "John" select new CustomerData() {
ID = Convert.ToInt32(s.Element("ID").Value),
FirstName = s.Element("FirstName").Value,
LastName = s.Element("LastName").Value
};

LINQ 中的匿名类型

在 MonoTouch 中,我们还可以在查询中使用 C#的强大功能——匿名类型,例如,select new { ID = … }

相关内容

在本章中:

  • 使用序列化存储数据

  • 存储数据

第五章:显示数据

在本章中,我们将涵盖以下主题:

  • 提供列表

  • 在表格中显示数据

  • 自定义行

  • 编辑表格:删除行

  • 编辑表格:插入行

  • 表索引

  • 搜索数据

  • 创建一个简单的网页浏览器

  • 显示本地内容

  • 显示格式化文本

  • 显示文档

简介

在上一章中,我们讨论了 iOS 应用程序中数据管理的一些可用选项。在本章中,我们将讨论向用户显示数据的各种方法。

具体来说,我们将看到如何使用以下控件:

  • UIPickerView: 这是一个提供类似列表框功能的控件。

  • UITableView: 这是一个非常可定制的视图,用于显示数据。iOS 应用程序中最常用的控件之一。

  • UISearchBar UISearchDisplayController: 这些是一组控件,提供了一种易于使用的界面来搜索数据。

  • UIWebView: 这将网页浏览器功能带给应用程序。

  • QLPreviewController: 这可以显示各种文档格式。

此外,我们将学习如何在表格中提供索引,以便用户可以轻松访问大量数据。我们还将讨论一些显示格式化文本的可用方法,甚至包括便携式文档格式(PDF)和其他文档。

从本章开始,所有代码示例都将使用默认视图控制器MainController,除非另有说明。

提供列表

在本食谱中,我们将学习如何使用UIPickerView类。

准备工作

UIPickerView类为我们提供了一个与列表框功能相似的控件,专门设计用于人类手指触摸屏幕。它与普通列表框的主要区别在于,每一列可以有自己的行数。要开始,创建一个新的 iPhone 项目,并将其命名为PickerViewApp

如何做到这一点...

  1. 在 Interface Builder 中打开MainController.xib文件。

  2. 在主视图中添加一个UILabel和一个UIPickerView

  3. 保存文档。

  4. 在 MonoDevelop 中,创建一个继承自UIPickerViewModel:MainController类中的嵌套类:

    private class PickerModelDelegate : UIPickerViewModel
    
    
  5. 在嵌套类中添加以下构造函数和字段:

    public PickerModelDelegate (MainController controller) {
    this.parentController = controller;
    this.transportList = new List<string>() { "On foot", "Bicycle", "Motorcycle", "Car", "Bus" };
    this.distanceList = new List<string>() { "0.5", "1", "5", "10", "100" };
    this.unitList = new List<string>() { "mi", "km" };
    this.transportSelected = this.transportList[0];
    this.distanceSelected = this.distanceList[0];
    this.unitSelected = this.unitList[0];
    }
    private MainController parentController;
    private List<string> transportList;
    private List<string> distanceList;
    private List<string> unitList;
    string transportSelected;
    string distanceSelected;
    string unitSelected;
    
    
  6. 您现在需要覆盖UIPickerViewModel类中的四个方法:

    • int GetComponentCount (UIPickerView picker)

    • int GetRowsInComponent (UIPickerView picker, int component)

    • string GetTitle (UIPickerView picker, int row, int component)

    • void Selected (UIPickerView picker, int row, int component)

  7. 最后,在控制器中的ViewDidLoad方法内将创建的模型对象设置为 picker 视图的Model属性:

    this.picker.Model = new PickerModelDelegate (this);
    
    

完整的代码可以在PickerViewApp项目中找到。

它是如何工作的...

UIPickerViewModel 类在 Objective-C 中不存在。MonoTouch 提供了这个类,作为原生协议 UIPickerViewDataSourceUIPickerViewDelegate 的包装器,并包含这两个类的所有方法,以便我们重写。这非常有帮助,因为我们只需要实现和分配一个类,而不是两个类来为我们的选择视图。这两个协议同时作为类在 MonoTouch 中可用。

在构造函数中,我们初始化将包含要显示在选择器中的数据的列表。我们需要重写的四个类负责显示数据:

  1. int GetComponentCount (UIPickerView picker): 这个方法返回我们想要选择视图显示的列数。

  2. int GetRowsInComponent (UIPickerView picker, int component): 这个方法返回每个组件将显示的行数。

  3. string GetTitle (UIPickerView picker, int row, int component): 这个方法返回每一行的文本。

  4. void Selected (UIPickerView picker, int row, int component): 这个方法返回当用户从选择视图的任何组件/行组合中选择项目时要采取的操作。

我们使用在构造函数中分配的列表来显示数据。例如,GetTitle 方法实现如下:

switch (component){
case 0:
return this.transportList[row];
case 1:
return this.distanceList[row];
default:
return this.unitList[row];
}

当我们运行应用程序并从选择器中选择任何内容时,结果将类似于以下截图:

工作原理...

更多内容...

我们可以通过调用方法 Select (int, int, bool) 来程序化选择选择视图的初始选择。前两个参数分别表示行和组件索引,而 bool 参数切换选择动画。使用此方法时需要注意的唯一一点是,我们必须在分配选择器的 Model 属性之后调用它。否则将发生异常。

更多关于 UIPickerView 定制的信息

除了提供的选项外,我们还可以设置每个组件的宽度。为此,我们重写 GetComponentWidth (UIPickerView, int) 方法,它返回一个表示每个组件宽度的浮点数。

我们还可以通过重写 GetView(UIPickerView, int, int, UIView) 方法来设置自定义视图作为选择视图中的项目,而不是纯文本。这可以通过返回我们想要在 UIPickerView 控制中的每个位置显示的视图来实现。

日期和时间选择

有一个名为 UIDatePicker 的控件,它与 UIPickerView 类似,专门用于显示和选择日期和时间值。请注意,尽管它的用户界面与选择视图相同,但它并不继承 UIPickerView 类。它只是使用它的一个实例作为子视图。

相关内容

在本章中:

  • 在表格中显示数据

在表格中显示数据

在本食谱中,我们将学习如何使用 UITableView 类来显示数据。

准备工作

UITableView 类以及 UITableViewCell 对象提供了一种在屏幕上以多行但单列的形式显示数据的接口。要开始,请在 MonoDevelop 中创建一个新的项目,并将其命名为 TableViewApp.

如何做到这一点...

  1. 将带有控制器的视图添加到项目中,并将其命名为 TableController

  2. TableController 类的继承从 UIViewController 更改为 UITableViewController:

    public partial class TableController : UITableViewController
    
    
  3. 在 Interface 中打开 TableController.xib 文件。

  4. 删除文档的 UIView,并在其位置添加一个 UITableView

  5. TableController 的输出端口 view 连接到添加的表格视图。

  6. 保存文档。

  7. 在 MonoDevelop 中,在 TableController 类内部创建以下嵌套类:

    private class TableSource : UITableViewSource{
    public TableSource (){
    this.cellID = "cellIdentifier";
    this.tableData = new Dictionary<int, string> () {
    {0, "Music"},
    {1, "Videos"},
    {2, "Images"}
    };
    }
    private string cellID;
    private Dictionary<int, string> tableData;
    public override int RowsInSection (UITableView tableview, int section){
    return this.tableData.Count;
    }
    public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath){
    int rowIndex = indexPath.Row;
    UITableViewCell cell = tableView.DequeueReusableCell (this.cellID);
    if (null == cell){
    cell = new UITableViewCell (UITableViewCellStyle.Default, this.cellID);
    }
    cell.TextLabel.Text = this.tableData[rowIndex];
    return cell;
    }
    }
    
    
  8. 重写控制器的 ViewDidLoad 方法,并添加以下代码行:

    this.TableView.Source = new TableSource ();
    
    

完整的代码可以在 TableViewApp 项目中找到。

它是如何工作的...

我们创建的嵌套类充当 UITableView 的数据源。它继承自 MonoTouch 的 UITableViewSource 类:

private class TableSource : UITableViewSource

注意

与之前讨论的示例中的 UIPickerView 类似,UITableViewSource 类在 Objective-C 中不存在。它仅仅是 MonoTouch 提供的围绕 UITableViewDelegateUITableViewSource 协议的包装对象。

在其构造函数中,我们初始化两个变量。一个字符串将作为单元格的标识符,以及一个用于数据源的通用 Dictionary

this.cellID = "cellIdentifier";
this.tableData = new Dictionary<int, string> () {
{0, "Music"},
{1, "Videos"},
{2, "Images"}
};

要使 TableSource 类工作,我们需要重写两个方法。第一个方法名为 RowsInSection,它返回表格应显示的行数。在这里,我们返回数据源对象中的项目数量:

return this.tableData.Count;

第二个方法 GetCell 返回将在表格中显示的 UITableViewCell 对象。

注意

UITableViewCell 类代表一行,并在 UITableView 中管理其内容。

为了更高效,表格视图在需要时创建其单元格对象。因此,我们需要通过其 DequeueReusableCell 方法从表格中获取之前使用的 UITableViewCell

UITableViewCell cell = tableView.DequeueReusableCell (this.cellID);

如果不存在特定单元格标识符的单元格,该方法将返回 null。因此,我们创建将要使用的单元格:

cell = new UITableViewCell (UITableViewCellStyle.Default, this.cellID);

然后,我们分配特定单元格将显示的文本并返回它:

cell.TextLabel.Text = this.tableData[rowIndex];
return cell;

默认情况下,UITableViewCell 类包含两个标签,可以用来显示文本。主标签可以通过 TextLabel 属性访问,次要标签可以通过 DetailTextLabel 属性访问。请注意,当使用具有 Default 风格的单元格时,DetailTextLabel 属性不能使用,并将返回 null

还有更多...

为了在用户选择特定行时提供功能,我们需要覆盖作为UITableViewSource的类的RowSelected属性。默认情况下,当用户点击一行时,单元格会以蓝色突出显示以表示选中。要取消选中行,我们使用UITableView.DeselectRow(NSIndexPath, bool)方法:

public override void RowSelected (UITableView tableView, NSIndexPath indexPath){
tableView.DeselectRow (indexPath, true);
}

UITableView样式

UITableView可以以两种不同的样式创建。默认样式是Plain。另一种可以使用的样式是Grouped样式。这种样式在许多 iOS 原生应用中都被使用,例如Settings应用。

此外,UITableView支持显示分为不同部分的数据。如果我们想使用不同的部分,我们必须在RowsInSection覆盖中明确返回每个部分将有的行数。

参见

在本章中:

  • 提供列表

  • 自定义行

在本书中:

第三章,用户界面:视图控制器:

  • 创建一个表格控制器

自定义行

在这个菜谱中,我们将介绍一些可用于自定义表格单元格内容显示的不同选项。

准备工作

以与上一个菜谱中创建项目相同的方式在 MonoDevelop 中创建一个新的项目。将其命名为CustomRowsApp.

如何做...

  1. 将上一个任务中的项目中的TableSource类复制并粘贴到TableController类内部。

  2. GetCell覆盖中执行以下更改:

    int rowIndex = indexPath.Row;
    string cellID = this.tableData[rowIndex];
    UITableViewCell cell = tableView.DequeueReusableCell (cellID);
    if (null == cell){
    cell = new UITableViewCell (this.cellStyles[rowIndex], cellID);
    }
    cell.TextLabel.Text = this.tableData[rowIndex];
    if (rowIndex > 0){
    cell.DetailTextLabel.Text = String.Format ("Details for {0}", cellID);
    }
    return cell;
    
    
  3. 删除cellID字段并添加一个新的:

    private Dictionary<int, UITableViewCellStyle> cellStyles;
    
    
  4. 在构造函数中初始化,如下所示:

    this.cellStyles = new Dictionary<int, UITableViewCellStyle>() {
    {0, UITableViewCellStyle.Default},
    {1, UITableViewCellStyle.Subtitle},
    {2, UITableViewCellStyle.Value1},
    {3, UITableViewCellStyle.Value2}
    };
    
    
  5. 在数据源对象中添加另一个KeyValuePair

    {3, "Recordings"}
    
    
  6. 在模拟器上编译并运行应用程序。输出应该类似于以下内容,如以下截图所示:

如何做...

它是如何工作的...

一个表格单元格可以有四种不同的单元格样式,这些样式由UITableViewCellStyle枚举表示。其值如下:

  1. 默认: 这是默认单元格样式。只能使用TextLabel属性来显示文本。

  2. 副标题: 这是一种提供DetailTextLabel作为TextLabel副标题的样式。

  3. Value1: 这是显示TextLabelDetailTextLabel文本大小相同、颜色不同且对齐到单元格两侧的样式。

  4. Value2: 这是显示TextLabel文本比DetailTextLabel文本小的样式。这种样式在原生的Contacts应用中的联系人详情屏幕中使用。

为了方便使用所有可用的样式,我们已经在Dictionary:中添加了UITableViewCellStyle枚举的所有值:

private Dictionary<int, UITableViewCellStyle> cellStyles;

现在我们使用了不同的单元格样式,因此需要为每个字符串提供一个单元格标识符。为了避免在类中声明另一个列表或更多字段,我们出于这个原因使用数据源:

int rowIndex = indexPath.Row;
string cellID = this.tableData[rowIndex];
UITableViewCell cell = tableView.DequeueReusableCell (cellID);

要为每个单元格创建具有特定样式的单元格,我们根据当前行从 cellStyles 字段中提取 UITableViewCellStyle 值:

cell = new UITableViewCell (this.cellStyles[rowIndex], cellID);

要为每个单元格设置 DetailTextLabel 文本,我们只需确保我们不是在具有 Default 样式的单元格上设置它,如本例中的第一个:

if (rowIndex > 0){
cell.DetailTextLabel.Text = String.Format ("Details for {0}", cellID);
}

还有更多...

可以在 UITableViewCell 中进行进一步的自定义。单元格包含的所有视图,包括 TextLabelDetailTextLabel,都是单元格视图的子视图,该视图通过其 ContentView 属性公开。我们可以创建自定义视图并将其作为子视图添加到其中。

UITableViewCell 类的其他有用属性

除了在默认标签中添加文本外,UITableViewCell 还包含一些其他属性,我们可以设置它们的值,以在单元格中添加更多默认项:

  • ImageView: 这个属性接受一个 UIImageView。我们可以用它来在单元格的左侧显示一个图像。

  • AccessoryView: 这个属性接受任何 UIView 实例。它的默认位置是单元格的右侧,在单元格的 Accessory 位置,位于单元格的右侧。

  • Accessory: 这个属性接受 UITableViewCellAccessory 类型的值。它为单元格的附件提供预定义视图,例如 DetailDisclosureButtonCheckmark

参见

在本章中:

  • 在表格中显示数据

  • 在表格中编辑数据

编辑表格:删除行

在这个菜谱中,我们将讨论如何从 UITableView 中删除行,并提供适当的用户反馈。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 EditingTableDataApp

如何操作...

  1. 将视图控制器添加到项目中,并将其转换为本章中 在表格中显示数据 部分所述的 UITableViewController,并将其命名为 TableController

  2. AppDelegate 类中添加一个 UINavigationController。初始化它,将 TableController 设置为其根控制器:

    TableController tableController;
    UINavigationController navController;
    //…
    this.tableController = new TableController();
    this.navController = new UINavigationController(this.tableController);
    window.RootViewController = this.navController;
    
    
  3. 在 MonoDevelop 中,在 TableController 类中添加三个字段:List<string> tableDataUIBarButtonItem buttonEditUIBarButtonItem buttonDone。重写类的 ViewDidLoad 方法,如下所示:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.buttonEdit = new UIBarButtonItem ("Edit", UIBarButtonItemStyle.Bordered, this.ButtonEdit_Clicked);
    this.buttonDone = new UIBarButtonItem (UIBarButtonSystemItem.Done, this.ButtonDone_Clicked);
    this.NavigationItem.SetRightBarButtonItem (this.buttonEdit, false);
    this.tableData = new List<string>() {"Music", "Videos", "Images" };
    this.TableView.Source = new TableSource(this.tableData);
    }
    
    
  4. 为表格创建适当的表格视图源,它在构造函数中接受 tableData 泛型 List 作为参数。创建处理方法 ButtonEdit_Clicked,并在其中输入以下代码:

    this.TableView.SetEditing (true, true);
    this.NavigationItem.SetRightBarButtonItem (this.buttonDone, true);
    
    
  5. 创建处理方法 ButtonDone_Clicked,并在其中输入以下代码:

    this.TableView.SetEditing (false, true);
    this.NavigationItem.SetRightBarButtonItem (this.buttonEdit, true);
    
    
  6. 最后,重写表格源代码中的 CommitEditingStyle 方法:

    public override void CommitEditingStyle (UITableView tableView, UITableViewCellEditingStyle editingStyle, NSIndexPath indexPath){
    int rowIndex = indexPath.Row;
    if (editingStyle == UITableViewCellEditingStyle.Delete) {
    this.tableData.RemoveAt (rowIndex);
    tableView.DeleteRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Left);
    }
    }
    
    

它是如何工作的...

在这里我们首先使用导航栏添加按钮来处理表格的编辑模式。当视图加载时,我们使用 SetRightBarButtonItem 方法设置编辑按钮:

this.NavigationItem.SetRightBarButtonItem (this.buttonEdit, false);

ButtonEdit_Clicked 方法内部,我们将表格设置为编辑模式。然后,我们更改导航栏中的按钮,以便用户可以退出编辑模式:

this.TableView.SetEditing (true, true);
this.NavigationItem.SetRightBarButtonItem (this.buttonDone, true);

SetEditing 方法用于启用或禁用表格的编辑模式。当表格处于编辑模式时,每个单元格左侧会出现一个带有减号 (-) 符号的圆形红色图标。当用户点击该图标时,单元格中会出现一个确认的红色 删除 按钮。要实际删除用户通过点击 删除 按钮确认删除的行,我们必须在表格源中实现 CommitEditingStyle 方法:

if (editingStyle == UITableViewCellEditingStyle.Delete){
this.tableData.RemoveAt (rowIndex);
tableView.DeleteRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Left);
}

我们需要做的第一件事是检查该方法是否是由于用户点击 删除 按钮而调用的。这是通过检查 editingStyle 参数来完成的,如代码中所突出显示的。然后,我们使用 DeleteRows 方法从数据源和表格中删除行的数据。

更多内容...

表视图为用户提供了另一种更直接的方式来删除行。这可以通过在想要删除的单元格上滑动手指来实现。在这种情况下,只会显示 删除 按钮。我们仍然需要在表格源中实现 CommitEditingStyle 方法,以实际从表格中删除行。

在这个菜谱中使用导航控制器并不意味着它是实现删除行功能的唯一方法。然而,它是一种在现实世界的应用场景中将被广泛使用的视图控制器组合。

行删除动画

DeleteRows 方法中使用的 UITableViewRowAnimation 枚举表示将要被删除的行的动画类型。它包含各种值(Left,Right,Middle,Fade,Top,Bottom 和 None),用于动画行。请注意,为了实现最佳效果,应根据行在数据源中的位置使用动画类型。例如,如果将要删除的行是表格中的最后一行,最好使用 UITableViewRowAnimation.Bottom,这样应该删除的行会向下移动。如果将要删除的行是数据源中的第一行,最好使用 UITableViewRowAnimation.Top,这样应该删除的行会向上移动。其余选项更适合中间行,即在第一行和最后一行之间。

相关内容

在本章中:

  • 在表格中显示数据

  • 编辑表格:插入行

在本书中:

第三章,用户界面:视图控制器:

  • 在不同视图控制器之间导航

编辑表格:插入行

在这个菜谱中,我们将学习如何为用户在 UITableView 中提供插入行的能力。

准备工作

对于这个任务,我们将使用上一个任务中的项目 EditingTableDataApp。在 MonoDevelop 中打开它。

如何操作...

  1. TableController 类中添加另一个 UIBarButtonItem 字段,并在 ViewDidLoad 方法中初始化它:

    this.buttonAdd = new UIBarButtonItem (UIBarButtonSystemItem.Add, this.ButtonAdd_Clicked);
    
    
  2. 添加处理方法 ButtonAdd_Clicked:

    private void ButtonAdd_Clicked (object sender, EventArgs e){
    this.tableData.Add ("Recordings");
    this.TableView.ReloadData ();
    }
    
    
  3. ButtonEdit_Clicked 方法中添加以下行:

    this.NavigationItem.SetLeftBarButtonItem (this.buttonAdd, true);
    
    
  4. 还需要在ButtonDone_Clicked方法中添加以下行:

    this.NavigationItem.SetLeftBarButtonItem (null, true);
    
    
  5. 在模拟器上编译并运行应用程序。

  6. 点击编辑按钮,你会看到添加按钮出现在导航栏的左侧。点击它,你会看到新行被添加到表格中。

它是如何工作的...

这里使用了一个系统默认的添加按钮。当用户点击按钮时,表格中会添加一行。按钮作为导航栏中的左侧按钮添加到编辑按钮的Clicked处理程序中。要移除它,我们调用相同的函数,将UIBarButtonItem参数传递为null值,在ButtonDone_Clicked方法中:

this.NavigationItem.SetLeftBarButtonItem (this.buttonAdd, true);
this.NavigationItem.SetLeftBarButtonItem (null, true);

这样,当用户禁用编辑模式时,添加按钮将消失。接下来,我们只需将数据添加到数据源中,并强制表格重新加载,具体操作如下:

this.tableData.Add ("Recordings");
this.TableView.ReloadData ();

还有更多...

这是在表格中插入行最简单的方法。但这并不是最有效的方法。调用UITableView.ReloadData方法会导致UITableView重新加载所有内容,如果表格包含大量行,这将降低性能。为了避免这种情况,您可以将此示例中的ReloadData调用替换为以下行:

this.TableView.InsertRows (new NSIndexPath[] { NSIndexPath.FromRowSection(this.tableData.Count - 1, 0) }, UITableViewRowAnimation.Right);

InsertRows方法只会重新加载表格内容中所需的部分,在本例中是数据源中的最后一个项目。注意,使用此方法,我们还可以指定单元格将被插入表格的哪个部分。

行重新排序

UITableView类的另一个有用功能是行重新排序。为了演示这一点,在表源中添加以下方法重写:

public override bool CanMoveRow (UITableView tableView, NSIndexPath indexPath){
return true;
}
public override void MoveRow (UITableView tableView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath){
string itemToMove = this.tableData[sourceIndexPath.Row];
this.tableData.Remove (itemToMove);
this.tableData.Insert (destinationIndexPath.Row, itemToMove);
}

CanMoveRow方法中返回true可以启用所有单元格的重新排序。这通过在每个单元格右侧显示的抓手外观图标来表示。当用户触摸并拖动图标时,单元格可以被移动到另一个位置。实际的重新排序发生在MoveRow方法中。需要做的只是使用sourceIndexPathdestinationIndexPath参数,在数据源中移除并重新插入项目到期望的索引。

参见

在本章中:

  • 在表格中显示数据

  • 编辑表格:删除行

表索引

在本教程中,我们将学习如何在表格中提供一个索引,使用户能够更快地浏览UITableView中的行。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为TableIndexApp。添加一个UITableViewController,如本章前面的任务所示,并实现TableSource类。

如何操作...

在表源类中,重写并实现以下方法:

public override int NumberOfSections (UITableView tableView){
return this.tableData.Count;
}
public override string TitleForHeader (UITableView tableView, int section){
return Convert.ToString (this.tableData[section][0]);
}
public override string[] SectionIndexTitles (UITableView tableView){
return this.tableData.Select (s => Convert.ToString (s[0])).Distinct ().ToArray ();
}

它是如何工作的...

在此任务中创建的表源包含许多不同的部分。为了简单起见,每个部分包含一行。NumberOfSections方法返回表格将显示的总部分数。

要为每个部分设置标题,我们必须重写TitleForHeader方法:

public override string TitleForHeader (UITableView tableView, int section){
return Convert.ToString (this.tableData[section][0]);
}

此实现返回数据源中每个字符串的第一个字母。为了提供索引,我们重写了SectionIndexTitles方法:

public override string[] SectionIndexTitles (UITableView tableView){
return this.tableData.Select (s => Convert.ToString (s[0])).Distinct ().ToArray ();
}

这里,它返回数据源中每个项目的第一个字母。此项目的结果将类似于以下:

工作原理...

当用户触摸索引上的任何位置时,表格视图将自动滚动到该特定部分。

更多内容...

应在具有Plain样式的表格上应用索引。在设置了Grouped样式的表格上应用索引是不推荐的,因为索引将不易区分。

一个具有表格索引的本地 iOS 应用程序的好例子可以在本地的Contacts应用程序中找到。

参见

在本章中:

  • 在表格中显示数据

  • 搜索数据

搜索数据

在本教程中,我们将学习如何为表格中的内容提供搜索功能。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为SearchTableApp。添加一个UITableViewController,并将其命名为TableController

如何实现...

  1. 在 Interface Builder 中打开TableController.xib文件。

  2. UITableView中添加搜索栏搜索显示控制器。请注意,在此操作之后,将自动创建并连接一些输出。我们需要大多数它们,所以我们保留它们原样并保存文档。

  3. 回到 MonoDevelop,实现一个将作为搜索显示控制器委托对象的类:

    private class SearchDelegate : UISearchDisplayDelegate{
    public SearchDelegate (TableController controller){
    this.parentController = controller;
    }
    private TableController parentController;
    public override bool ShouldReloadForSearchString ( UISearchDisplayController controller, string forSearchString){
    this.parentController.filterDataList = this.parentController.tableData
    .Where (s => s.ToLower ().Contains (forSearchString.ToLower ()))
    .ToList ();
    this.parentController.filterDataList.Sort (delegate (string firstStr, string secondStr) {
    return firstStr.CompareTo (secondStr);
    });
    return true;
    }
    }
    
    
  4. 重写ViewDidLoad方法,并在其中分配源和委托对象:

    this.TableView.Source = new TableSource (this);
    this.SearchDisplayController.SearchResultsSource = new TableSource(this);
    this.SearchDisplayController.Delegate = new SearchDelegate(this);
    
    
  5. 你可以在SearchTableApp项目中找到完整的代码。结果将是表格上方的常见 iOS 搜索栏,类似于以下截图:

如何实现...

工作原理...

UISearchDisplayController类提供了一个方便的搜索数据的方法。它包含一个UISearchBar,用于接受用户的输入,以及一个UITableView,用于显示结果。在我们在视图控制器中添加搜索控制器后,我们可以通过该控制器的SearchDisplayController属性访问它。为了触发结果表格,我们必须实现UISearchDisplayDelegate并重写其ShouldReloadForSearchString,它返回一个布尔值:

private class SearchDelegate : UISearchDisplayDelegate

ShouldReloadForSearchString方法重写中,我们根据其forSearchString参数搜索数据源,将过滤后的结果保存在一个新的数据源中:

this.parentController.filterDataList = this.parentController.tableData
.Where (s => s.ToLower ().Contains (forSearchString.ToLower ()))
.ToList ();

然后,我们按字母顺序对结果进行排序并返回true,这样搜索控制器的表格就会重新加载数据:

this.parentController.filterDataList.Sort (delegate( string firstStr, string secondStr) {
return firstStr.CompareTo (secondStr);
});
return true;

搜索控制器的表格视图也需要一个源对象。在这个例子中,我们将其设置为为我们表格创建的相同对象:

this.TableView.Source = new TableSource (this);
this.SearchDisplayController.SearchResultsSource = new TableSource(this);

由于我们使用的是相同对象的实例,我们需要对其进行一些修改以根据调用它的表格显示数据。例如,RowsInSection方法看起来如下:

public override int RowsInSection (UITableView tableview, int section){
if (tableview.Equals (this.parentController.TableView)){
return this.parentController.tableData.Count;
} else{
return this.parentController.filterDataList.Count;
}
}

这样,我们就根据表格调用该方法返回行数。同样,我们还需要在GetCell方法内部设置每个单元格的文本标签:

if (tableView.Equals (this.parentController.TableView)){
cell.TextLabel.Text = this.parentController.tableData[rowIndex];
} else{
cell.TextLabel.Text = this.parentController.filterDataList[rowIndex];
}

还有更多...

当用户点击搜索栏时,键盘出现,设置搜索控制器为活动状态。要使其不活动,我们可以挂钩到搜索栏的SearchButtonClicked事件。当用户点击键盘上的搜索按钮时,此事件将被触发:

this.SearchDisplayController.SearchBar.SearchButtonClicked += delegate {
this.SearchDisplayController.SetActive(false, true);
};

SetActive方法是我们用来启用或禁用搜索控制器的。

为其他控制器提供搜索功能。

虽然这个例子在UITableViewController中使用了一个UISearchDisplayController,但这并不意味着它是唯一的使用方式。我们可以使用任何我们想要的UIViewController类型的搜索控制器。在这种情况下,我们唯一需要做的额外事情是将搜索控制器的SearchContentsController属性设置为它所属的视图控制器。当我们在UITableViewController中添加UISearchDisplayController时,Interface Builder 会自动处理这一点,但不是在其他控制器中。

参见

在本章中:

  • 在表格中显示数据

  • 表格索引

创建简单的网页浏览器

在这个菜谱中,我们将讨论使用UIWebView类显示在线内容。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为WebBrowserApp

如何实现...

  1. 在 Interface Builder 中打开MainController.xib文件,并在主视图中添加一个UIWebView对象。

  2. 创建并连接一个名为webView的出口。

  3. 保存文档。

  4. MainController类中重写ViewDidAppear方法:

    public override void ViewDidAppear (bool animated){
    NSUrl url = new NSUrl ("http://software.tavlikos.com");
    NSUrlRequest urlRequest = new NSUrlRequest (url);
    this.webView.LoadRequest (urlRequest);
    }
    
    
  5. 在模拟器上编译并运行应用程序。观察屏幕上网站加载的情况!

它是如何工作的...

UIWebView类是 iOS SDK 的网页浏览器控件。要加载网页内容,我们只需调用其LoadRequest方法,该方法接受一个类型为NSUrlRequest的参数。NSUrlRequest对象包含我们想要它加载的 URL:

NSUrl url = new NSUrl ("http://software.tavlikos.com");

还有更多...

UIWebView类包含一些非常有用的事件:

  • LoadStarted: 当控件开始加载内容时触发

  • LoadFinished: 当内容成功加载完成时触发

  • LoadError: 当内容加载失败时触发

内容缩放

UIWebView类的另一个重要功能是内容的自动缩放。可以通过将其ScalePageToFit属性设置为true来激活它。

参见

在本章中:

  • 显示本地内容

  • 显示格式化文本

  • 显示文档

显示本地内容

在这个菜谱中,我们将讨论显示本地 HTML 文件。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为LocalContentApp

如何实现...

  1. MainController的主视图中添加一个UIWebView,并保存文档。

  2. 在项目中添加一个新的文件夹,并将其命名为html_content

  3. 通过 MonoDevelop 将您的内容添加到该文件夹中。别忘了将每个文件的 构建操作 设置为 内容

  4. MainController 类中重写 ViewDidAppear 方法,并输入以下代码:

    NSUrl fileUrl = NSUrl.FromFilename ( "./html_content/T-Shirts.html");
    NSUrlRequest urlRequest = new NSUrlRequest (fileUrl);
    this.webView.ScalesPageToFit = false;
    this.webView.LoadRequest (urlRequest);
    
    
  5. 在模拟器上编译并运行应用程序。

  6. 查看屏幕上显示的 HTML 内容。

  7. 放大以查看更大的内容,就像您在线内容中做的那样。

工作原理...

显示本地内容的过程与显示在线内容相同。NSUrl 类有一个静态方法,根据文件路径创建一个实例:

NSUrl fileUrl = NSUrl.FromFilename ("./html_content/T-Shirts.html");

还有更多...

UIWebView 是一个非常强大的控件。它可以用来显示 iOS 上 Safari 浏览器可以显示的一切。这包括 HTML、纯文本、图片和 PDF 文档。

在内容中导航

您还可以使用其 GoBack()GoForward() 方法在 UIWebView 的历史记录中导航。

UIWebView 支持的文件

UIWebView 控件也可以用来显示其他类型的文件。这些文件类型包括:

  • Excel (.xls)

  • Keynote (.key.zip)

  • 数字文件 (.numbers.zip)

  • Pages (.pages.zip)

  • PDF (.pdf)

  • PowerPoint (.ppt)

  • Word (.doc)

  • 富文本格式 (.rtf)

  • 富文本格式目录 (.rtfd.zip)

  • Keynote '09 (.key)

  • Numbers '09 (.numbers)

  • Pages '09 (.pages)

参见

在本章中:

  • 创建一个简单的网页浏览器

  • 显示文档

显示格式化文本

在本章中,我们将学习如何使用 UIWebView 类来显示格式化文本。

准备工作

在这个任务中,我们将处理之前讨论过的 LocalContentApp 项目。在 MonoDevelop 中打开它。

如何操作...

  1. ViewDidAppear 方法中注释掉之前的代码,并添加以下代码:

    string htmlString = "<html><head></head><body> <span style=\"font-weight: bold;\">This</span> " + "<span style=\"text-decoration: underline;\">is</span> <span style=\"font-style: italic;\">some formatted</span> " +" <span style=\"font-weight: bold; text-decoration: underline;\">text!</span><br></body></html>";
    this.webView.LoadHtmlString (htmlString, null);
    
    
  2. 在模拟器上编译并运行应用程序。

  3. 观察 HTML 字符串的显示。

工作原理...

如同在 第二章 中讨论的,用户界面:视图UITextView 可以用来显示大块文本并进行编辑,但它不能显示格式化文本。UIWebView 可以通过将我们的 HTML 格式化文本作为参数传递给 LoadHtmlString 方法来实现这一点:

this.webView.LoadHtmlString (htmlString, null);

第二个参数的类型是 NSUrl。由于我们在代码中创建了 HTML 字符串,并且没有对其他文件的引用,所以我们不需要它,因此我们只需传递 null

还有更多...

如果我们想在 HTML 字符串内部引用外部文件,我们应该将 LoadHtmlStringNSUrl 参数设置为包含文件的路径,从而设置 HTML 的基本目录。例如,考虑以下 HTML 字符串,它引用了应用程序包中 html_content 文件夹内的文件:

string htmlString = "<img style=\"width: 215px;\" src=\"tshirts_s.jpg\">";

如果我们要将其传递给 LoadHtmlString 来显示图片,我们还应该设置 baseUrl 参数:

this.webView.LoadHtmlString (htmlString, new NSUrl ( "./html_content", true));

NSUrl 构造函数的 bool 参数表示第一个参数的 URL 字符串是目录的路径,应该像目录一样处理。

备注

虽然 UIWebView 可以显示各种内容,但不能用于编辑。

允许特定的链接

UIWebView 还提供了对用户点击的链接如何处理的控制。为此,我们可以将其 ShouldStartLoad 属性分配一个处理程序。它接受 UIWebLoaderControl 类型的代理。

参见

在本章中:

  • 创建一个简单的网页浏览器

  • 显示本地内容

在本书中:

第二章, 用户界面:视图:

  • 显示和编辑文本

显示文档

在本配方中,我们将讨论如何使用 QLPreviewController 类轻松显示不同格式的各种文档。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 DocumentPreviewApp。添加一个带有控制器的视图,并将其命名为 MainController

如何做到这一点...

  1. 在 Interface Builder 中打开 MainController.xib 文件并添加一个 UIButton

  2. 保存文档。

  3. 在项目中添加一个名为 docs 的文件夹,并将一些文档文件放入其中。项目 DocumentPreviewApp 包含三种不同的文档:一个 PDF、一个 DOCX 和一个 XLSX

  4. MainController.cs 文件中输入以下 using 指令:

    using MonoTouch.QuickLook;
    
    
  5. MainController 类内部创建以下嵌套类:

    private class PreviewDataSource : QLPreviewControllerDataSource{
    public PreviewDataSource (List<PreviewItem> items){
    this.previewItems = items;
    }
    private List<PreviewItem> previewItems;
    public override int PreviewItemCount ( QLPreviewController controller){
    return this.previewItems.Count;
    }
    public override QLPreviewItem GetPreviewItem ( QLPreviewController controller, int index){
    return this.previewItems[index];
    }
    }
    
    
  6. MainControllerViewDidLoad 重写中输入以下代码:

    this.previewItems = new List<PreviewItem>() {
    new PreviewItem("PDF", NSUrl.FromFilename("docs/pdfdoc.pdf")),
    new PreviewItem("DOCX", NSUrl.FromFilename("docs/text.docx")),
    new PreviewItem("XLSX", NSUrl.FromFilename("docs/spreadsheet.xlsx"))
    };
    this.previewController = new QLPreviewController();
    this.previewController.DataSource = new PreviewDataSource(this.previewItems);
    this.buttonPreviewDocs.TouchUpInside += delegate {
    this.PresentModalViewController(this.previewController, true);
    };
    
    

它是如何工作的...

QLPreviewController 类提供了一种非常方便的方式,可以一次性显示多种文档格式。它是一个可以通过将其推入导航控制器堆栈或以模态方式显示的控制器。

为了定义我们想要显示的文档,我们必须创建一个 QLPreviewControllerDataSource 类并将其分配给其 DataSource 属性:

private class PreviewDataSource : QLPreviewControllerDataSource

QLPreviewControllerDataSource 包含两个我们需要重写的方法:PreviewItemCount,它返回控制器需要显示的项目数量,以及 GetPreviewItem,它返回实际的项目。此项目是 QLPreviewItem 类型,我们必须实现一个继承它的方法:

private class PreviewItem : QLPreviewItem

在此类中,我们必须重写两个属性,这两个属性都代表要预览的项目信息。这些是 ItemTitleItemUrl

当控制器调用 PreviewItemCount 方法并且返回一个大于 1 的数字时,它会添加一个带有两个箭头按钮的 UIToolbar,允许用户在文档之间导航。当调用 GetPreviewItem 方法时,它会将当前标题设置为 ItemTitle 属性,并根据 ItemUrl 属性加载文档。如果在此应用程序中点击按钮,结果将类似于以下内容:

它是如何工作的...

此截图显示了导航到最后一个文档(XLSX 类型的文件)后的 QLPreviewController

更多...

控制器在其导航栏上包含一个系统默认的完成按钮。如果按下该按钮,控制器将自动关闭。我们可以通过其WillDismiss和/或DidDismiss事件来提供额外的行为。

参见

在本章中:

  • 显示本地内容

  • 显示格式化文本

第六章:网络服务

在本章中,我们将涵盖:

  • 消费网络服务

  • 调用网络服务

  • 消费 WCF 服务

  • 读取 JSON 数据

简介

向用户提供在线信息是移动开发的关键部分。在本章中,我们将讨论开发与网络服务通信以提供信息的应用程序。我们将看到如何基于 SOAP 消费和调用网络服务,还将讨论如何使用 WCF 网络服务以及如何从网络服务器解析流行的 JSON 数据格式。

本章中的所有示例都使用与 Mono 框架一起提供的 xsp 轻量级网络服务器,因此无需在线上运行一个实时网络服务来使用提供的代码。

消费网络服务

在本配方中,我们将学习如何在 MonoTouch 项目中使用 SOAP 网络服务。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为WebServiceApp。本章的代码包含一个名为MTWebService的网络服务项目。这就是将要使用的网络服务。

如何操作...

  1. 要使用MTWebService网络服务,我们需要一个网络服务器。Mono 框架提供了用于测试目的的 xsp 轻量级网络服务器。

  2. 打开一个终端,并输入以下命令以进入网络服务的目录,将<code_directory>替换为下载的代码所在的路径:

    cd <code_directory>/CH06_code/MTWebService/MTWebService
    
    
  3. 通过在提示符中输入xsp4来运行 xsp 网络服务器。你将看到类似以下输出的内容:

    xsp4
    Listening on address: 0.0.0.0
    Root directory: /Users/dtavlikos/projects/CH06_code/MTWebService/ MTWebService
    Listening on port: 8080 (non-secure)
    Hit Return to stop the server. 
    
    
  4. 现在,我们需要在项目中添加对网络服务的引用。在解决方案面板中右键单击项目,然后选择添加 | 添加 Web 引用

  5. 在将显示的对话框中,添加以下截图提供的信息:如何操作...

  6. 在将引用添加到MTTestWebService网络服务后,在MainController的视图中添加一个按钮和一个标签。覆盖MainController类的ViewDidLoad方法,并在其中输入以下代码:

    this.buttonFetchData.TouchUpInside += delegate {
    using (MTTestWebService webService = new MTTestWebService()){
    this.lblMessage.Text = webService.GetMessage ("Hello Web Service!");
    }
    } ;
    
    
  7. 最后,为我们的网络服务命名空间提供一个using指令:

    using WebServiceApp.mtWebService;
    
    
  8. 在模拟器上编译并运行应用程序。

  9. 点击按钮以调用网络服务,并注意标签中的输出消息。

工作原理...

MonoTouch 可以像.NET 桌面应用程序一样消费网络服务。xsp轻量级网络服务器在安装 Mono 框架时默认安装,这是 MonoTouch 安装的要求。在终端中运行不带任何参数的xsp4命令时,它默认将其基本目录设置为当前目录,并开始监听8080端口。如果网络服务器已启动,可以通过在浏览器中输入http://localhost:8080/MTTestWebService.asmx来查看网络服务描述:

工作原理...

需要在网络服务引用对话框的 Web 服务 URL 字段中输入的链接,可以通过点击服务描述页面上的 服务描述 链接,然后点击位于网络服务 WSDL 描述上方的 下载 链接来找到。

然后,我们将 框架 值设置为 .NET 2.0 Web 服务,并提供一个 引用 名称,该名称将反映网络引用的命名空间。为了在我们的代码中使用网络服务,我们实例化它,然后只需调用我们感兴趣的方法:

this.lblMessage.Text = webService.GetMessage ("Hello Web Service!");

更多内容...

除了使用本地托管网络服务外,互联网上还有许多示例网络服务。简单的搜索会产生许多结果。

XSP 关闭

要关闭 xsp 网络服务器,只需在执行它的终端中按 回车键

参见

在这一章中:

  • 调用网络服务

  • 消费 WCF 服务

调用网络服务

在这个菜谱中,我们将讨论如何正确使用 MonoTouch 中的网络服务。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 WebServiceApp2。启动 xsp 网络服务器,并在项目中添加对 TTestWebService 网络服务的引用,具体操作如前一个任务所述。

如何做到...

  1. MainController 的视图中添加一个标签和一个按钮。

  2. 在 MonoDevelop 中的 MainController 类中,覆盖 ViewDidLoad 方法,并在其中输入以下代码:

    this.buttonInvoke.TouchUpInside += delegate {
    int a = 5;
    int b = 12;
    MTTestWebService webService = new MTTestWebService ();
    webService.MultiplyNumbersCompleted += MultiplyNumbers_CompletedHandler;
    webService.MultiplyNumbersAsync (a, b);
    UIApplication.SharedApplication. NetworkActivityIndicatorVisible = true;
    this.lblMessage.Text = String.Format ( "Multiplying {0} by {1}", a, b);
    } ;
    
    
  3. 最后,添加以下方法:

    private void MultiplyNumbers_CompletedHandler ( object sender, MultiplyNumbersCompletedEventArgs args){
    UIApplication.SharedApplication. NetworkActivityIndicatorVisible = false;
    this.InvokeOnMainThread (delegate {
    this.lblMessage.Text = String.Format ( "Multiplication result: {0}", args.Result);
    } );
    }
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 点击按钮,查看网络服务的结果在标签上显示。

它是如何工作的...

正如你可能已经注意到的,在前一个任务中,当应用程序与网络服务通信时,它冻结了,直到收到结果。在这个任务中,我们使用异步调用,这样在应用程序联系网络服务时用户界面就不会冻结。

我们希望在应用程序从网络服务收到响应时得到通知,但我们还需要它的结果。在调用我们感兴趣的 web 方法之前,我们订阅了网络服务对象的 MultiplyNumbersCompleted 事件,如 ViewDidLoad 覆盖中突出显示的代码所示。这个事件是 MonoDevelop 在我们添加网络引用时创建的类的一部分,每个 web 方法都有一个对应的事件。然后我们通过访问 MultiplyNumbersAsync 方法异步调用 web 方法。这个调用立即返回。下一个调用很有趣:

UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;

通过 UIApplication.SharedApplication 静态属性,我们可以访问一些应用程序范围内的组件,例如屏幕上显示的状态栏。当有进程挂起时,建议向用户提供某种信息。状态栏包含一个活动指示器,这是在原生 iOS 应用程序中设备连接到互联网时显示的内容。因此,用户习惯于这个控件,并知道当它显示时,设备正在连接以接收数据。通过将 NetworkActivityIndicatorVisible 属性设置为 true,网络指示器被激活并显示。

当对 Web 方法的调用完成时,将触发相应的事件。在 MultiplyNumbers_CompletedHandler 方法内部,我们首先确保隐藏网络指示器,以通知用户应用程序不再连接。我们可以通过 MultiplyNumbersCompletedEventArgs.Result 属性访问 Web 方法的返回结果。

在这个示例中,我们希望在处理程序内部直接在标签中显示结果。因为 Web 方法是异步调用的,处理程序很可能会在主线程之外的其他线程上执行。因此,我们将结果赋值给标签的操作包装在一个匿名方法中,并在主线程上执行,如处理程序实现中突出显示的代码所示。

更多内容...

Web 服务对象包含了一组更常见的异步调用,我们可以使用。它遵循 BeginInvoke EndInvoke 模式,方法名称根据服务的 Web 方法进行重命名。在这种情况下,这些方法被命名为 BeginMultiplyNumbersEndMultiplyNumbers

错误处理

MultiplyNumbersCompletedEventArgs 类还包含一个 Error 属性。它返回 System.Exception 类型的值,如果发生错误,例如由于网络连接问题,它将包含适当的信息。如果没有发生错误,Error 属性将返回 null。在检索 Web 方法的返回结果之前,建议始终检查此属性:

if (null != args.Error){
// Something went wrong, handle appropriately.
}

参见

在本章中:

  • 消费 WCF 服务

消费 WCF 服务

在本食谱中,我们将学习如何使用 MonoTouch 消费 WCF 服务。

准备工作

对于此项目,我们需要一个正在运行的 WCF 服务。本章的代码下载中可以找到 WCF 服务。要启动服务,打开终端并转到项目的目录。通过运行 start_wcfservice.sh 脚本来启动服务:

cd <code_directory>/CH06_code/ WcfService/WcfService
./start_wcfservice.sh

服务启动后,在 MonoDevelop 中创建一个新的项目,并将其命名为 WcfServiceApp

如何操作...

  1. System.Runtime.SerializationSystem.ServiceModel 的引用添加到项目中,并在 MainController.cs 文件中添加它们对应的 using 指令。

  2. MonoTouch 尚未提供对 WCF 服务的全面支持。为了生成客户端代理,我们将在 Windows 机器上使用slsvcutil工具。在 Windows 的终端中运行以下命令:

    "c:\Program Files\Microsoft SDKs\Silverlight\v3.0\Tools\slsvcutil /noconfig http://192.168.1.18:8080/WcfService.svc?wsdl" 
    
    

    此命令将生成一个名为service.cs的 C#源文件。将此文件添加到 MonoDevelop 项目中。

  3. MainController视图上添加一个标签和一个按钮。重写MainController类的ViewDidLoad方法,并在其中输入以下代码:

    this.buttonFetchData.TouchUpInside += delegate( object sender, EventArgs e) {
    WcfTestServiceClient client = new WcfTestServiceClient ( new BasicHttpBinding (), new EndpointAddress ( "http://192.168.1.18:8080/WcfTestService.svc"));
    client.GetBookInfoCompleted += WcfTestServiceClient_GetBookInfoCompleted;
    client.GetBookInfoAsync ();
    UIApplication.SharedApplication. NetworkActivityIndicatorVisible = true;
    } ;
    
    
  4. 最后,添加以下事件处理器:

    private void WcfTestServiceClient_GetBookInfoCompleted ( object sender, GetBookInfoCompletedEventArgs e){
    this.InvokeOnMainThread (delegate {
    UIApplication.SharedApplication. NetworkActivityIndicatorVisible = false;
    this.labelResult.Text = String.Format ("Book title: {0}\nAuthor: {1}", e.Result.Title, e.Result.Name);
    } );
    }
    
    
  5. 在模拟器上编译并运行应用程序。

  6. 点击按钮,并观察从服务返回的数据填充到标签中。

它是如何工作的...

MonoTouch 依赖于 Mono Framework 对 WCF 服务的支持,但这并不完整。然而,仅 WCF 服务可以在 iOS 应用程序中使用这一事实就使 MonoTouch 对.NET 开发者更具吸引力。

例如,没有工具可以在 Mac 上创建客户端代理,因此我们必须能够访问 Windows 机器来执行此操作,使用Silverlight 服务模型代理生成工具(slsvcutil.exe)。该工具生成的源文件允许我们在项目中消耗 WCF 服务。它基本上做了 MonoDevelop 在我们添加 Web 引用到 ASMX Web 服务时自动执行的事情,就像在前两个任务中一样。

注意

重要的一点是使用Silverlight 版本 3.0slsvcutil来创建客户端代理。

除了 Mono Framework 的支持外,还有一个限制:iOS 不允许动态代码生成。这使得任何依赖于System.Reflection.Emit命名空间的代码都无法使用。实际上,System.Reflection.Emit命名空间在 MonoTouch 中根本不可用。

在 Mac 上复制生成的文件后,我们将其添加到项目中,我们就可以使用 WCF 服务了。之前高亮显示的代码显示了如何实例化服务对象。请注意,服务对象的默认构造函数不能使用,因为 MonoTouch 不支持System.Configuration命名空间。

实际通信是通过调用方法异步实现来完成的,在设置其对应完成事件的处理器之后。请注意,在这种情况下,没有使用同步调用或BeginInvoke EndInvoke模式的替代方案:

client.GetBookInfoCompleted += WcfTestServiceClient_GetBookInfoCompleted;
client.GetBookInfoAsync ();

服务返回的结果可以通过指定的EventArgs派生类的Result属性检索:

this.labelResult.Text = String.Format ( "Book title: {0}\nAuthor: {1}", e.Result.Title, e.Result.Name);

还有更多...

当调试消耗 WCF 服务的项目时,请记住设置服务运行在的机器的地址,而不是localhost127.0.0.1。这是因为当我们将应用程序运行在设备上时,应用程序将无法连接到服务。

关于 MonoDevelop 的 WCF 支持的更多信息

消耗 Web 服务配方中显示的添加 Web 引用窗口中,可以通过 MonoDevelop 添加 WCF Web 引用。然而,它尚未完成。

WCF 服务创建

WcfService 服务返回的对象以及实际的服务本身都是在 Mac 上使用 MonoDevelop 完全创建的。由于没有 WCF 项目模板,使用了 空项目 模板。

相关内容

在本章中:

  • 消费 Web 服务

读取 JSON 数据

在本食谱中,我们将学习如何读取 JavaScript 对象表示法 (JSON ) 数据。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 JsonDataApp。在 MainController 的视图中添加一个按钮和一个标签。

如何实现...

  1. 将项目添加到 System.Json 程序集的引用中。

  2. MainController.cs 文件中添加以下 using 指令:

    using System.Json;
    using System.Net;
    using System.IO;
    
    
  3. 输入以下方法:

    private JsonValue GetJsonObject (){
    string responseString = string.Empty;
    Uri uri = new Uri ("http://192.168.1.18:8080/mtjson.txt");
    HttpWebRequest request = new HttpWebRequest (uri);
    request.Method = "GET";
    HttpWebResponse response = request.GetResponse () as HttpWebResponse;
    using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
    responseString = sr.ReadToEnd ();
    }
    response.Close ();
    return JsonValue.Parse (responseString);
    }
    
    
  4. 将处理程序附加到按钮的 TouchUpInside 事件,并在其中输入以下代码:

    JsonValue json = this.GetJsonObject ();
    this.labelResponse.Text = String.Format ("File name: {0}\n Description: {1}", json ["filename"], json ["description"]);
    
    
  5. 最后,在项目目录中运行 xsp 服务器。文件 "mtjson.txt" 包含了 JSON 格式的数据。

它是如何工作的...

JSON 是一种特定的文本格式,它易于阅读和实现。许多流行的网站使用此格式来分发数据。其主要优势是它不受语言限制。JSON 的结构基于名称/值对和数组。在本任务中使用的 JSON 数据对象相当简单:

{
"filename":"mtjson.txt",
"description":"a sample json object"
}

要从网络服务器读取数据,我们只需创建一个 HttpWebRequest 对象,将其 Method 属性设置为 HTTP GET:

HttpWebRequest request = new HttpWebRequest (uri);
request.Method = "GET";

然后,我们需要从服务器获取响应。我们通过检索请求的响应对象,并使用 StreamReader 从其底层流中读取数据来完成此操作:

HttpWebResponse response = request.GetResponse () as HttpWebResponse;
using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
responseString = sr.ReadToEnd ();
}

现在的 responseString 变量包含了之前显示的原始 JSON 数据。为了解析 JSON 数据,MonoTouch 提供了 JsonValue 类。要创建一个 JsonValue 对象,我们使用它的 Parse 静态方法,并将包含 JSON 数据的字符串传递给它:

return JsonValue.Parse (responseString);

要访问 JsonValue 对象解析的数据,我们使用索引器:

this.labelResponse.Text = String.Format ("File name: {0}\nDescription: {1}", json ["filename"], json ["description"]);

更多内容...

如果传递了一个在 JSON 对象中不存在的名称,将会抛出异常。如果不知道 JSON 对象的名称,我们可以使用整数来检索数据:

//json[0], json[1] etc...

JsonValue 类继承自 IEnumerable 接口。

序列化

System.Json 命名空间提供了用于简单解析 JSON 数据的对象。它不提供 JSON 序列化功能。然而,我们可以从一组 KeyValuePair<string, JsonValue> 对象创建一个 JsonObject。要创建之前的 JSON 对象,我们将编写如下内容:

JsonObject obj = new JsonObject (new KeyValuePair<string, JsonValue> ("filename", JsonValue.Parse ("\"mtjson.txt\"")), new KeyValuePair<string, JsonValue>("description", JsonValue.Parse("\"a sample json object\"")));

相关内容

在本章中:

  • 消费 Web 服务

第七章. 多媒体资源

在本章中,我们将介绍以下内容:

  • 选择图像和视频

  • 使用相机捕获媒体

  • 播放视频

  • 播放音乐和声音

  • 使用麦克风录制

  • 直接管理多个相册项目

简介

今天智能手机和平板电脑最重要的功能之一是它们捕捉和管理多媒体资源的能力。无论是照片、视频还是音频,针对这些设备且能够有效处理多媒体的应用程序非常重要。

在本章中,我们将学习如何管理设备上存储的媒体。我们还将了解如何使用设备的多媒体捕获设备(相机和麦克风)来捕捉内容并创建一个将为用户提供丰富体验的应用程序。

更具体地说,我们将讨论以下内容:

  • UIImagePickerController: 这是一个控制器,不仅通过用户界面提供对设备上已保存的照片和视频的访问,还提供了一个用于捕获的相机界面

  • MPMoviePlayerController: 这是一个允许我们播放和流式传输视频文件的控制器

  • MPMediaPickerController: 这是访问已保存内容的默认用户界面,由原生 iPod 应用程序管理

  • MPMusicPlayerController: 这是一个负责播放 iPod 内容的对象

  • AVAudioPlayer: 这是一个允许我们播放声音文件的类

  • AVAudioRecorder: 这是一个允许我们使用麦克风录制音频的类

  • ALAssetsLibrary: 这是一个提供对设备可用资源和其元数据的访问的类

选择图像和视频

在这个菜谱中,我们将学习如何为用户提供从设备相册导入图像和视频的能力。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为ImagePickerApp

如何操作...

  1. MainController的主视图中添加一个UIImageView和一个UIButton

  2. 覆盖MainController类的ViewDidLoad方法,并在其中输入以下代码:

    this.imagePicker = new UIImagePickerController();
    this.imagePicker.FinishedPickingMedia += this.ImagePicker_FinishedPickingMedia;
    this.imagePicker.Canceled += this.ImagePicker_Cancelled;
    this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
    this.buttonChoose.TouchUpInside += delegate {
    this.PresentModalViewController(this.imagePicker, true);
    } ;
    
    
  3. 实现处理FinishedPickingMediaCanceled事件的处理器方法:

    private void ImagePicker_FinishedPickingMedia (object sender, UIImagePickerMediaPickedEventArgs e){
    UIImage pickedImage = e.Info[UIImagePickerController.OriginalImage] as UIImage;
    this.imageView.Image = pickedImage;
    this.imagePicker.DismissModalViewControllerAnimated(true);
    }
    private void ImagePicker_Cancelled (object sender, EventArgs e){
    this.imagePicker.DismissModalViewControllerAnimated(true);
    }
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 点击按钮以显示图像选择器,并通过点击缩略图选择图像。图像将在图像视图中显示。UIImagePickerController在以下屏幕截图中显示:

如何操作...

它是如何工作的...

UIImagePickerController 是 iOS 提供的一个特殊视图控制器,用于选择保存在设备相册中的图像和视频,或从相机中获取。

注意

默认情况下,iOS 模拟器中没有存储在相册中的图像。要将图像添加到模拟器中,取消注释已下载项目源代码中的AddImagesToAlbum方法,并调用一次,将包含图像的计算机上的物理路径作为参数传递。

在初始化图片选择器对象后,我们需要订阅其FinishedPickingMedia事件,该事件为我们提供了用户所选的媒体。在分配给它的处理程序中,我们获取所选的图片:

UIImage pickedImage = e.Info[UIImagePickerController.OriginalImage] as UIImage;

Info属性返回一个包含有关所选媒体各种信息的NSDictionary对象。我们通过传递常量UIImagePickerController.OriginalImage作为键来检索图像。因为字典的值是NSObject类型,所以我们把返回值转换为UIImage。在我们将图像分配给要显示的UIImageView之后,我们关闭控制器:

this.imagePicker.DismissModalViewControllerAnimated(true);

当用户点击控制器的取消按钮时,会触发Canceled事件。我们必须订阅它以关闭控制器,因为它在用户点击取消按钮时不会自动关闭。

还有更多...

我们可以通过图片选择器的SourceType属性定义图片选择器将从中读取的图片/视频的来源。在此示例中,我们使用UIImagePickerController.PhotoLibrary,因为模拟器不支持相机硬件。

选择视频

UIImagePickerController默认只显示图片。要支持视频,其MediaType属性必须被设置。它接受一个string[],包含指定的媒体名称:

this.imagePicker.MediaTypes = new string[] { "public.image", "public.movie" };

要确定用户选择的媒体类型,我们在FinishedPickingMedia处理程序中的字典中检查MediaType键。如果是视频,我们使用MediaUrl键获取其 URL:

if (e.Info[UIImagePickerController.MediaType].ToString() == "public.movie"){
NSUrl mediaUrl = e.Info[UIImagePickerController.MediaURL] as NSUrl;
// Do something useful with the media url.
}

参见

在本章中:

  • 使用相机捕捉媒体

  • 直接管理相册项目

使用相机捕捉媒体

在本食谱中,我们将学习如何使用设备相机来捕捉媒体。

准备工作

打开之前任务中讨论的ImagePickerApp项目。

注意

在 iOS 模拟器上不可用相机功能。此示例只能在设备上运行。有关更多信息,请参阅第十四章部署

如何做...

  1. ViewDidLoad方法内部,替换以下行:

    this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
    
    
  2. 使用以下代码块:

    if (UIImagePickerController.IsSourceTypeAvailable( UIImagePickerControllerSourceType.Camera)){
    this.imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
    } else{
    this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
    }
    
    
  3. FinishedPickingMedia处理程序中,在关闭图片选择器之前添加以下代码:

    pickedImage.SaveToPhotosAlbum(delegate( UIImage image, NSError error) {
    if (null != error){
    Console.WriteLine("Image not saved! Message: {0}", error.LocalizedDescription);
    }
    } );
    
    
  4. 在设备上编译并运行应用程序。

  5. 点击按钮打开相机并拍照。照片将被保存到设备相册中。

它是如何工作的...

在展示相机取景器之前,我们必须确保应用程序运行的实际设备确实具有适当的硬件。我们通过调用UIImagePickerController类的静态IsSourceTypeAvailable方法来完成此操作:

if (UIImagePickerController.IsSourceTypeAvailable( UIImagePickerControllerSourceType.Camera))

如果它返回true,我们将源类型设置为Camera:

this.imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;

这将导致图片选择器控制器启动相机设备而不是加载设备相册。

当用户拍照(或录像)时,它不会自动保存在设备上。要保存它,我们使用UIImage类的SaveToPhotosAlbum方法。此方法接受一个类型为UIImage.SaveStatus的委托,如果发生错误,它会报告错误:

if (null != error){
Console.WriteLine("Image not saved! Message: {0}", error.LocalizedDescription);
}

更多...

摄像头视图也可以自定义。要禁用默认的摄像头控件,将ShowsCameraControls属性设置为false。然后,将您想要的控件的自定义视图传递给CameraOverlayView属性。要触发摄像头的快门,请调用TakePicture方法。

图像编辑

摄像头支持简单的编辑功能,在捕获图像后。此编辑功能允许用户选择图像的特定部分,甚至可以放大到特定区域。要显示编辑控件,将AllowsEditing属性设置为true。编辑后的图像可以从FinishedPickingMedia处理程序中的字典中检索,传递UIImagePickerController.EditedImage键。编辑界面如下截图所示:

图像编辑

参见

在这一章:

  • 选择图像和视频

播放视频

在这个菜谱中,我们将学习如何显示视频播放器界面并播放视频文件。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为PlayVideoApp

如何做...

  1. MainController的主视图中添加一个按钮。

  2. 将视频文件添加到项目中,并将其构建操作设置为内容

  3. MainController.cs文件中输入以下using指令:

    using MonoTouch.MediaPlayer;
    
    
  4. 覆盖MainController类的ViewDidLoad方法,并输入以下代码:

    this.moviePlayer = new MPMoviePlayerController( new NSUrl("videos/video.mov"));
    this.moviePlayer.View.Frame = this.View.Bounds;
    this.View.AddSubview(this.moviePlayer.View);
    
    this.playbackStateChanged = NSNotificationCenter.DefaultCenter.AddObserver( MPMoviePlayerController.PlaybackStateDidChangeNotification, this.MoviePlayer_PlaybackStateChanged);this.finishedPlaying = NSNotificationCenter.DefaultCenter.AddObserver( MPMoviePlayerController.PlaybackDidFinishNotification, this.MoviePlayer_FinishedPlayback);
    this.buttonPlay.TouchUpInside += delegate {
    this.moviePlayer.Play();
    } ;
    
    
  5. MainController类中输入以下方法:

    private void MoviePlayer_PlaybackStateChanged(NSNotification ntf){
    Console.WriteLine("Movie player load state changed: {0}", this.moviePlayer.PlaybackState);
    }
    private void MoviePlayer_FinishedPlayback(NSNotification ntf){
    Console.WriteLine("Movie player finished playing.");
    }
    
    
  6. 在模拟器上编译并运行应用程序。

  7. 点击按钮,视频将加载并开始播放。在 MonoDevelop 的应用程序输出中查看显示的消息。

工作原理...

MPMoviePlayerController播放存储在本地的视频文件或从网络流式传输的视频文件。我们使用接受NSUrl参数的构造函数来初始化它:

this.moviePlayer = new MPMoviePlayerController( new NSUrl("videos/video.mov"));

NSUrl对象映射到我们添加到项目中的本地文件。

创建实例后,我们为其视图定义一个框架并将其添加到我们的视图中:

this.moviePlayer.View.Frame = this.View.Bounds;
this.View.AddSubview(this.moviePlayer.View);

突出的代码向默认的通知中心添加观察者,这样当播放状态改变或完成时,我们会收到通知。然后,我们调用它的Play方法,MPMoviePlayerController的视图就会显示,视频开始播放。

MoviePlayer_PlaybackStateChanged方法内部,我们输出PlaybackState属性:

Console.WriteLine("Movie player load state changed: {0}", this.moviePlayer.PlaybackState);

此属性告诉我们播放状态,例如PausedPlayingSeekingForwardSeekingBackward等。

更多...

除了这个例子中使用的之外,我们还可以为MPMoviePlayerController添加更多通知的观察者,其中一些是:

  • DidEnterFullscreenNotification: 此通知表示用户点击了全屏控制,控制器已进入 fullscreen 模式。

  • DidExitFullscreenNotification: 此通知表示控制器已离开 fullscreen 模式。

  • DurationAvailableNotification: 此通知表示控制器已收到视频持续时间的信息。

  • LoadStateDidChangeNotification: 此通知对于网络播放很有用,当控制器在缓冲区中完成预加载媒体时被触发。

  • NaturalSizeAvailableNotification: 当电影帧的尺寸可用时,此通知被触发。大小可以通过播放器的 NaturalSize 属性检索。

  • NowPlayingMovieDidChangeNotification: 当播放器的视频内容发生变化时,此通知被触发。当前内容可以通过其 ContentUrl 属性获取。

无线流媒体

从 iOS 版本 4.3 开始,MPMoviePlayerController 可以用来将视频流式传输到 Apple 的 AirPlay 兼容设备。要启用它,将其 AllowsAirPlay 属性设置为 true。当 MPMoviePlayerController 显示时,它将提供一个界面,允许用户选择它检测到的设备。

相关内容

在本章中:

  • 播放音乐和声音

播放音乐和声音

在本教程中,我们将学习如何播放存储在设备上的简单音频文件和歌曲。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 PlayMusicApp

注意

此示例在模拟器上无法工作。您还需要在设备上至少存储一首歌曲。

如何操作...

  1. MainController 的视图中添加三个按钮。

  2. MainController.cs 文件中添加以下 using 指令:

    using MonoTouch.MediaPlayer;
    
    
  3. 在类中添加两个字段:

    private MPMusicPlayerController musicPlayerController;
    private MPMediaPickerController mediaPicker;
    
    
  4. 重写 MainController 类的 ViewDidLoad 方法,并输入以下代码:

    this.mediaPicker = new MPMediaPickerController(MPMediaType.Music);
    this.mediaPicker.ItemsPicked += MediaPicker_ItemsPicked;
    this.mediaPicker.DidCancel += MediaPicker_DidCancel;
    this.musicPlayerController = MPMusicPlayerController.ApplicationMusicPlayer;
    this.buttonSelectSongs.TouchUpInside += delegate {
    this.PresentModalViewController(this.mediaPicker, true);
    } ;
    this.buttonPlay.TouchUpInside += delegate {
    this.musicPlayerController.Play();
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.musicPlayerController.Stop();
    } ;
    
    
  5. 添加以下方法:

    private void MediaPicker_ItemsPicked ( object sender, ItemsPickedEventArgs e){
    this.musicPlayerController.SetQueue(e.MediaItemCollection);
    this.DismissModalViewControllerAnimated(true);
    }
    private void MediaPicker_DidCancel (object sender, EventArgs e){
    this.mediaPicker.DismissModalViewControllerAnimated(true);
    }
    
    
  6. 在设备上编译并运行应用程序。

  7. 点击 选择歌曲 按钮,并选择一首或多首歌曲。

它是如何工作的...

MPMediaPickerController 提供与原生 iPod 应用程序相同的用户界面。MPMusicPlayerController 负责播放设备上存储的歌曲。

我们首先初始化媒体选择器,通过其构造函数传递我们想要它查找的媒体类型:

this.mediaPicker = new MPMediaPickerController(MPMediaType.Music);

之后,我们订阅其 ItemsPickedDidCancel 事件,以便我们可以捕获用户的反馈:

this.mediaPicker.ItemsPicked += MediaPicker_ItemsPicked;
this.mediaPicker.DidCancel += MediaPicker_DidCancel;

突出的代码显示了如何初始化音乐播放器对象。这里演示的选项 MPMusicPlayerController.ApplicationMusicPlayer 创建了一个仅针对应用程序的实例。另一个可用的选项 MPMusicPlayerController.iPodMusicPlayer 创建了一个实例,允许在应用程序处于后台时播放媒体,类似于 iPod 应用程序。

MediaPicker_ItemsPicked 处理程序中,我们通过其 SetQueue 方法将用户选择的歌曲设置到音乐播放器中:

this.musicPlayerController.SetQueue(e.MediaItemCollection);

之后,我们取消显示模态媒体选择器控制器。播放和停止歌曲分别通过 MPMusicPlayerControllerPlay()Stop() 方法实现。

还有更多...

MPMusicPlayerController 包含有关当前播放项的信息。此信息可以通过其 NowPlayingItem 属性访问。它是 MPMediaItem 类型,包含有关当前播放媒体的各种类型信息。以下示例获取正在播放的歌曲的标题:

Console.WriteLine(this.musicPlayerController .NowPlayingItem.ValueForProperty(MPMediaItem.TitleProperty));

播放声音文件

MPMusicPlayerController 是一个专门设计用来管理和播放存储在设备 iPod 库中的项目和播放列表的对象。

对于播放简单的声音文件,MonoTouch 为 iOS 的 AVAudioPlayer 类提供了一个包装器。以下是其最简单用法的示例:

using MonoTouch.AVFoundation;
//...
AVAudioPlayer audioPlayer = AVAudioPlayer.FromUrl( new NSUrl("path/to/sound file"));
audioPlayer.Play();

参见

在本章中:

  • 播放视频

使用麦克风录制

在本食谱中,我们将学习如何使用设备的麦克风来录制声音。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 RecordSoundApp

注意

此示例在模拟器上无法工作。

如何操作...

  1. MainController 的视图中添加两个按钮。

  2. MainController.cs 文件中输入以下 using 指令:

    using System.IO;
    using MonoTouch.AVFoundation;
    using MonoTouch.AudioToolbox;
    
    
  3. 覆盖 ViewDidLoad 方法,并在其中添加以下代码:

    string soundFile = Path.Combine(Environment.GetFolderPath( Environment.SpecialFolder.Personal), "sound.wav");
    NSUrl soundFileUrl = new NSUrl(soundFile);
    NSDictionary recordingSettings = NSDictionary.FromObjectAndKey( AVAudioSettings.AVFormatIDKey, NSNumber.FromInt32((int) AudioFileType.WAVE));
    NSError error = null;
    this.audioRecorder = AVAudioRecorder.ToUrl( soundFileUrl, recordingSettings, out error);
    this.buttonStart.TouchUpInside += delegate {
    this.audioRecorder.Record();
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.audioRecorder.Stop();
    AVAudioPlayer player = AVAudioPlayer.FromUrl(soundFileUrl);
    player.Play();
    } ;
    
    
  4. 在设备上编译并运行应用程序。

  5. 点击 开始录制 按钮开始录制音频,例如,可以说些话来录制你的声音。

  6. 点击 停止录制 按钮停止录制并播放回放。

工作原理...

AVAudioRecorder 类提供了录制功能。它是通过将捕获的音频直接流式传输到文件系统来实现的。要初始化 AVAudioRecorder 的实例,我们使用它的静态 ToUrl 方法:

this.audioRecorder = AVAudioRecorder.ToUrl( soundFileUrl, recordingSettings, out error);

如果与 NSUrl 变量对应的文件已经存在,它将被覆盖。

recordingSettings 变量是 NSDictionary 类型,包含输出声音文件的设置。在初始化 AVAudioRecorder 时,我们必须提供至少一些最小设置。在这里,我们将声音格式设置为纯 wav:

NSDictionary recordingSettings = NSDictionary.FromObjectAndKey( AVAudioSettings.AVFormatIDKey, NSNumber .FromInt32((int)AudioFileType.WAVE));

要指示记录器开始录制,我们只需调用它的 Record() 方法:

this.audioRecorder.Record();

当用户点击 停止录制 按钮时,录制停止,保存的声音开始通过 AVAudioPlayer: 播放。

this.audioRecorder.Stop();
AVAudioPlayer player = AVAudioPlayer.FromUrl(soundFileUrl);
player.Play();

还有更多...

AVAudioRecorder 类还提供了声音计测选项。要启用声音计测,将它的 MeteringEnabled 属性设置为 true。然后我们可以在特定通道上输出峰值功率(以分贝为单位)。为了对我们录制的第一个通道执行此操作,请在 Record() 方法调用之后立即添加以下代码:

ThreadPool.QueueUserWorkItem(delegate {
while (this.audioRecorder.Recording){
this.audioRecorder.UpdateMeters();
Console.WriteLine(this.audioRecorder.PeakPower(0));
}
} );

PeakPower 方法接受通道的零基索引并返回该通道的峰值(以分贝为单位)。在调用 PeakPower 方法之前,请立即调用 UpdateMeters() 方法以获取最新的读数。

注意,在录制器上启用测光会使用 CPU 资源。如果您不打算使用测光值,请勿启用它。

预定义时间录制

要录制预定义时间的音频,而无需用户停止录制,请调用 RecordFor(double) 方法。其参数指定了录制的时间(以秒为单位)。

相关内容

在本章中:

  • 播放音乐和声音

直接管理多个相册项目

在本食谱中,我们将讨论以编程方式访问设备的照片相册。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 ManageAlbumApp

注意

此示例适用于模拟器。照片相册中必须至少存在一个图像。

如何操作...

  1. MainController 的主视图中添加一个按钮。

  2. MainController.cs 文件中输入以下 using 指令:

    using MonoTouch.AssetsLibrary;
    
    
  3. 重写 ViewDidLoad 方法,并在其中输入以下代码:

    this.buttonEnumerate.TouchUpInside += delegate {
    this.assetsLibrary = new ALAssetsLibrary();
    this.assetsLibrary.Enumerate(ALAssetsGroupType.All, this.GroupsEnumeration, this.GroupsEnumerationFailure);
    } ;
    
    
  4. 在类中添加以下方法:

    private void GroupsEnumeration(ALAssetsGroup assetGroup, ref bool stop){
    if (null != assetGroup){
    stop = false;
    assetGroup.SetAssetsFilter(ALAssetsFilter.AllPhotos);
    assetGroup.Enumerate(this.AssetEnumeration);
    }
    }
    private void AssetEnumeration(ALAsset asset, int index, ref bool stop){
    if (null != asset){
    stop = false;
    Console.WriteLine("Asset url: {0}", asset.DefaultRepresentation.Url.AbsoluteString);
    }
    }
    private void GroupsEnumerationFailure(NSError error){
    if (null != error){
    Console.WriteLine("Error enumerating asset groups! Message: {0}", error.LocalizedDescription);
    }
    }
    
    
  5. 编译并运行应用程序。

  6. 点击 枚举资产 按钮,并观察保存照片的 URL 在 应用程序输出 面板中显示。

它是如何工作的...

ALAssetsLibrary 类提供了对设备相册项的访问。这些项由 ALAsset 类表示,并按组划分,由 ALAssetGroup 类表示。

我们需要做的第一件事是枚举资产组。为此,调用 Enumerate 方法:

this.assetsLibrary.Enumerate(ALAssetsGroupType.All, this.GroupsEnumeration, this.GroupsEnumerationFailure);

第一个参数是 ALAssetGroupTypes 类型,它指示资产库要枚举哪些资产组。传递 ALAssetGroupTypes.All 表示我们想要枚举所有资产组。其他两个参数是委托类型。GroupsEnumeration 方法是我们读取组数据的地方,而 GroupsEnumerationFailure 将在发生错误时触发。当第一次调用 Enumerate 方法时,会要求用户授权应用程序访问设备的资产。如果用户拒绝访问,将触发失败方法。下次调用 Enumerate 方法时,访问消息会再次出现。

GroupsEnumeration 方法的签名如下:

private void GroupsEnumeration(ALAssetsGroup assetGroup, ref bool stop)

assetGroup 参数包含组的信息。

注意 stop 参数,它被声明为 ref。当枚举发生时,方法会被触发一次以返回第一个组,并且第二次不会调用,无论还有多少组存在。为了强制它继续被调用以枚举所有组,我们必须将 stop 变量设置为 false。当所有组都被枚举后,方法会最后一次被调用,此时 assetGroup 变量被设置为 null。因此,我们需要检查这一点。将这些放入代码中:

if (null != assetGroup){
// Continue enumerating
stop = false;
// Determine what assets to enumerate
assetGroup.SetAssetsFilter(ALAssetsFilter.AllPhotos);
// Enumerate assets
assetGroup.Enumerate(this.AssetEnumeration);
}

ALAssetGroup 类的实例上调用 SetAssetsFilter 方法,我们指示它过滤我们希望它查找的资产类型。之后,过程与组枚举类似。ALAssetGroup 类还包含一个 Enumerate 方法。它接受一个代表类型(在这里由 AssetsEnumeration 方法表示)的参数。其实现与 GroupsEnumeration 方法类似:

if (null != asset){
// Continue enumerating assets
stop = false;
// Output the asset url
Console.WriteLine("Asset url: {0}", asset.DefaultRepresentation.Url.AbsoluteString);

ALAsset 类包含各种信息和属性。大部分信息存储在其 DefaultRepresentation 属性中,该属性的类型为 ALAssetRepresentation

更多内容...

如果我们感兴趣的资产是一张图片,我们可以通过 DefaultRepresentation 属性获取实际图片:

CGImage image = asset.DefaultRepresentation.GetImage();

读取 EXIF 数据

我们可以通过 ALAssetRepresentationMetadata 属性读取照片的 交换图像文件格式 (EXIF) 元数据,Metadata 属性的类型为 NSDictionary,如下所示:

NSDictionary metaData = asset.DefaultRepresentation.Metadata;
if (null != metaData){
NSDictionary exifData = (NSDictionary)metaData[ new NSString("{Exif}")];
}

获取单个资产

如果我们知道资产的 URL,我们也可以通过 ALAssetLibraryAssetForUrl 方法检索单个资产。

参见

在本章中:

  • 选择图片和视频

第八章。集成 iOS 功能

在本章中,我们将介绍以下内容:

  • 开始电话通话

  • 发送短信和电子邮件

  • 在我们的应用程序中使用短信

  • 在我们的应用程序中使用电子邮件消息

  • 管理地址簿

  • 显示联系人

  • 管理日历

简介

移动设备为用户提供了一系列功能。创建一个与应用程序这些功能交互以向用户提供完整体验的应用程序当然可以被视为一种优势。

在本章中,我们将讨论 iOS 的一些最常见功能以及如何将它们的一些或全部功能集成到我们的应用程序中。我们将看到如何使用原生平台应用程序或通过在我们的项目中集成原生用户界面来使用户能够进行电话通话、发送短信和电子邮件。此外,我们还将讨论以下组件:

  • MFMessageComposeViewController: 这个控制器适合发送文本(SMS)消息

  • MFMailComposeViewController: 这是一个用于发送带或不带附件的电子邮件的控制器

  • ABAddressBook: 这是一个提供我们访问地址簿数据库的类

  • ABPersonViewController: 这是一个显示和/或编辑地址簿中联系人信息的控制器

  • EKEventStore: 这是一个负责管理日历事件的类

此外,我们还将学习如何读取和保存联系人信息,如何显示联系人详细信息,以及如何与设备日历交互。

注意,本章中的一些示例可能需要设备。例如,模拟器不包含消息应用程序。要将应用程序部署到设备,您需要通过 Apple 的开发者门户注册为 iOS 开发者,并获得 MonoTouch 的商业许可证。

开始电话通话

在本食谱中,我们将学习如何调用原生电话应用程序,允许用户进行通话。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 PhoneCallApp

注意

原生电话应用程序在模拟器上不可用。它仅在 iPhone 设备上可用。

如何操作...

  1. MainController 的视图中添加一个按钮,并重写 ViewDidLoad 方法。使用以下代码实现它。如果您实际上想要拨打电话,请将数字替换为真实的电话号码:

    this.buttonCall.TouchUpInside += delegate {
    NSUrl url = new NSUrl("tel:+123456789012");
    if (UIApplication.SharedApplication.CanOpenUrl(url)){
    UIApplication.SharedApplication.OpenUrl(url);
    } else{
    Console.WriteLine("Cannot open url: {0}", url.AbsoluteString);
    }
    } ;
    
    
  2. 在设备上编译并运行应用程序。点击 Call! 按钮开始通话。以下截图显示了电话应用程序正在拨打电话:

如何操作...

它是如何工作的...

通过 UIApplication.SharedApplication 静态属性,我们可以访问应用程序的 UIApplication 对象。我们可以使用它的 OpenUrl 方法,该方法接受一个 NSUrl 变量来发起通话:

UIApplication.SharedApplication.OpenUrl(url);

由于并非所有 iOS 设备都支持原生电话应用程序,因此首先检查其可用性将是有用的:

if (UIApplication.SharedApplication.CanOpenUrl(url))

当调用 OpenUrl 方法时,原生电话应用将被执行,并且它将立即开始拨打电话。请注意,需要 tel: 前缀来发起通话。

更多内容...

MonoTouch 也支持 CoreTelephony 框架,通过 MonoTouch.CoreTelephony 命名空间。这是一个简单的框架,提供了有关通话状态、连接、运营商信息等方面的信息。请注意,当通话开始时,原生电话应用进入前台,导致应用挂起。以下是对 CoreTelephony 框架的简单使用:

CTCallCenter callCenter = new CTCallCenter();
callCenter.CallEventHandler = delegate(CTCall call) {
Console.WriteLine(call.CallState);
} ;

注意,处理程序是用等号(=)而不是常见的加等号(+=)组合赋值的。这是因为 CallEventHandler 是一个属性而不是一个事件。当应用进入后台时,事件不会分配给它。当应用返回前台时,只有最后发生的事件会被分配。

关于 OpenUrl 的更多信息

OpenUrl 方法可以用来打开各种原生和非原生应用。例如,要在 Safari 中打开网页,只需创建一个包含以下链接的 NSUrl 对象:

NSUrl url = new NSUrl("http://www.packtpub.com");

相关链接

本章内容:

  • 发送短信和电子邮件

发送短信和电子邮件

在这个菜谱中,我们将学习如何在我们的应用中调用原生邮件和消息应用。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 SendTextApp

如何操作...

  1. MainController 的主视图中添加两个按钮。覆盖 MainController 类的 ViewDidLoad 方法,并使用以下代码实现:

    this.buttonSendText.TouchUpInside += delegate {
    NSUrl textUrl = new NSUrl("sms:");
    if (UIApplication.SharedApplication.CanOpenUrl(textUrl)){
    UIApplication.SharedApplication.OpenUrl(textUrl);
    } else{
    Console.WriteLine("Cannot send text message!");
    }
    } ;
    this.buttonSendEmail.TouchUpInside += delegate {
    NSUrl emailUrl = new NSUrl("mailto:");
    if (UIApplication.SharedApplication.CanOpenUrl(emailUrl)){
    UIApplication.SharedApplication.OpenUrl(emailUrl);
    } else{
    Console.WriteLine("Cannot send e-mail message!");
    }
    } ;
    
    
  2. 在设备上编译并运行应用程序。点击其中一个按钮以打开相应的应用。

它是如何工作的...

再次使用 OpenUrl 方法,我们可以发送文本或电子邮件消息。在这个示例代码中,只需使用 sms: 前缀就会打开原生短信应用。在 sms: 前缀后添加手机号码将打开原生消息应用:

UIApplication.SharedApplication.OpenUrl(new NSUrl("sms:+123456789012"));

如何工作...

注意

除了收件人号码外,在显示原生短信应用之前,没有其他数据可以设置。

对于打开原生电子邮件应用,过程类似。传递 mailto: 前缀将打开编辑邮件控制器。

UIApplication.SharedApplication.OpenUrl(new NSUrl("mailto:"));

如何工作...

mailto: URL 方案支持各种参数来定制电子邮件消息。这些参数允许我们输入发送者地址、主题和消息:

UIApplication.SharedApplication.OpenUrl("mailto:recipient@example.com?subject=Email%20with%20MonoTouch!&body=This%20is%20the%20message%20body!");

更多内容...

虽然 iOS 提供了打开原生消息应用的方法,但在电子邮件的情况下,预先定义消息内容,这里的控制从应用内部停止。实际上通过代码发送消息是没有办法的。是否发送消息将由用户决定。

关于打开外部应用的更多信息

OpenUrl方法提供了一个打开原生消息应用程序的接口。打开外部应用程序有一个缺点:调用OpenUrl方法的程序会转到后台。在 iOS 版本 3.*之前,这是通过应用程序提供消息的唯一方式。从 iOS 版本 4.0 开始,Apple 向 SDK 提供了消息控制器。以下菜谱讨论了它们的用法。

相关内容

在本章中:

  • 开始电话

  • 在我们的应用程序中使用文本消息

在我们的应用程序中使用文本消息

在这个菜谱中,我们将学习如何在我们的应用程序中使用原生消息用户界面提供文本消息功能。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为TextMessageApp

如何做到这一点...

  1. MainController的视图中添加一个按钮。在MainController.cs文件中输入以下指令:

    using MonoTouch.MessageUI;
    
    
  2. 使用以下代码实现ViewDidLoad方法,根据您的意愿更改接收者号码和/或消息正文:

    private MFMessageComposeViewController messageController;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.buttonSendMessage.TouchUpInside += delegate {
    if (MFMessageComposeViewController.CanSendText){
    this.messageController = new MFMessageComposeViewController();
    this.messageController.Recipients = new string[] { "+123456789012" };
    this.messageController.Body = "Text from MonoTouch";
    this.messageController.MessageComposeDelegate = new MessageComposerDelegate();
    this.PresentModalViewController( this.messageController, true);
    } else{
    Console.WriteLine("Cannot send text message!");
    }
    } ;
    }
    
    
  3. 添加以下嵌套类:

    private class MessageComposerDelegate : MFMessageComposeViewControllerDelegate{
    public override void Finished (MFMessageComposeViewController controller, MessageComposeResult result){
    switch (result){
    case MessageComposeResult.Sent:
    Console.WriteLine("Message sent!");
    break;
    case MessageComposeResult.Cancelled:
    Console.WriteLine("Message cancelled!");
    break;
    default:
    Console.WriteLine("Message sending failed!");
    break;
    }
    controller.DismissModalViewControllerAnimated(true);
    }
    }
    
    
  4. 在设备上编译并运行应用程序。

  5. 点击发送消息按钮以打开消息控制器。点击发送按钮发送消息,或点击取消按钮返回应用程序。

它是如何工作的...

MonoTouch.MessageUI命名空间包含允许我们在 iOS 应用程序中实现消息的必要 UI 元素。对于文本消息(SMS),我们需要MFMessageComposeViewController类。

只有 iPhone 能够直接发送短信。在 iOS 5 中,iPod 和 iPad 也可以发送短信,但用户可能没有在设备上启用此功能。因此,检查可用性是最佳实践。《MFMessageComposeViewController》类包含一个名为CanSendText的静态方法,它返回一个布尔值,指示我们是否可以使用此功能。在这种情况下,重要的是我们应该在初始化控制器之前检查发送短信是否可用。这是因为当你在不支持短信的设备或模拟器上尝试初始化控制器时,你将在屏幕上看到以下消息:

它是如何工作的...

为了确定用户在消息 UI 中采取了哪些操作,我们实现一个Delegate对象并重写Finished方法:

private class MessageComposerDelegate : MFMessageComposeViewControllerDelegate

MonoTouch 提供的另一个选项是订阅MFMessageComposeViewController类的Finished事件。

Finished方法中,我们可以根据MessageComposeResult参数提供功能。它的值可以是以下三个之一:

  1. Sent: 这个值表示消息已成功发送。

  2. Cancelled: 这个值表示用户点击了取消按钮,消息将不会发送。

  3. Failed: 这个值表示消息发送失败。

最后要做的就是取消消息控制器,操作如下:

controller.DismissModalViewControllerAnimated(true);

在初始化控制器后,我们可以将收件人和正文消息设置到相应的属性中:

this.messageController.Recipients = new string[] { "+123456789012" };
this.messageController.Body = "Text from MonoTouch";

Recipients属性接受一个字符串数组,允许有多个收件人号码。

你可能已经注意到,消息控制器的Delegate对象被设置为它的MessageComposeDelegate属性,而不是常见的Delegate。这是因为MFMessageComposeViewController类直接继承自UINavigationController类,所以Delegate属性接受UINavigationControllerDelegate类型的值。

还有更多...

SDK 提供了发送文本消息的用户界面,并不意味着它是可定制的。就像调用原生消息应用一样,是否发送消息或丢弃消息的决定权在用户手中。实际上,在控制器显示在屏幕上之后,任何尝试更改实际对象或其任何属性的操作都将失败。此外,用户可以更改或删除收件人和消息正文。真正的优势在于,消息用户界面是在我们的应用程序内显示的,而不是单独运行。

仅限短信

MFMessageComposeViewController只能用于发送短消息服务(SMS)消息,而不能发送多媒体消息服务(MMS)

在我们的应用程序中使用电子邮件消息

在这个菜谱中,我们将学习如何在应用程序中使用电子邮件消息界面。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为EmailMessageApp

如何做到这一点...

  1. MainController的视图中添加一个按钮,并在MainController.cs文件中的MonoTouch.MessageUI命名空间中。

  2. ViewDidLoad方法中输入以下代码:

    this.buttonSendEmail.TouchUpInside += delegate {
    this.mailController = new MFMailComposeViewController();
    this.mailController.SetToRecipients(new string[] { "recipient@example.com" });
    this.mailController.SetSubject("Email from MonoTouch!");
    this.mailController.SetMessageBody("This is the message body!", false);
    this.mailController.Finished += this.MailController_Finished;
    if (MFMailComposeViewController.CanSendMail){
    this.PresentModalViewController(this.mailController, true);
    } else{
    Console.WriteLine("Cannot send email!");
    }
    } ;
    
    
  3. 添加以下方法:

    private void MailController_Finished (object sender, MFComposeResultEventArgs e){
    switch (e.Result){
    case MFMailComposeResult.Sent:
    Console.WriteLine("Email sent!");
    break;
    case MFMailComposeResult.Saved:
    Console.WriteLine("Email saved!");
    break;
    case MFMailComposeResult.Cancelled:
    Console.WriteLine("Email sending cancelled!");
    break;
    case MFMailComposeResult.Failed:
    Console.WriteLine("Email sending failed!");
    if (null != e.Error){
    Console.WriteLine("Error message: {0}", e.Error.LocalizedDescription);
    }
    break;
    }
    e.Controller.DismissModalViewControllerAnimated(true);
    }
    
    
  4. 在模拟器或设备上编译并运行应用程序。

  5. 点击发送电子邮件按钮以显示邮件用户界面。发送或取消消息。应用程序将在模拟器上工作,并且行为与设备上的原生邮件应用相同,只是消息实际上不会发送或保存。

它是如何工作的...

MFMailComposeViewController类提供了原生邮件编写界面。为了确定设备是否能够发送电子邮件,我们首先检查其CanSendMail属性。

MFMessageComposeViewController类似,它包含一个Finished事件,我们使用它来响应用户操作,而无需实现Delegate对象。我们通过MailController_Finished方法来实现,基于MFComposeResultEventArgs.Result属性,该属性的类型为MFMailComposeResult。其可能的值将包括以下之一:

  • Sent: 这个值表示电子邮件消息已排队待发送

  • Saved: 这个值表示用户点击了取消按钮,动作表单中的保存草稿选项自动出现

如何工作...

  • Cancelled: 此值表示用户在控制器上点击 取消 按钮并在动作表中选择了 删除草稿 选项

  • Failed: 此值表示电子邮件消息发送失败

在初始化对象之后,我们可以通过相应的 Set 前缀方法来分配收件人列表、主题和消息正文:

this.mailController.SetToRecipients(new string[] { "recipient@example.com" });
this.mailController.SetSubject("Email from MonoTouch!");
this.mailController.SetMessageBody("This is the message body!", false);

SetMessageBody 消息的第二个参数,如果设置为 true,则通知控制器该消息应被视为 HTML 格式。

更多内容...

除了简单的或 HTML 格式的文本外,我们还可以发送附件。我们可以使用 AddAttachmentData 方法来完成此操作:

this.mailController.AddAttachmentData(UIImage.FromFile("image.jpg"). AsJPEG(), "image/jpg", "image.jpg");

第一个参数是 NSData 类型,应包含附件的内容。在这种情况下,我们通过 UIImage.AsJPEG() 方法附加一个图片,该方法返回一个包含在 NSData 对象中的图片内容。第二个参数代表附件的 Multipurpose Internet Mail Extensions (MIME) 类型,第三个参数是文件名。项目源代码包含一个完整并带有注释的示例。

草稿动作表

当用户点击 取消 按钮时显示的动作表由 MFMailComposeViewController 自动处理。

相关内容

在本章中:

  • 在我们的应用程序中使用短信

管理地址簿

在本食谱中,我们将讨论如何访问和管理设备地址簿中存储的用户联系人。

准备中

在 MonoDevelop 中创建一个新的项目,并将其命名为 AddressBookApp

如何操作...

  1. MainController 的视图中添加一个按钮。在 MainController.cs 文件中输入以下 using 指令:

    using MonoTouch.AddressBook;
    
    
  2. 覆盖 ViewDidLoad 方法:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.buttonGetContacts.TouchUpInside += delegate {
    ABAddressBook addressBook = new ABAddressBook();
    ABPerson[] contacts = addressBook.GetPeople();
    foreach (ABPerson eachPerson in contacts){
    Console.WriteLine(string.Format("{0} {1}", eachPerson.LastName, eachPerson.FirstName));
    }
    } ;
    }
    
    
  3. 在模拟器上编译并运行应用程序。

  4. 点击 获取联系人 按钮,并观察联系人名称在 MonoDevelop 的 应用程序输出 面板中显示。

注意

在安装 iOS SDK 之后,模拟器不包含任何联系人。您可以像在设备上一样添加联系人。

如何工作...

MonoTouch.AddressBook 命名空间包含所有允许我们管理设备地址簿的类。要直接访问数据,我们需要 ABAddressBook 类的一个实例:

ABAddressBook addressBook = new ABAddressBook();

要获取地址簿中存储的所有联系人,我们调用其 GetPeople() 方法:

ABPerson[] contacts = addressBook.GetPeople();

此方法返回一个 ABPerson 对象数组,其中包含所有单个联系人的信息。要读取联系人的详细信息,我们遍历 ABPerson 数组,并使用 FirstNameLastName 属性分别获取每个联系人的名和姓:

Console.WriteLine(string.Format("{0} {1}", eachPerson.LastName, eachPerson.FirstName));

更多内容...

要获取联系人的存储电话号码,请调用 GetPhones() 方法:

ABMultiValue<string> phones = eachPerson.GetPhones();
Console.WriteLine(phones[0].Value);

它返回一个 ABMultiValue<string> 类型的对象。ABMultiValue<T> 是一个泛型集合,特别设计用于地址簿的多个值。

向联系人添加电话号码

要向联系人添加电话号码,我们可以使用ABPerson类的SetPhones方法。它接受一个ABMultiValue<string>对象作为其参数,但我们不能向ABMultiValue对象添加新值。然而,我们可以将值写入ABMutableMultiValue<T>对象:

ABMutableMultiValue<string> newPhones = phones.ToMutableMultiValue();

这行代码创建了一个新的ABMutableMultiValue<string>对象实例,然后我们使用它来添加我们想要的电话号码:

newPhones.Add("+120987654321", ABPersonPhoneLabel.iPhone);
eachPerson.SetPhones(newPhones);
addressBook.Save();

Add方法的第二个参数是电话号码在保存到联系人时将拥有的标签。调用ABAddressBook.Save()方法很重要,否则更改将不会保存。

显示联系人

在这个菜谱中,我们将学习如何使用原生地址簿用户界面来显示联系人信息。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为DisplayContactApp。在MainController的视图中添加一个按钮。

如何做到这一点...

  1. AppDelegate类中为UINavigationController创建一个字段:

    UINavigationController navController;
    
    
  2. 实例化导航控制器,将其根控制器传递为MainController的实例:

    this.navController = new UINavigationController(new MainController());
    
    
  3. 将导航控制器设置为窗口的根视图控制器:

    window.RootViewController = this.navController;
    
    
  4. MainController.cs文件中添加命名空间MonoTouch.AddressBookMonoTouch.AddressBookUI

  5. 覆盖MainController类的ViewDidLoad方法,并使用以下代码实现:

    ABAddressBook addressBook = new ABAddressBook();
    ABPerson[] contacts = addressBook.GetPeople();
    ABPersonViewController personController = new ABPersonViewController();
    personController.DisplayedPerson = contacts[0];
    this.buttonDisplayContact.TouchUpInside += delegate {
    this.NavigationController.PushViewController( personController, true);
    } ;
    
    
  6. 在模拟器或设备上编译并运行应用程序。

  7. 点击显示第一个联系人按钮以显示联系人详细信息。

它是如何工作的...

MonoTouch.AddressBookUI命名空间包含原生Contacts应用程序使用的控制器,允许用户显示和管理联系人。每个联系人的详细信息都可以使用ABPersonViewController查看。此控制器必须通过UINavigationController来展示,否则将无法正确显示。

初始化后,我们将想要显示的ABPerson对象设置为其DisplayedPerson属性:

ABPersonViewController personController = new ABPersonViewController();
personController.DisplayedPerson = contacts[0];

然后,我们将其推送到导航控制器的堆栈中:

this.NavigationController.PushViewController(personController, true);

还有更多...

ABPersonViewController也可以用于编辑。为此,将AllowsEditing属性设置为true

personController.AllowsEditing = true;

结果将与原生Contacts应用程序完全相同:

还有更多...

注意,更改通常通过ABPersonViewController保存。

其他地址簿控制器

MonoTouch.AddressBookUI命名空间包含我们创建自定义联系人应用程序所需的所有控制器:

  • ABPeoplePickerNavigationController:这是一个显示已保存联系人的导航控制器。用户可以从列表中选择一个联系人。

  • ABPersonViewController:这个控制器在之前的示例中有描述。

  • ABNewPersonViewController:这是创建新联系人的控制器。

  • ABUnknownPersonViewController: 这是用于创建新联系人的部分数据显示的控制器。这与我们在设备上最近通话列表中点击未知号码时显示的控制器类似。

相关内容

在本章中:

  • 管理地址簿

管理日历

在本食谱中,我们将学习如何创建一个事件并将其保存到设备的日历数据库中。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 CalendarEventsApp

注意

此项目必须在设备上执行。本机 Calendar 应用程序未安装在模拟器上。

如何操作...

  1. MainController 的主视图中添加一个按钮。在 MainController.cs 文件中添加命名空间 MonoTouch.EventKit

  2. 最后,在 ViewDidLoad 方法中输入以下代码:

    this.buttonDisplayEvents.TouchUpInside += delegate {
    EKEventStore evStore = new EKEventStore();
    NSPredicate evPredicate = evStore.PredicateForEvents( DateTime.Now, DateTime.Now.AddDays(30), evStore.Calendars);
    evStore.EnumerateEvents(
    evPredicate, delegate(EKEvent calEvent, ref bool stop) {
    if (null != calEvent){
    stop = false;
    Console.WriteLine("Event title: {0}\nEvent start date: {1}", calEvent.Title, calEvent.StartDate);
    }
    } );
    } ;
    
    
  3. 在设备上编译并运行应用程序。

  4. 点击 显示事件 按钮以在 应用程序输出 面板中输出未来 30 天的日历事件。

工作原理...

MonoTouch.EventKit 命名空间负责管理日历事件。为了读取已存储的事件,我们首先初始化一个 EKEventStore 对象:

EKEventStore evStore = new EKEventStore();

EKEventStore 类为我们提供了访问已存储事件的权限。要检索日历事件,我们需要一个类型为 NSPredicate 的谓词。我们可以通过 EKEventStore 类的 PredicateForEvents 方法创建一个实例:

NSPredicate evPredicate = evStore.PredicateForEvents(DateTime.Now, DateTime.Now.AddDays(30), evStore.Calendars);

前两个参数的类型为 NSDate(可以隐式转换为 DateTime),表示要搜索事件的开始和结束日期。第三个参数的类型为 EKCalendar[],它是要搜索的日历数组。要搜索所有可用的日历,我们传递 EKEventStore.Calendars 属性。

最后,我们调用 EnumerateEvents 方法:

evStore.EnumerateEvents(evPredicate, delegate(EKEvent calEvent, ref bool stop) {
//...

我们将之前创建的谓词传递给第一个参数。第二个参数是类型为 EKEventSearchCallback 的委托。为了读取每个事件的数据,我们使用其 EKEvent 对象。请注意,枚举日历事件的过程与在上一章中讨论的从资产库枚举资产的过程类似。

更多信息...

除了枚举事件外,EKEventStore 允许我们创建新的事件。以下示例创建并保存了一个新的日历事件:

EKEvent newEvent = EKEvent.FromStore(evStore);
newEvent.StartDate = DateTime.Now.AddDays(1);
newEvent.EndDate = DateTime.Now.AddDays(1.1);
newEvent.Title = "MonoTouch event!";
newEvent.Calendar = evStore.DefaultCalendarForNewEvents;
NSError error = null;
evStore.SaveEvent(newEvent, EKSpan.ThisEvent, out error);

为了创建一个新的 EKEvent 实例,我们使用 EKEvent.FromStore 静态方法。然后我们设置开始和结束日期、标题以及事件将要存储的日历。在这里,我们使用 EKEventStore.DefaultCalendarForNewEvents 属性可以获取的默认日历。当一切设置完毕后,我们调用 SaveEvent 方法来保存它。

关于日历的信息

默认情况下,设备已设置两个日历:HomeWork。尽管我们无法在设备上创建新日历,但我们在用于同步设备的计算机上创建的新日历在同步时将自动添加。

相关内容

在本书中:

第七章, 多媒体资源:

  • 直接管理专辑项目

第九章.与设备硬件交互

在本章中,我们将涵盖:

  • 检测设备方向

  • 调整 UI 方向

  • 接近传感器

  • 获取电池信息

  • 处理运动事件

  • 处理触摸事件

  • 识别手势

  • 自定义手势

  • 使用加速度计

  • 使用陀螺仪

简介

今天的移动设备配备了非常先进的硬件。无论是加速度计来检测运动和方向,接近传感器,GPS 模块,以及许多其他组件,多触控屏幕相当复杂。

在本章中,我们将重点关注如何在我们的应用程序中使用这些硬件,为用户提供一个扩展到 3D 世界的体验。具体来说,我们将讨论如何根据设备的位置调整用户界面方向,如何使用接近传感器,以及读取电池信息。在一系列四个任务中,我们将学习如何捕获屏幕上的用户触摸并识别手势。

最后但同样重要的是,我们将创建高级应用程序,从加速度计和陀螺仪传感器读取原始数据以检测设备运动和旋转,并提供详细且简单的指南。

检测设备方向

在这个菜谱中,我们将学习如何制作一个能够感知设备方向变化的应用程序。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为DeviceOrientationApp

如何做到这一点...

  1. MainController的视图中添加一个标签。在MainController类中输入以下代码:

    private NSObject orientationObserver;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    UIDevice.CurrentDevice. BeginGeneratingDeviceOrientationNotifications();
    this.orientationObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIDevice.OrientationDidChangeNotification, delegate {
    this.lblOrientation.Text = UIDevice.CurrentDevice.Orientation.ToString();
    } );
    }
    public override void ViewDidUnload (){
    base.ViewDidUnload ();
    NSNotificationCenter.DefaultCenter. RemoveObserver(this.orientationObserver);
    UIDevice.CurrentDevice. EndGeneratingDeviceOrientationNotifications();
    }
    
    
  2. 在模拟器上编译并运行应用程序。

  3. 通过在 Mac 上按住Command键并按左或右箭头键来旋转模拟器。

如何做到这一点...

它是如何工作的...

虽然模拟器缺少加速度计硬件,但它支持方向变化的通知。

可以通过UIDevice.CurrentDevice单例对象访问设备方向通知机制。为了接收通知,我们首先需要指示运行时发出它们。我们使用以下方法来完成此操作:

UIDevice.CurrentDevice. BeginGeneratingDeviceOrientationNotifications();

此方法打开加速度计并开始生成方向通知。然后我们需要开始观察通知,以便响应变化:

this.orientationObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIDevice.OrientationDidChangeNotification, delegate {
this.lblOrientation.Text = UIDevice.CurrentDevice.Orientation.ToString();
} );

每当设备方向改变时,观察者触发匿名方法。在其中,我们将从Orientation属性获取的方向输出到标签。

ViewDidUnload方法是在视图控制器卸载其视图时被调用的方法。在其中,我们确保移除方向观察者,并指示运行时停止生成方向通知:

NSNotificationCenter.DefaultCenter. RemoveObserver(this.orientationObserver);
UIDevice.CurrentDevice.EndGeneratingDeviceOrientationNotifications();

还有更多...

Orientation属性返回类型为UIDeviceOrientation的枚举。其值如下:

  • Unknown: 此值指定设备方向未知

  • Portrait: 此值指定设备处于正常的纵向方向,主页按钮在底部

  • PortraitUpsideDown: 这个值指定设备处于颠倒的纵向方向,主页按钮在顶部

  • LandscapeLeft: 这个值指定设备处于横向方向,主页按钮在左侧

  • LandscapeRight: 这个值指定设备处于横向方向,主页按钮在右侧

  • FaceUp: 这个值指定设备与地面平行,屏幕向上

  • FaceDown: 这个值指定设备与地面平行,屏幕向下

FaceUpFaceDown是模拟器上无法复制的两个值。

设备方向和用户界面方向

在这个例子中可以明显看出,设备方向和用户界面方向之间存在差异。如果设备旋转,标签会更新为新方向值,但用户界面不会对变化做出响应。在下一个菜谱中,我们将讨论如何旋转用户界面。

参见

在本章中:

  • 调整 UI 方向

  • 使用加速度计

调整 UI 方向

在这个菜谱中,我们将学习如何根据屏幕方向旋转用户界面 (UI)

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为UIOrientationApp

如何操作...

  1. MainController视图上添加一个标签。在MainController类中输入以下代码:

    public override bool ShouldAutorotateToInterfaceOrientation ( UIInterfaceOrientation toInterfaceOrientation){
    return true;
    }
    public override void DidRotate ( UIInterfaceOrientation fromInterfaceOrientation){
    base.DidRotate (fromInterfaceOrientation);
    this.lblOutput.Text = this.InterfaceOrientation.ToString();
    }
    
    
  2. 在模拟器上编译并运行应用程序。

  3. 使用Command + 方向键旋转模拟器。以下图像显示了LandscapeRight方向:

如何操作...

它是如何工作的...

要使我们的 UI 适应设备方向,我们只需要重写视图控制器的ShouldAutorotateToInterfaceOrientation方法:

public override bool ShouldAutorotateToInterfaceOrientation ( UIInterfaceOrientation toInterfaceOrientation)

当视图控制器加载时,它会检查每个可用方向的方法结果。第一次接收到true时,它会自动将界面旋转到该方向。加载后,每次设备旋转时,都会重复相同的流程。

方法参数是类型为UIInterfaceOrientation的枚举,每次方法被调用时,它包含界面检查的方向值。

在界面方向完成旋转后,会调用DidRotate方法。我们使用UIViewController.InterfaceOrientation属性,它包含视图控制器当前方向的信息,来更新标签:

this.lblOutput.Text = this.InterfaceOrientation.ToString();

更多内容...

ShouldAutorotateToInterfaceOrientation方法返回true意味着界面将在所有设备方向上旋转。在大多数情况下,这并不是必要的,甚至应该根据我们的应用程序设计避免。要使我们的界面仅旋转到横向方向,方法应实现如下:

public override bool ShouldAutorotateToInterfaceOrientation ( UIInterfaceOrientation toInterfaceOrientation){
return toInterfaceOrientation == UIInterfaceOrientation.LandscapeLeft || toInterfaceOrientation == UIInterfaceOrientation.LandscapeRight;
}

注意,此实现将强制 UI 以横屏模式加载。

模拟器中的用户界面方向

如果你只实现ShouldAutorotateToInterfaceOrientation方法以支持横屏方向,那么加载模拟器“设备”的控制器也将以横屏方向旋转。然而,这只是为了方便,因为如果你检查UIDevice.CurrentDevice.Orientation属性,其值将是UIDeviceOrientation.Portrait

参见

在本章中:

  • 检测设备方向

  • 使用加速度计

接近传感器

在这个菜谱中,我们将讨论使用接近传感器来禁用设备屏幕。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为ProximitySensorApp

注意

模拟器不支持接近传感器。

如何做到这一点...

对于这个任务,除了MainController本身之外,不需要任何控件。

  1. 声明一个NSObject字段,它将包含通知观察者:

    private NSObject proximityObserver;
    
    
  2. ViewDidLoad重写方法中输入以下代码:

    UIDevice.CurrentDevice.ProximityMonitoringEnabled = true;
    if (UIDevice.CurrentDevice.ProximityMonitoringEnabled){
    this.proximityObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIDevice.ProximityStateDidChangeNotification, delegate(NSNotification ntf) {
    Console.WriteLine("Proximity state: {0}", UIDevice.CurrentDevice.ProximityState);
    } );
    }
    
    
  3. 最后,在ViewDidUnload重写方法中输入以下代码:

    if (UIDevice.CurrentDevice.ProximityMonitoringEnabled){
    NSNotificationCenter.DefaultCenter. RemoveObserver(this.proximityObserver);
    UIDevice.CurrentDevice.ProximityMonitoringEnabled = false;
    }
    
    
  4. 在设备上编译并运行应用程序。

  5. 将手指放在接近传感器上,或者就像在通话时那样,将其靠近你的耳朵。观察 MonoDevelop 中的应用程序输出面板显示传感器的状态。

它是如何工作的...

尽管接近传感器的功能相当简单,但它提供了一个非常重要的特性。iOS 设备正面只有一个按钮,那就是按钮。几乎每一次用户与设备的交互都是基于触摸屏的。这在 iPhone 上造成了一个问题:除了它的多种功能外,它也是一个电话。这意味着它很可能会在用户的面部侧面花费一些时间来进行通话。

为了避免意外触碰到虚拟按钮,当手机应用程序运行时,接近传感器会被激活,以在设备靠近用户的耳朵或传感器上方的任何物体时禁用屏幕。

要启用接近传感器,将UIDevice.CurrentDevice.ProximityMonitoringEnabled属性的值设置为true

UIDevice.CurrentDevice.ProximityMonitoringEnabled = true;

如果设备不支持接近传感器,即使将其设置为true,此属性也将返回false。因此,在将其设置为true之后,我们可以检查它以查看设备是否支持传感器:

if (UIDevice.CurrentDevice.ProximityMonitoringEnabled)

在检查后,我们可以使用UIDevice.ProximityStateDidChangeNotification键添加一个观察者来通知传感器的状态:

this.proximityObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIDevice.ProximityStateDidChangeNotification, delegate(NSNotification ntf) {
Console.WriteLine("Proximity state: {0}", UIDevice.CurrentDevice.ProximityState);
} );

ProximityState属性返回true表示传感器已经关闭了屏幕,如果它重新打开屏幕则返回false

还有更多...

接近传感器的使用不仅限于电话功能。例如,如果你正在开发一个在设备放在用户的口袋或钱包中时可以执行一些工作的应用程序,启用接近传感器可以确保不会意外触碰到控制按钮。甚至可以通过仅关闭屏幕来节省电池电量。

传感器支持

并非所有设备都支持接近传感器。如果你针对各种 iOS 设备,请考虑传感器可能不会在所有设备上可用。

相关内容

在本章中:

  • 检索电池信息

检索电池信息

在这个菜谱中,我们将学习如何读取设备的充电状态及其电池使用情况。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 BatteryInfoApp

如何做到...

  1. MainController 的视图中添加一个标签。在 MainController 类中输入以下代码:

    private NSObject batteryStateChangeObserver;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    UIDevice.CurrentDevice.BatteryMonitoringEnabled = true;
    this.batteryStateChangeObserver = NSNotificationCenter. DefaultCenter.AddObserver(UIDevice. BatteryStateDidChangeNotification, delegate (NSNotification ntf) {
    this.lblOutput.Text = string.Format("Battery state: {0}", UIDevice.CurrentDevice.BatteryState);
    } );
    }
    
    
  2. 在设备上编译并运行应用程序。

  3. 应用程序加载后,断开并/或连接设备的 USB 线缆。观察标签上的电池状态。

它是如何工作的...

我们可以通过 UIDevice 类检索电池信息。我们必须做的第一件事是启用电池监控:

UIDevice.CurrentDevice.BatteryMonitoringEnabled = true;

在不支持电池监控的模拟器上,即使我们将其设置为 true,此属性也将返回 false。

我们可以通过 UIDevice.BatteryStateDidChangeNotification 键添加一个观察者来监听电池状态变化通知,如之前高亮显示的代码所示。可以通过 BatteryState 属性检索电池状态:

this.lblOutput.Text = string.Format("Battery state: {0}", UIDevice.CurrentDevice.BatteryState);

BatteryState 属性的可能值包括:

  • Unknown: 此值指定无法确定电池状态,或者电池监控被禁用

  • Unplugged: 此值指定设备正在使用电池供电

  • Charging: 此值指定设备电池正在充电,并且 USB 线缆已连接

  • Full: 此值指定设备电池已满电,并且 USB 线缆已连接

更多内容...

除了电池状态外,我们还可以获取其电量信息。为此,我们需要为 UIDevice.BatteryLevelDidChangeNotification 键添加一个观察者:

private NSObject batterLevelChangeObserver;
//...
this.batterLevelChangeObserver = NSNotificationCenter.DefaultCenter .AddObserver(UIDevice.BatteryLevelDidChangeNotification, delegate(NSNotification ntf) {
this.lblOutput.Text = string.Format("Battery level: {0}", UIDevice.CurrentDevice.BatteryLevel);
} );

BatteryLevel 属性返回一个范围从 0.0(电池耗尽)到 1.0(电池满电 100%)的浮点值。如果电池监控被禁用,它将返回 -1.0 的值。

禁用电池监控

在不需要时始终禁用电池监控。实际的监控机制本身会消耗电池电量。

相关内容

在本章中:

  • 接近传感器

处理运动事件

在这个菜谱中,我们将学习如何拦截和响应摇动手势。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 MotionEventsApp

如何做到...

  1. MainController 的视图中添加一个标签。在 MainController 类中输入以下代码:

    public override bool CanBecomeFirstResponder{
    get { return true; }
    }
    public override void ViewDidAppear (bool animated){
    base.ViewDidAppear (animated);
    this.BecomeFirstResponder();
    }
    public override void MotionBegan (UIEventSubtype motion, UIEvent evt){
    base.MotionBegan (motion, evt);
    this.lblOutput.Text = "Motion started!";
    }
    public override void MotionEnded (UIEventSubtype motion, UIEvent evt){
    base.MotionEnded (motion, evt);
    this.lblOutput.Text = "Motion ended!";
    }
    public override void MotionCancelled (UIEventSubtype motion, UIEvent evt){
    base.MotionCancelled (motion, evt);
    this.lblOutput.Text = "Motion cancelled!";
    }
    
    
  2. 在设备上编译并运行应用程序。

  3. 摇动设备并观察标签上的输出。你还可以在模拟器上测试这个应用程序。

  4. 加载完成后,点击菜单栏上的 硬件 | 摇动手势

它是如何工作的...

通过重写UIViewController类的运动方法,我们可以拦截并响应用户系统发送的运动事件。尽管如此,仅仅重写这些方法是不够的。为了使控制器接收运动事件,它需要成为第一个响应者。为了确保这一点,我们首先重写CanBecomeFirstResponder属性并从中返回true

public override bool CanBecomeFirstResponder{
get { return true; }
}

然后,我们通过在ViewDidAppear重写方法中调用BecomeFirstResponder方法来确保当其视图出现时,我们的控制器成为第一个响应者:

public override void ViewDidAppear (bool animated){
base.ViewDidAppear (animated);
this.BecomeFirstResponder();
}

ViewDidAppear方法在视图出现在屏幕上之后被调用。

系统确定一个运动是否是摇动手势,并调用适当的方法。我们可以使用以下三个方法来重写并捕获摇动手势:

  • MotionBegan: 此方法指定摇动动作开始

  • MotionEnded: 此方法指定摇动动作结束

  • MotionCancelled: 此方法指定摇动动作取消

当设备开始移动时,会调用MotionBegan方法。如果运动持续大约一秒或更短,则调用MotionEnded方法。如果持续时间更长,系统将其分类为非摇动手势,并调用MotionCancelled方法。当我们在应用程序中实现摇动手势时,建议重写所有三个方法并相应地做出反应。

更多...

只有从继承自UIResponder类的对象才会发送运动事件。这包括UIViewUIViewController类。

更多信息:运动事件

运动事件机制相当简单。它仅检测几乎瞬间的设备摇动,而不提供有关其方向或速率的任何信息。为了根据不同的特性处理运动事件,可以将加速度计与组合使用。

相关内容

在本章中:

  • 使用加速度计

处理触摸事件

在本食谱中,我们将学习如何拦截和响应用户触摸。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为TouchEventsApp

如何实现...

  1. MainController的视图中添加一个标签,并在MainController类中输入以下代码:

    public override void TouchesMoved (NSSet touches, UIEvent evt){
    base.TouchesMoved (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    UIColor currentColor = this.View.BackgroundColor;
    float red, green, blue, alpha;
    currentColor.GetRGBA(out red, out green, out blue, out alpha);
    PointF previousLocation = touch.PreviousLocationInView(this.View);
    PointF touchLocation = touch.LocationInView(this.View);
    if (previousLocation.X != touchLocation.X){
    this.lblOutput.Text = "Changing background color...";
    float colorValue = touchLocation.X / this.View.Bounds.Width;
    this.View.BackgroundColor = UIColor.FromRGB(colorValue, colorValue, colorValue);
    }
    }
    
    
  2. 在模拟器上编译并运行应用程序。

  3. 在模拟器屏幕上使用光标进行侧向点击和拖动,并观察视图的背景颜色逐渐从白色变为黑色。请注意,在模拟器屏幕上使用光标点击相当于用手指触摸设备的屏幕。

它是如何工作的...

为了响应用户的触摸,作为触摸接收器的对象必须将其 UserInteractionEnabled 属性设置为 true。几乎每个对象默认都启用了用户交互,除非其主要用途不是直接用于用户交互,例如,UILabelUIImageView。我们需要明确地将 UserInteractionEnabled 设置到这些对象上。除此之外,可以处理触摸事件的对象必须继承自 UIResponder 类。请注意,尽管 UIViewController 类继承自 UIResponder,因此可以捕获触摸事件,但它没有 UserInteractionEnabled 属性,而是其主要的 UIView 属性控制着触摸事件的传递。这意味着,如果你重写了 UIViewController 的触摸方法,但它的视图的 UserInteractionEnabled 属性设置为 false,则这些方法将不会响应用户的触摸。

负责处理触摸事件的以下方法:

  • TouchesBegan: 当用户触摸屏幕时,会调用此方法。

  • TouchesMoved: 当用户在屏幕上拖动手指时,会调用此方法。

  • TouchesEnded: 当用户从屏幕上抬起手指时,会调用此方法。

  • TouchesCancelled: 当触摸事件被系统事件取消时,会调用此方法,例如,当显示通知警报时。

整个项目可以在下载的源代码中找到。TouchesMoved 方法的实现在此处解释。

每个触摸方法都有两个参数。第一个参数是 NSSet 类型,包含 UITouch 对象。NSSet 类表示对象的集合,而 UITouch 类保存每个用户触摸的信息。第二个参数是 UIEvent 类型,包含实际事件的详细信息。

我们可以通过 NSSet.AnyObject 返回值检索与实际触摸相关的 UITouch 对象:

UITouch touch = touches.AnyObject as UITouch;

它返回一个 NSObject 类型的对象,我们将它转换为 UITouch。我们可以通过以下方法获取触摸的先前和当前位置:

PointF previousLocation = touch.PreviousLocationInView(this.View);
PointF touchLocation = touch.LocationInView(this.View);

它们都返回一个包含触摸接收器坐标系中触摸位置的 PointF 结构体。在接收到触摸位置后,我们相应地调整背景颜色。

更多...

此示例基于单个用户的触摸。为了使视图能够响应用户的多个触摸,我们必须将其 MultipleTouchEnabled 属性设置为 true。然后我们可以获取数组中的所有 UITouch 对象:

UITouch[] allTouches = touches.ToArray<UITouch>();

获取点击次数

我们可以通过 ToucheEnded 方法中的 UITouch.TapCount 属性确定连续用户点击的次数。

参见

在本章中:

  • 触摸事件

  • 识别手势

  • 自定义手势

识别手势

在本食谱中,我们将讨论如何识别触摸手势并相应地做出反应。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 GestureApp

如何做到这一点...

  1. MainController的视图中添加一个标签。在MainController类的源文件中输入以下using指令:

    using MonoTouch.ObjCRuntime;
    
    
  2. MainController类中输入以下代码:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    UIPinchGestureRecognizer pinchGesture = new UIPinchGestureRecognizer(this, new Selector("PinchHandler:"));
    this.View.AddGestureRecognizer(pinchGesture);
    }
    [Export("PinchHandler:")]
    private void PinchHandler(UIGestureRecognizer gesture){
    UIPinchGestureRecognizer pinch = gesture as UIPinchGestureRecognizer;
    switch (pinch.State)
    {
    case UIGestureRecognizerState.Began:
    this.lblOutput.Text = "Pinch began!";
    break;
    case UIGestureRecognizerState.Changed:
    this.lblOutput.Text = "Pinch changed!";
    break;
    case UIGestureRecognizerState.Ended:
    this.lblOutput.Text = "Pinch ended!";
    break;
    }
    }
    
    
  3. 在模拟器上编译并运行应用程序。

  4. 按住Option键,并用鼠标点击拖动以在模拟器屏幕上执行捏合动作的等效操作。

它是如何工作的...

随着 iOS 3.2 版本的发布,iPad 一同推出,苹果引入了UIGestureRecognizer类及其派生类。手势识别器利用 iOS 设备上的多点触控屏幕。手势基本上是触摸组合,可以执行特定操作。

例如,在原生照片应用程序的全屏图像上捏合将缩小视图。捏合动作是用户执行的手势,而手势识别器负责识别并将手势事件传递给接收者。

在这个例子中,我们创建了一个UIPinchGestureRecognizer,它将识别屏幕上的捏合手势。其实例使用以下代码创建:

UIPinchGestureRecognizer pinchGesture = new UIPinchGestureRecognizer(this, new Selector("PinchHandler:"));

初始化实例的构造函数接受两个参数。第一个参数是NSObject类型,它是将接收手势的目标对象。在这种情况下,它是MainController实例,我们使用this关键字传递。第二个参数是MonoTouch.ObjCRuntime命名空间中的类型,它表示当识别器接收到手势时将被调用的方法。简单来说,Objective-C 中的Selector基本上是一个方法签名。传递给其构造函数的字符串代表将被调用的 Objective-C 方法。

由于我们使用 C#,我们可以轻松地将一个方法暴露为 Objective-C 的Selector。我们只需创建我们想要的方法,并用ExportAttribute装饰它,确保传递给它的字符串与传递给Selector构造函数的字符串相同:

[Export("PinchHandler:")]
private void PinchHandler(UIGestureRecognizer gesture)

在方法内部,我们读取手势识别器对象的State属性,并相应地做出反应。

更多内容...

每个手势识别器的状态由类型为UIGestureRecognizerState的枚举表示。其值包括:

  • Possible: 此值指定手势尚未被识别,并且是默认值

  • Began: 此值指定手势已开始

  • Changed: 此值指定手势已更改

  • Ended: 此值指定手势已结束

  • Cancelled: 此值指定手势已被取消

  • Failed: 此值指定手势无法被识别

  • Recognized: 此值指定手势已被识别

手势识别器的优势

手势识别器的优点是它们可以节省开发者创建自己的手势识别机制的时间,通过触摸事件。此外,它们基于用户在 iOS 设备上习惯使用的手势。

相关内容

在本章中:

  • 触摸事件

  • 自定义手势

自定义手势

在这个食谱中,我们将学习如何创建一个自定义手势识别器来创建我们自己的手势。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 CustomGestureApp

如何操作...

  1. MainController 视图中添加一个标签。在 MainController 类中创建以下嵌套类:

    private class DragLowerLeftGesture : UIGestureRecognizer{
    public DragLowerLeftGesture(NSObject target, Selector action) : base(target, action){}
    private PointF startLocation;
    private RectangleF lowerLeftCornerRect;
    public override UIGestureRecognizerState State{
    get{
    return base.State;
    } set{
    base.State = value;
    }
    }
    public override void TouchesBegan (NSSet touches, UIEvent evt){
    base.TouchesBegan (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    this.startLocation = touch.LocationInView(this.View);
    RectangleF viewBounds = this.View.Bounds;
    this.lowerLeftCornerRect = new RectangleF(0f, viewBounds.Height - 50f, 50f, 50f);
    if (this.lowerLeftCornerRect.Contains(this.startLocation)){
    this.State = UIGestureRecognizerState.Failed;
    } else{
    this.State = UIGestureRecognizerState.Began;
    }
    }
    public override void TouchesMoved (NSSet touches, UIEvent evt){
    base.TouchesMoved (touches, evt);
    this.State = UIGestureRecognizerState.Changed;
    }
    public override void TouchesEnded (NSSet touches, UIEvent evt){
    base.TouchesEnded (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    PointF touchLocation = touch.LocationInView(this.View);
    if (this.lowerLeftCornerRect.Contains(touchLocation)){
    this.State = UIGestureRecognizerState.Ended;
    } else{
    this.State = UIGestureRecognizerState.Failed;
    }
    }
    }
    
    
  2. 使用前面食谱中展示的自定义手势识别器。

工作原理...

要创建一个手势识别器,声明一个继承自 UIGestureRecognizer 类的类。在这个例子中,我们创建一个手势,通过在屏幕上拖动手指指向左下角的一个 50x50 点矩形来识别这个手势。

private class DragLowerLeftGesture : UIGestureRecognizer

UIGestureRecognizer 类包含我们在视图中拦截触摸事件时使用的相同触摸方法。我们还可以通过其 View 属性访问它所添加的视图。在 TouchesBegan 方法中,我们确定初始触摸位置。如果它在视图的左下部分之外,我们将 State 属性设置为 Began。如果它在左下部分内部,我们将 State 属性设置为 Failed,这样就不会调用选择器。

TouchesEnded 方法中,如果触摸的位置在视图的左下部分内部,我们考虑手势为 Ended。如果没有,则手势识别被认为是 Failed

TouchesMoved 方法是设置 Changed 状态的地方。对于这个简单的手势识别器,我们不需要在其中添加其他逻辑。

更多内容...

这是一个简单的手势识别器,它依赖于单个触摸。通过触摸方法提供的信息,我们可以创建更复杂的支持多个触摸的手势。

自定义手势识别器的另一种用法

有些视图继承自 UIView 类,根据苹果开发者文档,这些视图不应该被子类化。MKMapView 就是这些视图之一,用于显示地图。如果我们想拦截这些视图的触摸事件,这会带来一个问题。虽然我们可以在其上方使用另一个视图并拦截其触摸事件,但这有点复杂。一个更简单的方法是创建一个简单的自定义手势识别器并将其添加到我们无法子类化的视图中。这样,我们就可以在不子类化的情况下拦截其触摸事件。

相关内容

在本章中:

  • 识别手势

  • 触摸事件

使用加速度计

在这个食谱中,我们将学习如何接收加速度计事件来创建一个能够感知设备运动的应用程序。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 AccelerometerApp

注意

模拟器不支持加速度计硬件。本例中的项目将在设备上正确运行。

如何做...

  1. MainController的视图中添加两个按钮和一个标签。

  2. 覆盖ViewDidLoad方法,并使用以下代码实现它:

    this.buttonStop.Enabled = false;
    UIAccelerometer.SharedAccelerometer.UpdateInterval = 1 / 10;
    this.buttonStart.TouchUpInside += delegate {
    this.buttonStart.Enabled = false;
    UIAccelerometer.SharedAccelerometer.Acceleration += this.Acceleration_Received;
    this.buttonStop.Enabled = true;
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.buttonStop.Enabled = false;
    UIAccelerometer.SharedAccelerometer.Acceleration -= this.Acceleration_Received;
    this.buttonStart.Enabled = true;
    } ;
    
    
  3. 在类中添加以下方法:

    private void Acceleration_Received (object sender, UIAccelerometerEventArgs e){
    this.lblOutput.Text = string.Format("X: {0}\nY: {1}\nZ: {2}", e.Acceleration.X, e.Acceleration.Y, e.Acceleration.Z);
    }
    
    
  4. 在设备上编译并运行应用程序。

  5. 点击开始加速度计按钮,在移动或摇晃设备时,观察标签上显示的值。

如何工作...

UIAccelerometer类通过其SharedAccelerometer静态属性提供对加速度计硬件的访问。要激活它,我们只需要将其Acceleration事件分配给一个处理程序:

UIAccelerometer.SharedAccelerometer.Acceleration += this.Acceleration_Received;

在处理程序内部,我们通过UIAccelerometerEventArgs.Acceleration属性接收加速度计的值。该属性返回一个UIAcceleration类型的对象,它包含三个属性:XYZ,分别表示加速度计在三个轴上的量。

这些属性表示在XYZ轴上的运动。考虑以下图示:

如何工作...

这些值中的每一个都测量设备在每个轴上移动的重力量。例如,如果X的值为1,则设备在X 轴上向右移动,加速度为1g。如果X的值为-1,则设备在X 轴上向左移动,加速度为1g。当设备放在桌子上,背面朝向地面且不移动时,加速度的正常值应接近或等于以下值:

  • X: 0

  • Y: 0

  • Z: -1

尽管设备没有移动,Z的值将是-1,因为设备测量地球的重力。

我们可以通过设置其UpdateInterval属性来设置加速度计发布加速度事件的时间间隔:

UIAccelerometer.SharedAccelerometer.UpdateInterval = 1 / 10;

它接受一个double,表示加速度计在秒内发布加速度事件的时间间隔。设置更新间隔时必须小心,因为加速度计在特定时间段内需要发布的事件越多,它消耗的电量就越多。

要停止使用加速度计,我们只需要将其处理程序从Acceleration事件中取消绑定:

UIAccelerometer.SharedAccelerometer.Acceleration -= this.Acceleration_Received;

更多内容...

UIAcceleration类还包含另一个有用的属性,名为Time。它是一个表示加速度事件发生相对时间的double值。它是相对于 CPU 时间的,不建议使用此值来计算事件的精确时间戳。

使用加速度计的注意事项

尽管 iPhone 的加速度计是一个非常精确和敏感的传感器,但它不应该用于精确测量。此外,即使是同一型号的设备,不同 iOS 设备产生的结果也可能会有所不同。

相关内容

在本章中:

  • 使用陀螺仪

使用陀螺仪

在本食谱中,我们将学习如何使用内置的陀螺仪。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为GyroscopeApp

注意

模拟器不支持陀螺仪硬件。此外,只有较新的设备才包含陀螺仪。如果在这个没有陀螺仪的设备或模拟器上运行此应用程序,不会发生错误,但不会显示任何数据。

如何操作...

  1. MainController的视图中添加两个按钮和一个标签。在MainController.cs文件中添加命名空间MonoTouch.CoreMotion。在类中输入以下私有字段:

    private CMMotionManager motionManager;
    
    
  2. 重写ViewDidLoad方法,并使用以下代码实现它:

    this.motionManager = new CMMotionManager();
    this.motionManager.GyroUpdateInterval = 1 / 10;
    this.buttonStart.TouchUpInside += delegate {
    this.motionManager.StartGyroUpdates(NSOperationQueue.MainQueue, this.GyroData_Received);
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.motionManager.StopGyroUpdates();
    } ;
    
    
  3. 添加以下方法:

    private void GyroData_Received(CMGyroData gyroData, NSError error){
    Console.WriteLine("rotation rate x: {0}, y: {1}, z: {2}", gyroData.RotationRate.x, gyroData.RotationRate.y, gyroData.RotationRate.z);
    }
    
    
  4. 在设备上编译并运行应用程序。

  5. 点击开始陀螺仪按钮,并在所有轴上旋转设备。观察应用程序输出中显示的值。

它是如何工作的...

陀螺仪是一种测量方向的机制。较新的 iOS 设备支持陀螺仪硬件,以及加速度计,以提供更精确的设备运动测量。

MonoTouch.CoreMotion命名空间封装了原生CoreMotion 框架中包含的对象。在代码中使用陀螺仪硬件的过程与加速度计类似。第一个区别是UIApplication类中没有陀螺仪的单例对象。因此,我们需要创建CMMotionManager类的实例:

private CMMotionManager motionManager;
//...
this.motionManager = new CMMotionManager();

就像使用加速度计一样,我们可以设置接收陀螺仪事件的间隔,单位为秒:

this.motionManager.GyroUpdateInterval = 1 / 10;

要开始接收陀螺仪事件,我们调用对象的StartGyroUpdates方法:

this.motionManager.StartGyroUpdates(NSOperationQueue.MainQueue, this.GyroData_Received);

此方法被重载;第一个重载是无参数的,当调用时,将陀螺仪测量的值设置为GyroData属性。使用此重载非常简单且容易,但没有触发任何事件,我们必须提供一个机制从属性中读取测量值。

第二个重载,在此示例中使用,接受两个参数。第一个参数是NSOperationQueue,更新将在其上发生,第二个参数是当发生更新时将被执行的处理器。

NSOperationQueue类表示 iOS 管理NSOperation对象执行的一种机制。我们通过静态属性NSOperationQueue.MainQueue访问运行时的主操作队列。基本上,这种方式,我们指示运行时以更有效的方式管理处理器的传递。

第二个参数是CMGyroHandler类型的委托。其签名,由我们创建的方法表示,如下所示:

private void GyroData_Received(CMGyroData gyroData, NSError error)

CMGyroData对象包含通过其RotationRate属性从陀螺仪接收的实际测量值:

Console.WriteLine("rotation rate x: {0}, y: {1}, z: {2}", gyroData.RotationRate.x, gyroData.RotationRate.y, gyroData.RotationRate.z);

旋转速率反映在X, YZ轴上,分别由相应的X, YZ属性表示。每个值是该轴上发生的每秒旋转角度的数量,以弧度为单位。

虽然一开始可能看起来有点复杂,但实际上很简单。例如,Z 轴上的0.5值表示设备以0.5弧度/秒的速率向左旋转。Z 轴上的-0.5值表示设备以0.5弧度/秒的速率向右旋转。确定旋转方向的模式基于右手定则

更多内容...

如果您希望您的应用程序仅适用于支持陀螺仪的设备,那么请在项目的Info.plist文件中添加键UIRequiredDeviceCapabilities,其值为gyroscope。如果您的应用程序的功能完全基于陀螺仪,添加此键必须被视为必要,以避免应用程序被使用较旧设备的用户下载,最终导致应用程序无法使用。

确定陀螺仪可用性

要确定应用程序运行在的设备是否支持陀螺仪硬件,请检查CMMotionManager实例的GyroAvailable属性的值。

将弧度转换为度

弧度是角度的测量单位。要将角度测量值从弧度转换为度,请考虑以下方法:

public static double RadiansToDegrees (double radians){
return (radians * 180 / Math.PI);
}

参见

在本章中:

  • 使用加速度计

第十章。位置服务和地图

在这一章中,我们将涵盖:

  • 确定位置

  • 确定航向

  • 使用区域监控

  • 使用显著变化位置服务

  • 背景中的位置服务

  • 显示地图

  • 地理编码

  • 添加地图标注

  • 添加地图叠加

简介

今天的手持设备和智能手机都配备了高精度的全球定位系统(GPS)硬件。GPS 硬件从一组卫星接收位置信息。除了卫星之外,iOS 设备还利用蜂窝和 Wi-fi 网络向用户提供位置信息。

在这一章中,我们将讨论如何使用适当的框架来利用设备的位置服务。此外,我们还将学习如何显示地图并对其进行标注。具体来说,我们将关注以下主题:

  • 位置服务:它们提供设备上提供位置信息的可用服务。这些服务包括:

    • 标准位置服务:这是完全依赖于设备 GPS 模块的位置服务,提供最高精度的位置数据

    • 区域监控服务:这是监控边界穿越的位置服务

    • 显著变化位置服务:这是监控位置显著变化的服务

  • CLLocationManager: 这是一个允许我们使用位置服务的类

  • Compass: 这是一个显示我们如何使用内置指南针的类

  • MKMapView: 这是一个用于显示地图的视图

  • MKAnnotation: 这是一个允许我们在地图上添加标注的类

  • MKOverlay: 这是一个允许我们在地图上添加叠加的类

确定位置

在这个菜谱中,我们将讨论如何从内置的 GPS 硬件接收位置信息。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为LocationApp。在MainController的视图中添加两个按钮和一个标签。

如何做到...

要从内置的 GPS 硬件中检索位置数据,我们需要使用CoreLocaction框架。它通过MonoTouch.CoreLocation命名空间暴露:

using MonoTouch.CoreLocation;

  1. MainController类中添加以下代码:

    private CLLocationManager locationManager;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.locationManager = new CLLocationManager();
    this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
    this.locationManager.Failed += this.LocationManager_Failed;
    this.buttonStart.TouchUpInside += delegate {
    this.lblOutput.Text = "Determining location...";
    this.locationManager.StartUpdatingLocation();
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.locationManager.StopUpdatingLocation();
    this.lblOutput.Text = "Location update stopped.";
    } ;
    }
    private void LocationManager_Failed (object sender, NSErrorEventArgs e){
    this.lblOutput.Text = string.Format("Location update failed! Error message: {0}", e.Error.LocalizedDescription);
    }
    private void LocationManager_UpdatedLocation (object sender, CLLocationUpdatedEventArgs e){
    double latitude = Math.Round(e.NewLocation.Coordinate. Latitude, 4);
    double longitude = Math.Round(e.NewLocation.Coordinate. Longitude, 4);
    double accuracy = Math.Round(e.NewLocation. HorizontalAccuracy, 0);
    this.lblOutput.Text = string.Format("Latitude: {0}\nLongitude: {1},\nAccuracy: {2}m", latitude, longitude, accuracy);
    }
    
    
  2. 在设备上编译并运行应用程序。

  3. 点击开始按钮,在屏幕上查看您的位置坐标。

注意

使用CoreLocation框架确定当前位置的项目可以在模拟器上运行。然而,坐标将是固定的,值为加利福尼亚州苹果公司的总部或运行模拟器的 Mac 电脑所在国家的中心坐标。

它是如何工作的...

GPS 模块提供的位置数据可以通过CLLocationManager类访问。初始化类的实例后,我们需要订阅其UpdatedLocation事件:

this.locationManager = new CLLocationManager();
this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;

当这些事件发布时,位置数据将变得可用。订阅Failed事件也是一个好的实践:

this.locationManager.Failed += this.LocationManager_Failed;

当位置管理器首次请求位置更新时,用户会通过系统特定的警报得到通知,类似于以下截图:

如何工作...

此警报基本上是请求用户允许应用程序检索位置数据。如果用户拒绝此请求,将触发带有适当信息的Failed事件。未来的位置请求将不会触发权限警报,用户必须通过设备设置启用应用程序的位置服务,因此我们需要相应地处理这种情况。

在订阅了适当的事件之后,我们通过StartUpdatingLocation方法请求发送位置更新:

this.locationManager.StartUpdatingLocation();

要停止接收位置更新,我们调用StopUpdatingLocation方法:

this.locationManager.StopUpdatingLocation();

还有更多...

UpdatedLocation事件接受类型为EventHandler<CLLocationUpdatedEventArgs>的委托。CLLocationUpdatedEventArgs参数包含两个类型为CLLocationCoordinate2D的属性。NewLocation属性包含最新的位置信息,而OldLocation属性包含之前的位置信息。在第一次位置更新时,OldLocation属性将返回null

坐标以double类型的值返回,并代表位置的坐标(以度为单位):

double latitude = Math.Round(e.NewLocation.Coordinate.Latitude, 4);
double longitude = Math.Round(e.NewLocation.Coordinate.Longitude, 4);
double accuracy = Math.Round(e.NewLocation.HorizontalAccuracy, 0);

负的latitude值表示南坐标,而正的值表示北坐标。负的longitude值表示西坐标,而正的longitude值表示东坐标。

HorizontalAccuracy属性返回 GPS 定位的精度(以米为单位)。例如,17m的值表示位置是在直径为 17 米的圆内确定的。较低的值表示更好的精度。

GPS 精度

UpdateLocation事件可能会在没有新的 GPS 读取的情况下被触发。这就是为什么我们提供了之前的位置,以便我们可以比较两个值以确定是否发生了位置变化。此外,位置数据总是存在一定的误差范围,这与 GPS 硬件无关,并且存在一些变量因素来定义它,例如周围的建筑物、各种障碍物等等。您会注意到,当设备在户外时,HorizontalAccuracy将返回较低的值,而当我们在室内使用 GPS 或在高楼林立的街道上时,将返回较高的值。

位置服务可用性

并非所有设备都配备了位置服务硬件。此外,即使设备配备了适当的硬件,用户也可能已禁用位置服务。

要确定设备上是否启用了位置服务,我们在初始化位置管理器对象之前读取CLLocationManager.LocationServicesEnabled静态属性的返回值:

if (CLLocationManager.LocationServicesEnabled) {
// Initialize the location manager
//...
}

位置服务使用指示器

当使用任何类型的定位服务时,定位服务图标将出现在状态栏的右侧,紧邻电池指示器:

位置服务使用指示器

此指示器仅在设备上显示,而不在模拟器上显示。

参见

在本章中:

  • 确定航向

  • 后台位置服务

确定航向

在本配方中,我们将学习如何使用内置指南针来确定航向。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为HeadingApp。就像在之前的任务中一样,在MainController的视图中添加两个按钮和一个标签。

注意

本任务中的项目无法在模拟器上测试。需要一个带有指南针硬件(磁力计)的设备。

如何操作...

  1. 航向信息再次通过CLLocationManager类检索。在MainController类中创建并初始化一个实例。

  2. ViewDidLoad方法中添加以下代码:

    this.locationManager = new CLLocationManager();
    this.locationManager.UpdatedHeading += this.LocationManager_UpdatedHeading;
    this.buttonStart.TouchUpInside += delegate {
    this.lblOutput.Text = "Starting updating heading...";
    this.locationManager.StartUpdatingHeading();
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.locationManager.StopUpdatingHeading();
    this.lblOutput.Text = "Stopped updating heading.";
    } ;
    
    
  3. 添加以下方法:

    private void LocationManager_UpdatedHeading (object sender, CLHeadingUpdatedEventArgs e){
    this.lblOutput.Text = string.Format("Magnetic heading: {0}", Math.Round(e.NewHeading.MagneticHeading, 1));
    }
    
    
  4. 在设备上编译并运行应用程序。

  5. 点击开始按钮并旋转设备以查看不同的航向值。

它是如何工作的...

要检索航向信息,我们首先需要订阅定位管理器的UpdatedHeading事件:

this.locationManager.UpdatedHeading += this.LocationManager_UpdatedHeading;

要启动航向信息的传输,我们调用StartUpdatingHeading方法:

this.locationManager.StartUpdatingHeading();

UpdatedHeading事件处理程序内部,我们通过事件参数的NewHeading属性,通过CLHeading对象的MagneticHeading属性检索航向信息:

this.lblOutput.Text = string.Format("Magnetic heading: {0}", Math.Round(e.NewHeading.MagneticHeading, 1));

要停止检索航向更新,我们调用StopUpdatingHeading方法:

this.locationManager.StopUpdatingHeading();

更多内容...

航向以度为单位测量。地平线四个方向的值,如简单指南针上所示,如下:

  • 0 360 度: 北;磁力计将返回最多359.99度的值,当设备朝北时,将回到0

  • 90 度:

  • 180 度:

  • 270 度: 西

磁航向与真航向

磁航向是基于普通指南针显示的北方。真航向是基于地球北极实际位置的北方方向。两者之间略有差异,随时间变化,通常约为2度。

CLHeading类通过MagneticHeadingTrueHeading属性提供两种读数。这对开发者来说非常有帮助,因为计算两者之间的差异可能需要昂贵的设备,或者基于年份和其他因素进行非常复杂的计算。

罗盘可用性

磁力计,一个可以确定航向(以度为单位)并为设备提供指南针功能的模块,并非所有设备都可用。要检查设备是否可以提供航向信息,请从CLLocationManager.HeadingAvailable静态属性中检索值:

if (CLLocationManager.HeadingAvailable) {
// Start updating heading
//...
}

参见

在本章中:

  • 确定位置

  • 后台位置服务

使用区域监控

在这个菜谱中,我们将讨论如何使用 GPS 来响应特定区域的位置变化。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 RegionApp。在 MainController 的视图中添加两个按钮和一个标签。

如何操作...

  1. MainController 类中创建两个字段:

    private CLLocationManager locationManager;
    private CLRegion region;
    
    
  2. ViewDidLoad 方法中,对其进行初始化,并订阅 UpdatedLocationRegionEnteredRegionLeft 事件:

    this.locationManager.RegionEntered += this.LocationManager_RegionEntered;
    this.locationManager.RegionLeft += this.LocationManager_RegionLeft;
    this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
    
    
  3. 在类中输入以下事件处理程序:

    private void LocationManager_UpdatedLocation (object sender, CLLocationUpdatedEventArgs e){
    if (e.NewLocation.HorizontalAccuracy < 100){
    this.region = new CLRegion(e.NewLocation.Coordinate, 100, "Home");
    this.locationManager.StartMonitoring(this.region, 65);
    this.locationManager.StopUpdatingLocation();
    }
    }
    private void LocationManager_RegionLeft (object sender, CLRegionEventArgs e){
    this.lblOutput.Text = string.Format("{0} region left.", e.Region.Identifier);
    }
    private void LocationManager_RegionEntered (object sender, CLRegionEventArgs e){
    this.lblOutput.Text = string.Format("{0} region entered.", e.Region.Identifier);
    }
    
    
  4. 在开始按钮的 TouchUpInside 处理程序中,调用 StartUpdatingLocation 方法:

    this.locationManager.StartUpdatingLocation();
    
    
  5. 在停止按钮的 TouchUpInside 处理程序中,调用 StopMonitoring 方法:

    this.locationManager.StopMonitoring(this.region);
    
    

此应用程序需要在支持区域监控的设备上进行测试。

它是如何工作的...

区域监控是一个监控边界穿越的功能。当特定区域的边界被穿越时,CLLocationManager 对象会触发适当的事件:

this.locationManager.RegionEntered += this.LocationManager_RegionEntered;
this.locationManager.RegionLeft += this.LocationManager_RegionLeft;
this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;

在这个例子中,我们根据当前位置定义区域;因此,我们也订阅了 UpdatedLocation 事件。

当应用程序开始接收位置更新时,它首先检查位置精度:

if (e.NewLocation.HorizontalAccuracy < 100)

如果达到了所需的精度(<100m,可自行修改),我们初始化 CLRegion 对象:

this.region = new CLRegion(e.NewLocation.Coordinate, 100, "Home");

CLRegion 类用于定义区域。在这里,我们根据我们的当前位置在第一个参数中创建要监控的区域。第二个参数声明了坐标周围的半径(以米为单位),定义了区域边界。第三个参数是区域的字符串标识符。

要开始监控区域,我们调用 StartMonitoring 方法:

this.locationManager.StartMonitoring(this.region, 65);

第一个参数是要监控的区域,第二个参数定义了边界穿越所需的精度(以米为单位)。此值作为精度偏移量,防止系统在用户接近区域边界时触发连续的 进入离开 事件。

当区域监控开始时,当设备进入或离开区域时,将根据所需的精度值触发适当的事件。

还有更多...

区域监控是一个非常实用的功能。例如,一个应用程序可以根据用户接近的不同区域提供特定的信息。此外,它可以在应用程序在后台时通知边界穿越。

区域监控可用性

要检查设备是否支持区域监控,检索 RegionMonitoringAvailable 静态属性的值:

if (CLLocationManager.RegionMonitoringAvailable) {
// Start monitoring a region
//...
}

参见

在本章中:

  • 使用显著变化的位置服务

  • 后台位置服务

使用显著变化的位置服务

在这个菜谱中,我们将学习如何使用显著变化的位置监控功能。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 SLCApp。在 MainController 的视图中添加一个标签和两个按钮。

如何做...

  1. ViewDidLoad方法中添加以下代码:

    this.locationManager = new CLLocationManager();
    this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
    this.buttonStart.TouchUpInside += delegate {
    this.lblOutput.Text = "Starting monitoring significant location changes...";
    this.locationManager. StartMonitoringSignificantLocationChanges();
    } ;
    this.buttonStop.TouchUpInside += delegate {
    this.locationManager.StopMonitoringSignificantLocationChanges();
    this.lblOutput.Text = "Stopped monitoring significant location changes.";
    } ;
    
    
  2. 添加以下方法:

    private void LocationManager_UpdatedLocation (object sender, CLLocationUpdatedEventArgs e){
    double latitude = Math.Round(e.NewLocation.Coordinate. Latitude, 4);
    double longitude = Math.Round(e.NewLocation.Coordinate. Longitude, 4);
    double accuracy = Math.Round(e.NewLocation. HorizontalAccuracy, 0);
    this.lblOutput.Text = string.Format("Latitude: {0}\nLongitude: {1}\nAccuracy: {2}", latitude, longitude, accuracy);
    }
    
    
  3. 在设备上编译并运行应用程序。

  4. 点击开始按钮以开始监控显著位置变化。

它是如何工作的...

显著变化位置服务监控显著的位置变化,并在这些变化发生时提供位置信息。在功耗方面,它是要求较低的位置服务。它使用设备的蜂窝无线电收发器来确定用户的位置。只有配备了蜂窝无线电收发器的设备才能使用此服务。

使用显著变化位置服务的代码与标准位置服务的代码类似。唯一的不同之处在于启动和停止服务的方法。要启动服务,我们调用StartMonitoringSignificantLocationChanges方法:

this.locationManager.StartMonitoringSignificantLocationChanges();

位置更新通过UpdatedLocation事件处理程序发布,这与我们用于标准位置服务的事件相同:

this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
//...
private void LocationManager_UpdatedLocation (object sender, CLLocationUpdatedEventArgs e){
//...
}

更多内容...

显著变化位置服务可以在后台报告位置变化,唤醒应用程序。

显著变化位置服务可用性

要确定设备是否能够使用显著变化位置服务,检索SignificantLocationChangeMonitoringAvailable静态属性的值:

if (CLLocationManager.SignificantLocationChangeMonitoringAvailable) {
// Start monitoring for significant location changes.
//...
}

参见

在本章中:

  • 使用区域监控

  • 后台位置服务

后台位置服务

在这个菜谱中,我们将讨论如何在应用处于后台时使用位置服务。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为BackgroundLocationApp。就像在之前的任务中一样,在MainController的视图中添加一个标签和两个按钮。

如何做...

  1. 解决方案面板中,双击Info.plist文件以打开它。

  2. 高级选项卡下,通过单击加号(+)或通过右键单击并从上下文菜单中选择新建键来添加一个新键。

  3. 从下拉列表中选择必需的后台模式,或者在字段中直接输入UIBackgroundModes

  4. 展开键,在其下方的空白项上右键单击。在上下文菜单中单击新建键。在其字段中输入单词location

  5. 保存文档。完成后,你应该会有以下截图所示的内容:如何做...

  6. MainController类中,输入与本章中确定位置菜谱中使用的相同代码。

  7. LocationManager_UpdatedLocation方法的底部添加以下行:

    Console.WriteLine("{0}:\n\t{1} ", DateTime.Now, this.lblOutput.Text);
    
    
  8. 在设备上编译并运行应用程序。

  9. 点击开始按钮以开始接收位置更新。按设备上的主页按钮使应用程序进入后台。观察 MonoDevelop 的应用程序输出面板显示位置更新。

它是如何工作的...

要在应用程序处于后台时接收位置更新,我们需要在 Info.plist 文件中将位置值设置为 UIBackgroundModes 键。这基本上确保了应用程序在后台时具有接收位置更新的适当权限,并且它不会挂起。

为了确保应用程序正在接收位置更新,请检查状态栏。位置服务图标应该显示:

如何工作...

更多内容...

为位置服务设置 UIBackgroundModes 键仅适用于标准位置服务。默认情况下,区域监控和重大变化位置服务都支持在应用程序处于后台时传输位置更新。当其中一个位置服务开始更新位置数据时,应用程序甚至可以被终止。当接收到位置更新时,应用程序将被启动或从挂起状态唤醒,并给予有限的时间来执行代码。

要确定应用程序是否由这两个位置服务之一启动,请检查 AppDelegate 类中 FinishedLaunching 方法的选项参数:

if (null != options){
if (options.ContainsKey (UIApplication.LaunchOptionsLocationKey)){
Console.WriteLine ("Woken from location service!");
CLLocationManager locationManager = new CLLocationManager();
locationManager.UpdatedLocation += this.LocationUpdatedHandler;
locationManager.StartMonitoringSignificantLocationChanges();
}
}

选项参数的类型为 NSDictionary。如果这个字典包含 LaunchOptionsLocationKey,则应用程序是由于位置服务而被启动或从挂起状态唤醒的。在这种情况下,我们需要在 CLLocationManager 实例上再次调用 StartMonitoringSignificantLocationChanges 方法来检索位置数据。

同样适用于区域监控位置服务。请注意,如果我们使用这两个位置服务之一,但我们的应用程序不支持位置事件的后台传输,那么我们必须确保在不再需要时停止监控位置更新。如果我们不这样做,那么位置服务将继续运行,导致电池消耗显著增加。

限制到支持的硬件

如果我们的应用程序的功能完全依赖于位置服务,并且不能在不支持它们的设备上正确运行,我们必须在 Info.plist 文件中添加键 UIRequiredDeviceCapabilities,其值为 location-services

此外,当应用程序需要使用使用 GPS 硬件的标准位置服务时,我们需要将值 gps 添加到这个键中。这样,我们确保应用程序不会通过应用商店提供给未配备适当硬件的设备。

参见

在本章中:

  • 确定位置

在本书中:

第一章,开发工具:

  • 在 MonoDevelop 中创建 iPhone 项目

显示地图

在本食谱中,我们将学习如何在屏幕上显示地图。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 MapDisplayApp

如何操作...

  1. MainController 的视图中添加一个 MKMapView。输入以下 using 指令:

    using MonoTouch.MapKit;
    using MonoTouch.CoreLocation;
    
    
  2. MainController 类中添加以下代码:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.mapView.ShowsUserLocation = true;
    this.mapView.RegionChanged += this.MapView_RegionChanged;
    }
    private void MapView_RegionChanged (object sender, MKMapViewChangeEventArgs e){
    if (this.mapView.UserLocation.Location != null){
    CLLocationCoordinate2D mapCoordinate = this.mapView.UserLocation.Location.Coordinate;
    Console.WriteLine("Current coordinates: LAT: {0}, LON: {1}", mapCoordinate.Latitude, mapCoordinate.Longitude);
    }
    }
    
    
  3. 在模拟器或设备上编译并运行应用程序。如果应用程序在模拟器上运行,默认位置将是苹果公司的总部位于 库比蒂诺如何操作...

  4. 放大或平移地图以在 应用程序输出 中输出当前位置。

它是如何工作的...

MonoTouch.MapKit 命名空间封装了 MapKit 框架中包含的所有对象。MapKit 框架使用谷歌地图来显示地图。

MKMapView 是默认的 iOS 视图,用于显示地图。它专门为此目的而设计,不应被子类化。

要在地图上显示用户的位置,我们将它的 ShowsUserLocation 属性设置为 true

this.mapView.ShowsUserLocation = true;

这将激活标准位置服务以开始接收位置更新并将它们内部传递给 MKMapView 对象。

为了确定用户何时放大或平移地图,我们订阅 RegionChanged 事件:

this.mapView.RegionChanged += this.MapView_RegionChanged;

在事件处理程序内部,我们通过 UserLocation 属性检索当前位置:

if (this.mapView.UserLocation.Location != null){
CLLocationCoordinate2D mapCoordinate = this.mapView.UserLocation.Location.Coordinate;
Console.WriteLine("Current coordinates: LAT: {0}, LON: {1}", mapCoordinate.Latitude, mapCoordinate.Longitude);
}

如果将 ShowsUserLocation 属性设置为 false,则位置服务将不会激活,并且 UserLocation.Location 属性将返回 null。当应用程序首次运行时,它也会返回 null,因为它会请求用户使用位置服务的权限。然而,只要设备或模拟器有活跃的互联网连接,就会显示地图。

还有更多...

我们可以使用 SetCenterCoordinate 方法设置要显示的地图的中心坐标:

CLLocationCoordinate2D mapCoordinates = new CLLocationCoordinate2D(0, 0);
this.mapView.SetCenterCoordinate(mapCoordinates, true);

第一个参数是我们希望地图中心对齐的地图坐标,表示为 CLLocationCoordinate2D 类型的对象。第二个参数声明我们是否希望地图中心对齐是动画的。

除了对齐地图外,我们还可以设置其缩放级别。我们通过 SetRegion 方法来完成:

this.mapView.SetRegion(MKCoordinateRegion.FromDistance( mapCoordinates, 1000, 1000), true);

第一个参数是 MKCoordinateRegion 类型。在这里,使用其 FromDistance 静态方法创建一个实例。其第一个参数是区域中心的坐标,接下来的两个参数代表要显示的地图的水平范围和垂直范围,单位为米。这基本上意味着由这个 MKCoordinateRegion 实例表示的区域将以 mapCoordinates 为中心,地图的水平部分和垂直部分将分别代表地图上的 1000 米。

注意,MKMapView会将实际区域设置为MKCoordinateRegion值的近似。这是因为MKMapView的尺寸不能总是与提供的水平和垂直跨度值匹配。例如,这里我们设置了一个1000x1000米的正方形区域,但我们的MKMapView布局并不是一个绝对的正方形,因为它基本上占据了整个屏幕。我们可以通过其Region属性检索MKMapView显示的实际地图区域。

使用 MapKit 时需要注意的事项

MapKit框架使用 Google Maps 和 Google Earth API 来显示地图。使用此框架将开发者绑定到 Google 的服务条款,可在code.google.com/apis/maps/iphone/terms.html查看。

一个可能直接影响你的应用程序是否会在应用商店被拒绝的重要术语是在地图上使用 Google 标志。应确保在显示地图时标志始终可见。

参见

在本章中:

  • 地理编码

  • 添加地图标注

  • 添加地图覆盖

地理编码

在本配方中,我们将学习如何根据位置坐标提供地址、城市或国家信息。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为GeocodingApp

如何操作...

  1. MainController视图的上半部分添加一个MKMapView,在下半部分添加一个标签和一个按钮。添加MonoTouch.MapKitMonoTouch.CoreLocation命名空间。

  2. MainController类中输入以下ViewDidLoad重写方法:

    private MKReverseGeocoder reverseGeocoder;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.mapView.ShowsUserLocation = true;
    this.buttonGeocode.TouchUpInside += delegate {
    this.lblOutput.Text = "Reverse geocoding location...";
    this.buttonGeocode.Enabled = false;
    CLLocationCoordinate2D currentLocation = this.mapView.UserLocation.Location.Coordinate;
    this.mapView.SetRegion(MKCoordinateRegion.FromDistance( currentLocation, 1000, 1000), true);
    this.reverseGeocoder = new MKReverseGeocoder(currentLocation);
    this.reverseGeocoder.Delegate = new ReverseGeocoderDelegate(this);
    this.reverseGeocoder.Start();
    } ;
    }
    
    
  3. 创建以下嵌套类:

    private class ReverseGeocoderDelegate : MKReverseGeocoderDelegate{
    public ReverseGeocoderDelegate(MainController parentController){
    this.parentController = parentController;
    }
    private MainController parentController;
    public override void FoundWithPlacemark (MKReverseGeocoder geocoder, MKPlacemark placemark){
     this.parentController.lblOutput.Text = string.Format( "Locality: {0}\nAdministrative area: {1}\nCountry: {2}", placemark.Locality, placemark.AdministrativeArea, placemark.Country); 
    geocoder.Dispose();
    this.parentController.buttonGeocode.Enabled = true;
    }
    public override void FailedWithError (MKReverseGeocoder geocoder, NSError error){
    this.parentController.lblOutput.Text = string.Format( "Reverse geocoding failed with error: {0}", error.LocalizedDescription);
    this.parentController.buttonGeocode.Enabled = true;
    }
    }
    
    
  4. 在模拟器或设备上编译并运行应用程序。如果在设备上运行,当你点击按钮时,将在标签上显示你当前所在的国家和地区的位置信息。

它是如何工作的...

地理编码是将地址信息与地理坐标匹配的过程。反向地理编码是将地理坐标与地址信息匹配的过程。iOS 上仅提供后者。然而,在线上还有可用的正向地理编码服务。

要进行反向地理编码,我们使用MKReverseGeocoder类:

private MKReverseGeocoder reverseGeocoder;

此类需要一个提供信息的代理对象。我们将为MKReverseGeocoder的代理对象创建的类必须继承自MKReverseGeocoderDelegate类:

private class ReverseGeocoderDelegate : MKReverseGeocoderDelegate

在代理对象内部,我们需要重写两个方法。第一个是FoundWithPlacemark:

public override void FoundWithPlacemark (MKReverseGeocoder geocoder, MKPlacemark placemark)

这是当反向地理编码器检索地理编码信息时将被触发的方法。信息包含在placemark参数中,该参数是MKPlacemark类型。如前所述的高亮代码所示,信息可以通过MKPlacemark类的各种属性获取。

我们需要重写的第二个方法是FailedWithError:

public override void FailedWithError (MKReverseGeocoder geocoder, NSError error)

当由于某种原因反向地理编码失败时,此方法会被触发。信息包含在 error 参数中。

要初始化 MKReverseGeocoder 类的实例,我们将我们想要地理编码信息的坐标与类型为 CLLocationCoordinate2D 的对象传递给其构造函数:

this.reverseGeocoder = new MKReverseGeocoder(currentLocation);

在分配其代理对象后,我们调用 Start 方法来反向地理编码坐标:

this.reverseGeocoder.Delegate = new ReverseGeocoderDelegate(this);
this.reverseGeocoder.Start();

更多...

可以通过 MKPlacemark 类的 AddressDictionary 属性检索位置地址的详细信息,该属性是 NSDictionary 类型。

使用 MKReverseGeocoder 类时需要注意的事项

MKReverseGeocoder 类是 MapKit 框架的一部分。使用此类将开发者绑定到 Google 的服务条款:code.google.com/apis/maps/iphone/terms.html

使用反向地理编码服务的一个重要条款是,它应该始终与 Google 地图结合使用。此外,为了避免滥用服务,Apple 建议每分钟不要进行超过一次反向地理编码调用。

参见

在本章中:

  • 显示地图

  • 添加地图注释

  • 添加地图覆盖

添加地图注释

在本食谱中,我们将讨论如何注释地图以显示各种信息。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 MapAnnotateApp

如何操作...

  1. MainController 的视图中添加一个 MKMapView。在底部留出一些空间,并添加一个按钮。

  2. 添加命名空间 MonoTouch.MapKitMonotouch.CoreLocation,并在 ViewDidLoad 覆盖方法中输入以下代码:

    this.mapView.ShowsUserLocation = true;
    this.mapView.Delegate = new MapViewDelegate();
    this.buttonAddPin.TouchUpInside += delegate {
    CLLocationCoordinate2D mapCoordinate = this.mapView.UserLocation.Location.Coordinate;
    this.mapView.SetRegion(MKCoordinateRegion.FromDistance( mapCoordinate, 1000, 1000), true);
    MKPointAnnotation myAnnotation = new MKPointAnnotation();
    myAnnotation.Coordinate = mapCoordinate;
    myAnnotation.Title = "MyAnnotation";
    myAnnotation.Subtitle = "Standard annotation";
    this.mapView.AddAnnotation(myAnnotation);
    } ;
    
    
  3. 创建以下嵌套类:

    private class MapViewDelegate : MKMapViewDelegate{
    public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation){
    if (annotation is MKUserLocation){
    return null;
    } else{
    string reuseIdentifier = "MyAnnotation";
    MKPinAnnotationView pinView = mapView.DequeueReusableAnnotation(reuseIdentifier) as MKPinAnnotationView;
    if (null == pinView){
    pinView = new MKPinAnnotationView(annotation, reuseIdentifier);
    pinView.PinColor = MKPinAnnotationColor.Purple;
    pinView.AnimatesDrop = true;
    pinView.CanShowCallout = true;
    }
    return pinView;
    }
    }
    }
    
    
  4. 在模拟器或设备上编译并运行应用程序。如果在模拟器上运行,当你点击按钮时,结果应该类似于以下截图:

如何操作...

  • 点击“添加引脚”会显示带有注释标题和副标题的呼叫气泡。

它是如何工作的...

注释地图对于提供与地图数据相关的各种信息非常有用。我们可以使用 MKPointAnnotation 类来创建一个简单的注释:

MKPointAnnotation myAnnotation = new MKPointAnnotation();
myAnnotation.Coordinate = mapCoordinate;
myAnnotation.Title = "MyAnnotation";
myAnnotation.Subtitle = "Standard annotation";
this.mapView.AddAnnotation(myAnnotation);

我们为注释将出现的地图坐标分配,以及可选的标题和副标题。然后我们使用 AddAnnotation 方法将注释添加到地图视图中。

仅向地图视图添加注释对象是不够的。注释需要一个显示其信息的视图。这是通过为地图视图创建一个代理对象并覆盖其 GetViewForAnnotation 方法来实现的:

public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation)

由于地图已经显示了用户位置,已经存在一个注释,并且它是 MKUserLocation 类型。在 GetViewForAnnotation 中,我们必须确保通过检查注释参数的类型来为我们自己的注释提供一个视图:

if (annotation is MKUserLocation)

在这个例子中,我们只返回 null。如果注释参数是 MKPointAnnotation 类型,那么我们首先尝试检索它的视图,类似于 UITableView 创建它包含的单元格:

MKPinAnnotationView pinView = mapView.DequeueReusableAnnotation( reuseIdentifier) as MKPinAnnotationView;

如果 DequeueReusableAnnotation 方法的返回结果是 null,那么我们为我们的注释视图初始化一个新的实例:

pinView = new MKPinAnnotationView(annotation, reuseIdentifier);
pinView.PinColor = MKPinAnnotationColor.Purple;
pinView.AnimatesDrop = true;
pinView.CanShowCallout = true;

我们为注释创建的视图是 MKPinAnnotationView 类型。这是由地图上的针表示的标准视图。我们设置的属性相当直接,定义了其外观和行为。PinColor 属性定义了针的颜色,AnimatesDrop 属性定义了针是否以动画形式显示在地图上,而 CanShowCallout 属性定义了注释视图是否在呼出气泡中显示其底层注释的信息。

在我们创建了注释视图之后,我们只需从方法中返回它:

return pinView;

更多内容...

我们还可以创建自定义注释和注释视图。对于注释,我们必须重写 MKAnnotation 类,而对于注释视图,我们可以重写 MKAnnotationView 类。

注释性能

理论上,我们可以向地图视图添加任意数量的注释。虽然 MKMapView 可以高效地管理大量注释,但强烈建议考虑性能下降。一种克服这个问题的方法是根据当前地图区域显示注释,这基本上管理了地图的缩放级别。另一种方法是确保我们为不需要不同注释视图的注释使用相同的注释视图实例。

相关内容

在本章中:

  • 显示地图

  • 添加地图覆盖

在本书中:

第五章, 显示数据:

  • 在表中显示数据

添加地图覆盖

在这个菜谱中,我们将讨论使用覆盖层在地图上绘制。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 MapOverlayApp

如何做...

  1. MainController 的视图中添加一个 MKMapView。在底部留出一些空间,并添加一个按钮。

  2. 添加命名空间 MonoTouch.MapKitMonotouch.CoreLocation,并在 ViewDidLoad 重写中输入以下代码:

    this.mapView.ShowsUserLocation = true;
    this.mapView.Delegate = new MapViewDelegate();
    this.buttonAddOverlay.TouchUpInside += delegate {
    CLLocationCoordinate2D mapCoordinate = this.mapView.UserLocation.Location.Coordinate;
    this.mapView.SetRegion(MKCoordinateRegion.FromDistance( mapCoordinate, 1000, 1000), true);
    MKCircle circleOverlay = MKCircle.Circle(mapCoordinate, 250);
    this.mapView.AddOverlay(circleOverlay);
    } ;
    
    
  3. 添加以下嵌套类:

    private class MapViewDelegate : MKMapViewDelegate{
    public override MKOverlayView GetViewForOverlay (MKMapView mapView, NSObject overlay){
    MKCircle circleOverlay = overlay as MKCircle;
    if (null != circleOverlay){
    MKCircleView circleView = new MKCircleView(circleOverlay);
    circleView.FillColor = UIColor.FromRGBA( 1.0f, 0.5f, 0.5f, 0.5f);
    circleView.StrokeColor = UIColor.Red;
    circleView.LineWidth = 2f;
    return circleView;
    } else{
    return null;
    }
    }
    }
    
    
  4. 在模拟器或设备上编译并运行应用程序。如果在模拟器上运行,在点击按钮后,结果应该类似于以下内容:

如何做...

工作原理...

虽然 MKAnnotation 代表地图上的一个点,但 MKOverlay 对象可以代表地图上的一个区域。在这个例子中,我们使用继承自 MKOverlayMKCircle 类来在地图上的区域上显示一个圆。

初始化 MKCircle 实例使用其 Circle 静态方法:

MKCircle circleOverlay = MKCircle.Circle(mapCoordinate, 250);

第一个参数表示圆心的坐标,而第二个参数表示圆的半径,单位为米。初始化后,我们使用AddOverlay方法将叠加添加到地图视图中:

this.mapView.AddOverlay(circleOverlay);

就像注释一样,叠加需要视图来显示其信息。为了为我们的叠加提供视图,我们在地图视图的代理对象实现中重写了GetViewForOverlay方法:

public override MKOverlayView GetViewForOverlay (MKMapView mapView, NSObject overlay)

在此方法内部,我们首先检查叠加参数是否是我们想要的类型;在这种情况下,是一个MKCircle:

MKCircle circleOverlay = overlay as MKCircle;
if (null != circleOverlay)

然后,我们创建MKCircleView类的实例并返回它:

MKCircleView circleView = new MKCircleView(circleOverlay);
circleView.FillColor = UIColor.FromRGBA(1.0f, 0.5f, 0.5f, 0.5f);
circleView.StrokeColor = UIColor.Red;
circleView.LineWidth = 2f;
return circleView;

我们设置适当的属性来定义叠加的外观。在这种情况下,我们设置了FillColorStrokeColorLineWidth属性。

更多内容...

地图视图有效地处理叠加。地图视图为我们处理的一个重要事项是,当我们缩放地图时,叠加会自动缩放到匹配每个缩放级别。这样,我们就不需要在代码中手动缩放叠加。

创建自定义叠加

我们可以创建自己的自定义叠加。为此,我们需要为叠加重写MKOverlay类,为叠加视图重写MKOverlayView类。

标准叠加对象

除了MKCircle之外,其他标准叠加对象还包括MKPolygon用于创建多边形形状和MKPolyline用于创建折线,如在轨迹记录应用程序中。

参考内容

在本章中:

  • 显示地图

  • 添加地图注释

第十一章. 图形和动画

在本章中,我们将涵盖:

  • 动画化视图

  • 变换视图

  • 使用图像进行动画

  • 动画化层

  • 绘制线条和曲线

  • 绘制形状

  • 绘制文本

  • 一个简单的绘图应用程序

  • 创建图像上下文

简介

在本章中,我们将讨论自定义绘制和动画。iOS SDK 包含两个用于这些任务的非常有用的框架:Core Graphics 和 Core Animation。

这两个框架简化了在 UI 元素上动画化以及绘制 2D 图形的过程。有效使用这两个框架将在平淡无奇和令人惊叹的应用程序之间产生差异。毕竟,这两个框架在使 iOS 平台在其类别中独一无二方面发挥着非常重要的作用。

我们将学习如何为控件提供简单甚至更复杂的动画,以提供独特的用户体验。我们还将看到如何在屏幕上自定义绘制线条、曲线、形状和文本。最后,通过所有提供的示例,我们将创建两个绘图应用程序。

动画化视图

在这个菜谱中,我们将学习如何利用UIKit动画在屏幕上移动UILabel

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为ViewAnimationApp。在MainController的视图中添加一个标签和一个按钮。

如何做到这一点...

  1. 添加MonoTouch.ObjCRuntime命名空间,并在以下ViewDidLoad重写中输入:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.lblOutput.BackgroundColor = UIColor.Green;
    this.buttonAnimate.TouchUpInside += delegate {
    RectangleF labelFrame = this.lblOutput.Frame;
    labelFrame.Y = 380f;
    UIView.BeginAnimations("LabelPositionAnimation");
    UIView.SetAnimationDuration(1);
    UIView.SetAnimationCurve(UIViewAnimationCurve.EaseInOut);
    UIView.SetAnimationDelegate(this);
    UIView.SetAnimationDidStopSelector(new Selector("LabelPositionAnimationStopped"));
    this.lblOutput.Frame = labelFrame;
    UIView.CommitAnimations();
    };
    }
    
    
  2. 添加以下方法:

    [Export("LabelPositionAnimationStopped")]
    public void LabelAnimationStopped(){
    this.lblOutput.Text = "Animation ended!";
    this.lblOutput.BackgroundColor = UIColor.Red;
    }
    
    
  3. 在模拟器上编译并运行应用程序。

  4. 点击Animate!按钮,并观察标签移动到视图的下半部分。

它是如何工作的...

UIView类包含许多针对动画的静态方法。在这个例子中,我们只是通过动画改变了一个标签的位置。

要动画化位置的变化,我们需要在调用BeginAnimations方法之后应用这些更改:

UIView.BeginAnimations("LabelPositionAnimation");

它接受一个字符串参数,该参数声明了动画的名称。在这次调用之后我们对视图所做的更改将被动画化。但,我们也可以调整各种动画参数:

UIView.SetAnimationDuration(1);
UIView.SetAnimationCurve(UIViewAnimationCurve.EaseInOut);

SetAnimationDuration方法定义了动画的持续时间(以秒为单位)。SetAnimationCurve方法定义了将在动画的起点和/或终点应用到的默认缓动函数。

当动画完成时,我们有执行代码的选项。为此,我们首先需要使用SetAnimationDelegate方法设置动画代理对象:

UIView.SetAnimationDelegate(this);

在这个例子中,我们将我们的控制器对象MainController设置为动画代理对象。在设置代理对象后,我们需要设置当动画完成时将被调用的选择器:

UIView.SetAnimationDidStopSelector(new Selector("LabelPositionAnimationStopped"));

要创建Selector实例,我们需要使用MonoTouch.ObjCRuntime命名空间:

using MonoTouch.ObjCRuntime;

在对动画进行所有调整后,我们将新值设置到将被动画化的对象上,并调用CommitAnimations方法:

this.lblOutput.Frame = labelFrame;
UIView.CommitAnimations();

注意,在 BeginAnimations 调用下面的代码将在 CommitAnimations 行执行。此外,每个使用 BeginAnimations 方法开始的动画都应该有一个相应的 CommitAnimations 方法调用,否则可能会出现意外结果;例如,对 UI 元素所做的每个更改都将被动画化。

还有更多...

UIView 类还包含一个重载的 Animate 方法。此方法基本上将我们在这里使用的方法封装在一个方法中。使用 Animate 方法的前一个示例表示为以下代码:

UIView.Animate(1, 0, UIViewAnimationOptions.CurveEaseInOut, delegate { this.lblOutput.Frame = labelFrame; }, delegate { this.LabelAnimationStopped(); } );

此重载的第二个参数是动画开始后的延迟。

UIKit 动画和 iOS 版本

Animate 方法是在 iOS 4.0 版本中引入的。当针对低于 4 的 iOS 版本时,使用由 BeginAnimationsCommitAnimations 方法定义的动画块。

可动画属性

UIKit 动画支持一组特定的 UIView 属性。这些属性被称为 可动画属性。以下是可被动画化的 UIView 属性列表:

  • Frame

  • Bounds

  • Center

  • Transform

  • Alpha

  • BackgroundColor

  • ContentStretch

视图变换

在这个菜谱中,我们将通过应用变换来旋转 UILabel。此外,旋转将会有动画效果。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 TransformViewApp。在 MainController 的视图中添加一个标签和一个按钮。

如何实现...

  1. 添加 MonoTouch.CoreGraphics 命名空间:

    using MonoTouch.CoreGraphics;
    
    
  2. MainController 类中输入以下代码:

    private double rotationAngle;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.buttonRotate.TouchUpInside += delegate {
    this.rotationAngle += 90;
    CGAffineTransform transform = CGAffineTransform.MakeRotation( (float)this.DegreesToRadians(this.rotationAngle));
    UIView.BeginAnimations("RotateLabelAnimation");
    UIView.SetAnimationDuration(0.5f);
    this.lblOutput.Transform = transform;
    UIView.CommitAnimations();
    this.lblOutput.Text = string.Format("Rotated to {0} degrees.", this.rotationAngle);
    if (this.rotationAngle >= 360){
    this.rotationAngle = 0;
    this.lblOutput.Transform = CGAffineTransform.MakeIdentity();
    }
    };
    }
    public double DegreesToRadians (double degrees){
    return (degrees * Math.PI / 180);
    }
    
    
  3. 在模拟器上编译并运行应用程序。

  4. 点击按钮并观察标签旋转。

如何实现...

工作原理...

MonoTouch.CoreGraphics 命名空间是 CoreGraphics 框架的包装器。这个框架是 iOS 的基本图形框架。

要旋转视图,我们需要一个变换对象,该对象将通过视图的 Transform 属性应用于视图:

CGAffineTransform transform = CGAffineTransform.MakeRotation( (float)this.DegreesToRadians(this.rotationAngle));

变换对象是 CGAffineTransform 类的实例,并通过 MakeRotation 静态方法初始化。此方法接受一个浮点值,表示我们想要应用的角度,单位为弧度。可以使用 DegreesToRadians 方法将度转换为弧度。创建变换对象后,我们将其分配给标签的 Transform 属性,在动画块内部:

UIView.BeginAnimations("RotateLabelAnimation");
UIView.SetAnimationDuration(0.5f);
this.lblOutput.Transform = transform;
UIView.CommitAnimations();

注意,每次按下按钮时,我们需要增加旋转角度,因为我们应用的变化不会被自动增加。如果我们应用另一个具有相同角度的旋转变换对象,将不会有任何效果,因为它基本上是相同的变化。

当标签旋转到完整圆(=360 度)时,我们重置 rotationAngle 值和变换对象:

this.rotationAngle = 0;
this.lblOutput.Transform = CGAffineTransform.MakeIdentity();

MakeIdentity静态方法创建一个恒等变换对象,这是在应用变换对象之前所有视图的默认变换。

还有更多...

CGAffineTransform类包含用于创建变换对象的静态方法。这些是:

  • CGAffineTransformInvert: 此方法反转当前变换并返回结果

  • MakeIdentity: 此方法创建一个恒等变换

  • MakeRotation: 此方法创建一个旋转变换

  • MakeScale: 此方法创建一个缩放变换

  • MakeTranslation: 此方法创建一个平移变换

  • Multiply: 此方法将两个变换相乘并返回结果

变换和框架

在对一个视图应用变换后,其Frame属性不应被考虑。如果需要在变换后更改视图的大小或位置,请分别使用BoundsCenter属性。

相关内容

在这一章中:

  • 动画视图

  • 动画层

使用图像的动画

在这个菜谱中,我们将使用UIImageView内置的动画功能创建一个简单的图片幻灯片。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为ImageAnimationApp。在MainController的视图中添加一个UIImageView和两个按钮。此任务的示例项目包含三张图片。将两张或更多图片添加到项目中,并将它们的构建操作设置为内容

如何做到这一点...

  1. 输入以下ViewDidLoad重写:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
    this.imageView.AnimationImages = new UIImage[] {
    UIImage.FromFile("images/Kastoria.jpg"),
    UIImage.FromFile("images/Parga02.jpg"),
    UIImage.FromFile("images/Toroni.jpg")
    } ;
    this.imageView.AnimationDuration = 3;
    this.imageView.AnimationRepeatCount = 10;
    this.buttonAnimate.TouchUpInside += delegate {
    if (!this.imageView.IsAnimating){
    this.imageView.StartAnimating();
    }
    };
    this.buttonStop.TouchUpInside += delegate {
    if (this.imageView.IsAnimating){
    this.imageView.StopAnimating();
    }
    };
    }
    
    
  2. 在模拟器上编译并运行应用程序。

  3. 点击动画图像按钮以开始动画。

它是如何工作的...

UIImageView可以接受一个UIImage对象的数组,并自动按顺序显示它们。

要加载视图将动画的图像,将图像的数组分配给其AnimationImages属性:

this.imageView.AnimationImages = new UIImage[] {
UIImage.FromFile("images/Kastoria.jpg"),
UIImage.FromFile("images/Parga02.jpg"),
UIImage.FromFile("images/Toroni.jpg")
} ;

图像显示的顺序由它们在数组中的顺序定义。在设置将动画的图像后,我们设置动画的持续时间(以秒为单位)和它将发生的次数:

this.imageView.AnimationDuration = 3;
this.imageView.AnimationRepeatCount = 10;

要开始或停止动画,分别调用StartAnimatingStopAnimating方法。

还有更多...

UIImageViewAnimationImages属性和Image属性之间没有关系。如果需要在没有动画时显示图像,请在动画前后将图像设置到Image属性中。

检查动画

要确定是否发生动画,请检查UIImageViewIsAnimating属性。

相关内容

在这一章中:

  • 动画视图

在这本书中:

第二章,用户界面:视图:

  • 显示图像

动画层

在这个菜谱中,我们将学习如何使用Core Animation框架通过动画其层来复制屏幕上的UILabel

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 LayerAnimation。在 MainController 的视图中添加两个标签和一个按钮。为第一个视图设置文本和背景颜色,并为第二个视图设置不同的背景颜色。

如何做...

  1. 添加 MonoTouch.CoreAnimation 命名空间:

    using MonoTouch.CoreAnimation;
    
    
  2. 在类中添加一个 CALayer 类型的字段:

    private CALayer copyLayer;
    
    
  3. ViewDidLoad 覆盖中添加以下代码:

    this.buttonCopy.TouchUpInside += delegate {
    this.lblTarget.Text = string.Empty;
    this.lblTarget.BackgroundColor = UIColor.Blue;
    this.copyLayer = new CALayer();
    this.copyLayer.Frame = this.lblSource.Frame;
    this.copyLayer.Contents = this.lblSource.Layer.Contents;
    this.View.Layer.AddSublayer(this.copyLayer);
     CABasicAnimation positionAnimation = CABasicAnimation.FromKeyPath("position");
    positionAnimation.To = NSValue.FromPointF(this.lblTarget.Center);
    positionAnimation.Duration = 1;
    positionAnimation.RemovedOnCompletion = true;
    positionAnimation.TimingFunction = CAMediaTimingFunction.FromName( CAMediaTimingFunction.EaseInEaseOut);
    positionAnimation.AnimationStopped += delegate {
    this.lblTarget.BackgroundColor = this.lblSource.BackgroundColor;
    this.lblTarget.Text = this.lblSource.Text;
    this.lblTarget.TextColor = this.lblSource.TextColor;
    this.copyLayer.RemoveFromSuperLayer();
    };
    CABasicAnimation sizeAnimation = CABasicAnimation.FromKeyPath("bounds");
    sizeAnimation.To = NSValue.FromRectangleF(new RectangleF(0f, 0f, this.lblSource.Bounds.Width * 2f, this.lblSource.Bounds.Height * 2));
    sizeAnimation.Duration = positionAnimation.Duration / 2;
    sizeAnimation.RemovedOnCompletion = true;
    sizeAnimation.AutoReverses = true; 
    this.copyLayer.AddAnimation(positionAnimation, "PositionAnimation");
    this.copyLayer.AddAnimation(sizeAnimation, "SizeAnimation");
    } ;
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 点击 复制 按钮以动画方式将第一个标签的内容复制到第二个标签。

如何做...

它是如何工作的...

MonoTouch.CoreAnimation 命名空间是 Core Animation 框架的包装器。

每个视图都有一个 Layer 属性,它返回视图的 CALayer 对象。在这个任务中,我们正在创建一个动画,它以图形方式显示从一个标签复制内容到另一个标签。

我们不是创建另一个标签并用 UIView 动画移动它,而是创建一个层并移动它。我们通过设置其 FrameContents 属性来创建层,后者来自源标签的层。然后我们使用 AddSublayer 方法将层添加到主视图的层中。从这一点开始,主视图包含一个层,它显示相同的内容,并且位于源标签之上。

this.copyLayer = new CALayer();
this.copyLayer.Frame = this.lblSource.Frame;
this.copyLayer.Contents = this.lblSource.Layer.Contents;
this.View.Layer.AddSublayer(this.copyLayer);

要动画化从源标签到目标标签的过渡,我们将使用 CABasicAnimation 类。前面的高亮代码展示了如何初始化和设置类的实例。FromKeyPath 静态方法创建一个新的实例,接受作为参数的将被动画化的层的属性名称。To 属性表示属性将被动画到的值。Duration 属性表示动画的持续时间(以秒为单位),而 RemovedOnCompletion 属性声明当动画完成时应该从层中移除动画对象。TimingFunction 属性设置动画的行为。当动画完成时,会触发 AnimationStopped 事件。在分配给它的处理程序内部,我们将源标签的内容设置为目标标签,从而完成复制。AutoReverses 属性表示当 To 属性的值达到时,动画应该反转。正是这个属性使得标签在达到最终位置时先变大然后变小。

当动画被添加到层中时,动画开始:

this.copyLayer.AddAnimation(positionAnimation, "PositionAnimation");
this.copyLayer.AddAnimation(sizeAnimation, "SizeAnimation");

还有更多...

可以在以下链接中找到 FromKeyPath 方法接受的字符串列表:http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/KVCAdditions.html#//apple_ref/doc/uid/TP40005299

除了 To 属性外,CABasicAnimation 类还有两个用于定义动画的属性:FromBy。它们都是 NSObject 类型,但应该分配给它们的实际值应该是 NSValue 类型。NSValue 类包含创建其实例的各种静态方法。

是非常强大且高效的用于绘制和动画的对象。强烈建议使用层在视图上执行动画,而不是使用实际的视图本身。

参见

在本章

  • 动画视图

绘制线条和曲线

在这个菜谱中,我们将实现自定义绘制,在 UIView 上绘制两条线。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 DrawLineApp

如何操作...

  1. 向项目中添加一个新类,并将其命名为 DrawingView。从 UIView 派生它:

    public class DrawingView : UIView
    
    
  2. DrawingView.cs 文件中添加以下 using 指令:

    using MonoTouch.CoreGraphics;
    
    
  3. 覆盖 UIViewDraw 方法,并使用以下代码实现它:

    public override void Draw (RectangleF rect){
    base.Draw (rect);
    Console.WriteLine("DrawingView draw!");
    using (CGContext context = UIGraphics.GetCurrentContext()){
    context.SetLineWidth(5f);
    context.SetStrokeColorWithColor(UIColor.Green.CGColor);
    context.AddLines(new PointF[] {
    new PointF(0f, this.Bounds.Height),
    new PointF(this.Bounds.Width, 0f)
    } );
    context.StrokePath();
    context.SetStrokeColorWithColor(UIColor.Red.CGColor);
     context.MoveTo(0, this.Bounds.Height);
    context.AddCurveToPoint(0f, this.Bounds.Height, 50f, this.Bounds.Height / 2f, this.Bounds.Width, 0f); 
    context.StrokePath();
    }
    }
    
    
  4. MainControllerViewDidLoad 覆盖中,初始化并添加视图:

    DrawingView drawingView = new DrawingView(this.View.Bounds);
    drawingView.BackgroundColor = UIColor.Gray;
    this.View.AddSubview(this.drawingView);
    
    
  5. 在模拟器上编译并运行应用程序。结果应该类似于以下内容:如何操作...

它是如何工作的...

MonoTouch.CoreGraphics 命名空间是对原生 Core Graphics 框架的封装。Core Graphics 框架包含了在视图中进行自定义绘制的必要对象。

要在视图上绘制,我们必须覆盖其 Draw(RectangleF) 方法:

public override void Draw (RectangleF rect)

Draw 方法内部,我们需要当前图形上下文的实例:

using (CGContext context = UIGraphics.GetCurrentContext())

图形上下文由 CGContext 类表示。UIGraphics.GetCurrentContext 静态方法返回当前上下文的一个实例。

CGContext 类包含各种方法,允许我们在视图上绘制。我们需要设置线宽、颜色,然后添加绘制类型:

context.SetLineWidth(5f);
context.SetStrokeColorWithColor(UIColor.Green.CGColor);
context.AddLines(new PointF[] {
new PointF(0f, this.Bounds.Height),
new PointF(this.Bounds.Width, 0f)
} );

要添加线条,我们使用接受包含每条线的起点和终点的 PointF 结构数组 AddLines 方法。仅仅将线条添加到上下文中是不够的。为了在视图上呈现绘制内容,我们调用 StrokePath 方法:

context.StrokePath();

要向绘制中添加另一个项目,我们相应地重复步骤。MoveTo 方法将当前点移动,以便附加项目将有一个曲线的起点。

还有更多...

当运行时需要绘制视图的内容时,会调用 Draw 方法。我们只能在 Draw 方法内部获取当前图形上下文的实例。我们不应该直接调用它,因为如果这样做,UIGraphics.GetCurrentContext 方法将返回 null。如果我们需要强制运行时调用 Draw 方法,我们需要调用 SetNeedsDisplay()。在调用它时应该小心,因为绘制操作在 CPU 使用方面代价高昂。

当不需要重新绘制整个视图区域时,我们可以调用SetNeedsDisplayInRect方法,传递视图坐标系中要更新的区域的RectangleF

在 UIImageView 上的图形上下文

UIImageView的当前图形上下文保留用于绘制图像内容。在继承自UIImageView的自定义视图中调用SetNeedsDisplay与直接调用Draw方法具有相同的效果。如果我们需要在自定义图像视图中绘制,我们必须要么在其上方添加另一个视图并在该视图上绘制,要么在自定义图层上绘制并将其添加到视图的主图层中。

参见

在本章中:

  • 绘制文本

在本书中:

第二章, 用户界面:视图:

  • 创建一个自定义视图

绘制形状

按照前一个食谱中的示例,我们将在屏幕上绘制一个圆和一个正方形。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为DrawShapeApp。添加一个类似于之前的任务的自定义视图,并将其命名为DrawingView

如何做...

  1. Draw方法的重写中添加以下代码:

    using (CGContext context = UIGraphics.GetCurrentContext()){
    context.SetFillColorWithColor(UIColor.Blue.CGColor);
    context.SetShadow(new SizeF(10f, 10f), 5f);
    context.AddEllipseInRect(new RectangleF(100f, 100f, 100f, 100f));
    context.FillPath();
    context.SetFillColorWithColor(UIColor.Red.CGColor);
    context.AddRect(new RectangleF(150f, 150f, 100f, 100f));
    context.FillPath();
    }
    
    
  2. 在模拟器上编译并运行应用程序。屏幕上的结果应类似于以下内容:

如何做...

它是如何工作的...

要在视图中绘制形状,我们需要调用适当的方法。我们首先设置CGContext实例的填充颜色:

context.SetFillColorWithColor(UIColor.Blue.CGColor);

要绘制圆,我们调用AddEllipseInRect方法,传递一个包含圆边界矩形的RectangleF对象:

context.AddEllipseInRect(new RectangleF(100f, 100f, 100f, 100f));

形状是否为椭圆或绝对圆由边界矩形的尺寸定义。然后我们调用FillPath方法:

context.FillPath();

阴影效果由SetShadow方法定义:

context.SetShadow(new SizeF(10f, 10f), 5f);

第一个参数,其类型为SizeF,定义了阴影的偏移量,而第二个参数定义了模糊量。

还有更多...

当调用SetShadow方法时,所有添加到上下文中的对象都会显示阴影。要移除阴影,调用SetShadowWithColor方法,传递一个完全透明的颜色或为颜色参数传递null

透明颜色

要用透明色填充形状,创建一个具有适当值的CGColor实例:

context.SetFillColorWithColor(new CGColor(1f, 0f, 0f, 0.5f));

这将创建一个红色,其不透明度设置为 50%的颜色。

参见

在本章中:

  • 绘制线条和曲线

绘制文本

在本食谱中,我们将学习如何使用轮廓绘制样式文本。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为DrawTextApp。将我们在之前的任务中创建的DrawingView类添加到项目中。

如何做...

  1. DrawingView类中实现以下Draw方法的重写:

    using (CGContext context = UIGraphics.GetCurrentContext()){
    context.SetFillColorWithColor(UIColor.Yellow.CGColor);
    context.SetTextDrawingMode(CGTextDrawingMode.FillStroke);
    NSString drawText = new NSString("This text is drawn!");
    drawText.DrawString(new PointF(10f, 100f), UIFont.FromName("Verdana-Bold", 28f));
    }
    
    
  2. 在模拟器上编译并运行应用程序。文本将在屏幕上显示。结果应类似于以下内容:

如何做...

它是如何工作的...

NSString 类包含一个非常有用的方法 DrawString,它将包含的文本绘制到当前上下文中。为了提供轮廓效果,我们调用 SetTextDrawingMode 方法:

context.SetTextDrawingMode(CGTextDrawingMode.FillStroke);

我们传递了 CGTextDrawingMode.FillStroke 值。由于我们没有为上下文设置描边颜色,它默认为黑色。

最后,调用 DrawString 方法:

drawText.DrawString(new PointF(10f, 100f), UIFont.FromName( "Verdana-Bold", 28f));

此方法有多个重载。我们在这里使用的是接受一个 PointF 结构的重载,它表示字符串在视图坐标系中的位置,以及一个 UIFont 实例,它表示文本将在屏幕上通过该字体渲染。

还有更多...

CGContext 类包含用于绘制文本的方法。我们首先需要调用 SelectFont 方法来分配字体:

context.SelectFont("Verdana-Bold", 28f, CGTextEncoding.MacRoman);

然后我们调用 ShowTextAtPoint 方法来绘制文本:

context.ShowTextAtPoint(10, 100, drawText.ToString());

这将给出以下结果:

还有更多...

文本将显示在正确的位置,但方向相反。为了纠正这一点,我们需要将一个变换矩阵设置到 TextMatrix 属性:

context.TextMatrix = new CGAffineTransform(1, 0, 0, -1, 0, 0);

使用 CGContext 类的方法的最大优点是我们可以轻松地变换文本的外观。例如,通过应用一个稍微不同的变换矩阵,我们可以轻松地显示倾斜的文本:

context.TextMatrix = new CGAffineTransform(1, 1, 0, -1, 0, 0);

还有更多...

绘制文本的大小

NSString 类的 DrawString 方法返回文本的边界矩形的大小。然而,我们可以通过 StringSize 方法在绘制之前获取文本的大小:

Console.WriteLine("Text size: {0}", drawText.StringSize(UIFont.FromName("Verdana-Bold", 28f)));

参见

在本章中:

  • 绘制线条和曲线

  • 绘制形状

一个简单的绘图应用程序

在这个菜谱中,我们将使用我们学到的技术来创建一个绘图应用程序。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 FingerDrawingApp。再次,我们需要一个自定义视图。添加一个继承自 UIView 的类,并将其命名为 CanvasView

如何实现...

  1. 使用以下代码实现 CanvasView 类:

    public class CanvasView : UIView{
    public CanvasView (RectangleF frame) : base(frame){
    this.drawPath = new CGPath();
    }
    private PointF touchLocation;
    private PointF previousTouchLocation;
    private CGPath drawPath;
    private bool fingerDraw;
    public override void TouchesBegan (NSSet touches, UIEvent evt){
    base.TouchesBegan (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    this.fingerDraw = true;
    this.touchLocation = touch.LocationInView(this);
    this.previousTouchLocation = touch.PreviousLocationInView(this);
    this.SetNeedsDisplay();
    }
    public override void TouchesMoved (NSSet touches, UIEvent evt){
    base.TouchesMoved (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    this.touchLocation = touch.LocationInView(this);
    this.previousTouchLocation = touch.PreviousLocationInView(this);
    this.SetNeedsDisplay();
    }
    public override void Draw (RectangleF rect){
    base.Draw (rect);
    if (this.fingerDraw){
    using (CGContext context = UIGraphics.GetCurrentContext()){
    context.SetStrokeColorWithColor(UIColor.Blue.CGColor);
    context.SetLineWidth(5f);
     context.SetLineJoin(CGLineJoin.Round);
    context.SetLineCap(CGLineCap.Round); 
    this.drawPath.MoveToPoint(this.previousTouchLocation);
    this.drawPath.AddLineToPoint(this.touchLocation);
    context.AddPath(this.drawPath);
    context.DrawPath(CGPathDrawingMode.Stroke);
    }
    }
    }
    }
    
    
  2. 在模拟器或设备上编译并运行应用程序。

  3. 用手指触摸并拖动(或使用光标点击并拖动)开始绘画!

如何实现...

它是如何工作的...

在这个任务中,我们将结合触摸事件和自定义绘制来创建一个简单的绘图应用程序。当用户触摸并在屏幕上移动手指时,我们保留触摸位置点的信息,并在 Draw 方法中使用这些信息来绘制线条。

在将触摸位置设置为类字段后,我们调用 SetNeedsDisplay 来强制调用 Draw 方法。fingerDraw 字段用于确定 Draw 方法是由屏幕上的触摸触发的,而不是在视图首次加载时由运行时触发的。

每次我们调用一个方法将某个东西绘制到图形上下文中时,该上下文中的先前绘图都会被清除。为了避免这种行为,我们使用CGPath对象。我们可以在CGPath中添加各种绘图对象,并通过将它们添加到图形上下文中来显示这些对象。因此,每次用户在屏幕上移动手指时,由触摸位置点定义的新线就会被添加到路径中,并且路径会在当前上下文中绘制。

注意,我们需要保留当前触摸位置和上一个位置的信息。这是因为AddLineToPoint方法接受一个点,该点定义了线的终点,假设路径中已经有一个点。每条线的起点是通过调用MoveToPoint,传递上一个触摸位置点来定义的。

通过在屏幕上滑动手指绘制的路径基本上由一系列连续的直线组成。然而,由于TouchesMoved方法在手指在屏幕上每次移动时都会被触发,因此结果是平滑的路径,它遵循手指的移动。

在将线添加到路径后,我们将其添加到上下文中并绘制它:

context.AddPath(this.drawPath);
context.DrawPath(CGPathDrawingMode.Stroke);

更多内容...

在本任务中引入了两个新的CGContext方法:SetLineJoinSetLineCapSetLineJoin方法设置每条线如何与前一条线连接,而SetLineCap设置线的端点外观。

它们接受的值解释如下:

  • SetLineJoin

    • CGLineJoin.Miter: 以斜角连接两条线

    • CGLineJoin.Round: 以圆角连接两条线

    • CGLineJoin.Bevel: 以方形端点连接两条线

  • SetLineCap

    • CGLineCap.Butt: 线将以端点上的方形边缘结束

    • CGLineCap.Round: 线将以圆角结束,并扩展到端点之外

    • CGLineCap.Square: 线将以扩展到端点之外的方形边缘结束

清除绘图

为了清除绘图,我们只需将fingerDraw变量设置为false并调用SetNeedsDisplay。这样,Draw方法将不会调用我们的自定义绘图代码,从而清除当前上下文。

参见

在本章中:

  • 绘制线条和曲线

  • 绘制形状

  • 绘制文本

创建图像上下文

在本食谱中,我们将通过为用户提供保存创建的绘图的功能来扩展我们之前创建的手指绘图应用程序。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为ImageContextApp。将我们在上一个任务中创建的CanvasView类添加到项目中。

如何操作...

  1. MainController的视图中添加两个按钮。一个将用于保存图像,另一个用于清除当前绘图。

  2. CanvasView类中添加以下方法:

    public UIImage GetDrawingImage(){
    UIImage toReturn = null;
    UIGraphics.BeginImageContext(this.Bounds.Size);
    using (CGContext context = UIGraphics.GetCurrentContext()){
    context.SetStrokeColorWithColor(UIColor.Blue.CGColor);
    context.SetLineWidth(10f);
    context.SetLineJoin(CGLineJoin.Round);
    context.SetLineCap(CGLineCap.Round);
    context.AddPath(this.drawPath);
    context.DrawPath(CGPathDrawingMode.Stroke);
    toReturn = UIGraphics.GetImageFromCurrentImageContext();
    }
    UIGraphics.EndImageContext();
    return toReturn;
    }
    public void ClearDrawing(){
    this.fingerDraw = false;
    this.drawPath.Dispose();
    this.drawPath = new CGPath();
    this.SetNeedsDisplay();
    }
    
    
  3. MainController类中添加以下代码:

    private CanvasView canvasView;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.canvasView = new CanvasView(new RectangleF( this.View.Bounds.Location, new SizeF(this.View.Bounds.Width, this.buttonClear.Frame.Top - 10f)));
    this.canvasView.BackgroundColor = UIColor.Gray;
    this.View.AddSubview(this.canvasView);
    this.buttonSave.TouchUpInside += delegate {
    UIImage drawingImage = this.canvasView.GetDrawingImage();
    drawingImage.SaveToPhotosAlbum(delegate( UIImage image, NSError error) {
    if (error != null){
    Console.WriteLine("Error saving image! {0}", error.LocalizedDescription);
    }
    } );
    } ;
    this.buttonClear.TouchUpInside += delegate {
    this.canvasView.ClearDrawing();
    } ;
    }
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 在画布上绘制一些东西,然后点击 保存绘图 按钮来保存你的绘图。

  6. 点击 清除 绘图按钮来清除画布。然后你可以检查模拟器的照片库以查看你的绘图。

工作原理...

使用 UIGraphics 类,我们可以创建一个图像上下文,通过这个上下文我们可以从 UIImage 对象中检索我们的绘图。

要创建图像上下文,在 GetDrawingImage 方法中我们调用 BeginImageContext 静态方法,传递我们想要图像上下文具有的大小:

UIGraphics.BeginImageContext(this.Bounds.Size);

当前上下文现在是我们使用 BeginImageContext 调用创建的图像上下文。然后我们重复 Draw 方法中的代码,只是这次不需要向路径中添加新线条。我们只需将已有的路径添加到上下文中并绘制它。

添加路径后,我们通过调用 GetImageFromCurrentContext 方法获取上下文图像:

toReturn = UIGraphics.GetImageFromCurrentImageContext();

最后,我们必须结束图像上下文块并返回 UIImage 对象:

UIGraphics.EndImageContext();
return toReturn;

要从屏幕上清除绘图,我们只需将 fingerDraw 变量设置为 false,并在 ClearDrawing 方法中处理并准备我们的 CGPath 对象以供重新使用:

this.fingerDraw = false;
this.drawPath.Dispose();
this.drawPath = new CGPath();

要立即在屏幕上反映清除操作,我们调用 SetNeedsDisplay 方法:

this.SetNeedsDisplay();

更多...

我们不能在 Draw 方法内部创建图像上下文。这是因为当我们调用 BeginImageContext 方法时,实际上创建了一个上下文,但视图的默认上下文仍然是当前上下文。因此,GetImageFromCurrentImageContext 方法会返回 null

UIImageView 上绘制

这里讨论的技术可以用来在自定义 UIImageViews 上绘制。要显示当手指在屏幕上滑动时的绘图,我们只需将其 Image 属性设置为从图像上下文中获取的图像。

保存绘图的背景信息

你会注意到,尽管我们将 CanvasView 的背景设置为灰色,但保存的绘图却是白色背景。这是因为视图的背景颜色不包括在绘图内。要包括它,我们只需将与背景颜色相同的矩形绘制到图形上下文中。

参见

在本章中:

  • 绘制线条和曲线

  • 绘制形状

  • 绘制文本

  • 简单绘图

第十二章。多任务

在本章中,我们将涵盖:

  • 检测应用程序状态

  • 接收应用程序状态的通知

  • 在后台运行代码

  • 在后台播放音频

  • 网络连接维护

简介

当 2007 年推出 iOS 平台时,为用户带来了许多令人兴奋的新功能,它彻底改变了移动设备的概念。

尽管当时它取得了巨大的成功,但它缺少了一些被认为是“基本”的功能。其中之一就是多任务处理;即同时运行多个进程的支持。实际上,该平台在内部支持多任务处理系统进程,但这对开发者来说并不可用。从 iOS 4 开始,苹果提供了对多任务处理的支持,尽管它与大多数开发者习惯的方式仍然有很大不同。

在本章中,我们将讨论如何利用平台的多任务功能。我们将了解在什么情况下可以使用这些功能,以及我们可以通过多任务为应用程序用户提供哪些功能。具体来说,我们将学习应用程序的状态及其运行时生命周期。通过一系列详细的示例项目,我们能够在应用程序后台执行代码,支持音频播放和 VoIP 连接维护。

检测应用程序状态

在本配方中,我们将讨论如何检测应用程序从活动状态到非活动状态以及相反状态的变化,并相应地做出反应。

准备就绪

在 MonoDevelop 中创建一个新的项目,并将其命名为 AppStateApp。在这个例子中不需要视图控制器。

如何操作...

  1. AppDelegate类中添加以下方法重写:

    public override void OnActivated (UIApplication application){
    Console.WriteLine("Activated, application state: {0}", application.ApplicationState);
    }
    public override void OnResignActivation (UIApplication application){
    Console.WriteLine("Resign activation, application state: {0}", application.ApplicationState);
    }
    public override void DidEnterBackground (UIApplication application){
    Console.WriteLine("Entered background, application state: {0}", application.ApplicationState);
    }
    public override void WillEnterForeground (UIApplication application){
    Console.WriteLine("Will enter foreground, application state: {0}", application.ApplicationState);
    }
    
    
  2. 在模拟器或设备上编译并运行应用程序。

  3. 按下主页按钮以挂起应用程序,并观察 MonoDevelop 中的应用程序输出面板。

它是如何工作的...

UIApplicationDelegate类包含由运行时发出的特定通知触发的方法。这些方法是:

  • OnActivated: 当应用程序变为活动状态时,会调用此方法,例如,当解锁屏幕或应用程序启动时。

  • OnResignActivation: 当应用程序即将变为非活动状态时,会调用此方法,例如,当屏幕锁定或显示多任务栏时。

  • DidEnterBackground: 当应用程序进入后台时,会调用此方法,例如,当按下主页按钮。应用程序被挂起。

  • WillEnterForeground: 当应用程序即将返回前台时,会调用此方法。

注意,当应用程序移动到后台时,会调用OnResignActivationDidEnterBackground方法。同样,当应用程序移动到前台时,会调用WillEnterForegroundOnActivated方法。

所有这些方法都包含一个参数,该参数包含应用程序的 UIApplication 实例。UIApplication 类包含一个属性 ApplicationState,它返回 UIApplicationState 属性中的应用程序状态值。这些值是:

  • Active: 此值表示应用程序处于活动状态

  • Inactive: 此值表示应用程序处于非活动状态,例如,当显示通知警报时

  • Background: 此值表示应用程序处于后台

还有更多...

多任务处理是 iOS 4+ 的一个功能,并且并非所有设备都支持多任务处理,即使它们运行在 iOS 4+ 上。对于 4 版本之前的版本,当按下 主页 按钮时,会调用 WillTerminate 方法:

public override void WillTerminate (UIApplication application){
Console.WriteLine("App will terminate!");
}

然而,在某些情况下,iOS 会终止您的应用程序;例如,当发出内存警告而您的应用程序没有释放资源时。在这些情况下,也会调用 WillTerminate 方法。

多任务处理支持

要检查设备是否支持多任务处理,请检查 UIDevice.CurrentDevice.IsMultitaskingSupported 属性。

正确使用

这些方法非常有用,因为它们允许我们在应用程序状态改变时保存向用户展示的当前数据。当应用程序过渡到非活动或后台状态时,每个方法都有一定的时间限制来执行,因此我们应该确保它们不执行长时间运行的操作,否则 iOS 会终止应用程序。

接收应用程序状态的通知

在本菜谱中,我们将讨论在 UIApplicationDelegate 实现范围之外接收应用程序状态变化的通知。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 NotifyStatesApp。向项目中添加一个带有控制器的视图,并将其命名为 MainController

如何操作...

  1. MainController 类中输入以下字段:

    private NSObject didEnterBgdObserver;
    private NSObject willEnterFgdObserver;
    
    
  2. 创建以下方法:

    private void AddAppStateObservers(){
    this.didEnterBgdObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIApplication.DidEnterBackgroundNotification, delegate(NSNotification obj) {
    Console.WriteLine("App entered background, app state: {0}", UIApplication.SharedApplication.ApplicationState);
    } );
    this.willEnterFgdObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIApplication.WillEnterForegroundNotification, delegate(NSNotification obj) {
    Console.WriteLine("App will enter foreground, app state: {0}", UIApplication.SharedApplication.ApplicationState);
    } );
    }
    private void RemoveAppStateObservers(){
    NSNotificationCenter.DefaultCenter.RemoveObservers(new NSObject[] { this.didEnterBgdObserver, this.willEnterFgdObserver });
    }
    
    
  3. ViewDidLoad 覆盖中,调用 AddAppStateObservers 方法:

    this.AddAppStateObservers();
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 按下 主页 按钮,并观察 应用程序输出 面板中的输出。它应该类似于以下截图:

如何操作...

它是如何工作的...

除了调用 UIApplicationDelegate 对象的应用程序状态方法外,iOS 还会发出我们可以接收的通知。这非常有用,因为在大多数情况下,我们需要在 AppDelegate 类的范围之外接收应用程序状态变化的通知。

要实现这一点,我们使用 NSNotificationCenter:

this.didEnterBgdObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIApplication.DidEnterBackgroundNotification, delegate(NSNotification obj) {
Console.WriteLine("App entered background, app state: {0}", UIApplication.SharedApplication.ApplicationState);
} );

我们感兴趣的通知密钥通过 UIApplication 静态属性公开。此示例仅添加了背景和前台之间的转换通知观察者。

结果类似于前一个菜谱的示例,但仅在 MainController 加载后。

还有更多...

要为应用程序激活或停用激活时添加通知观察者,我们分别使用 UIApplication.DidBecomeActiveNotificationUIApplication.WillResignActiveNotification 键。

移除通知观察者

当不再需要通知观察者时,在 ViewDidUnload 覆盖方法中调用 RemoveAppStateObservers 方法:

this.RemoveAppStateObservers();

参见

在本章中:

  • 检测应用程序状态

在后台运行代码

在本食谱中,我们将学习如何在后台执行代码,充分利用 iOS 的多任务功能。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为 BackgroundCodeApp。在这个例子中不需要视图控制器。

如何做到这一点...

  1. AppDelegate 类中输入以下代码:

    private int taskID;
    public override void DidEnterBackground (UIApplication application){
    if (UIDevice.CurrentDevice.IsMultitaskingSupported && this.taskID == 0){
    this.taskID = application.BeginBackgroundTask(delegate {
    application.EndBackgroundTask(taskID);
    this.taskID = 0;
    } );
    ThreadPool.QueueUserWorkItem(delegate {
    for (int i = 0; i < 60; i++){
    Console.WriteLine("Task: {0} - Current Time: {1}", this.taskID, DateTime.Now);
    Thread.Sleep(1000);
    }
    application.EndBackgroundTask(this.taskID);
    this.taskID = 0;
    } );
    }
    }
    public override void WillEnterForeground (UIApplication application){
    if (this.taskID != 0){
    Console.WriteLine("Background task is running!");
    } else{
    Console.WriteLine("Background task completed!");
    }
    }
    
    
  2. 在模拟器上编译和运行应用程序。

  3. 按下主页按钮使应用程序进入后台,并观察应用程序输出

  4. 在后台任务完成之前(一分钟),可以通过在多任务栏中点击其图标或在其主页屏幕上的图标来将应用程序带到前台。

它是如何工作的...

在前面的任务中,我们学习了如何得知应用程序从前台到后台以及相反的转换。

iOS 上的多任务处理并不完全像我们在其他平台上所习惯的那样。iOS 平台确保前台应用程序将拥有所有可用的资源(以及用户的)。为了实现这一点,当应用程序进入后台时,它会被操作系统挂起。当它被挂起时,它不会执行任何代码。

如果我们想要防止用户按下主页按钮时应用程序被挂起,我们可以请求后台时间。我们请求的时间限制为 600 秒(10 分钟),这对于我们可能在后台执行的大多数任务来说已经足够了(例如,保存 UI 状态,完成文件下载/上传,关闭任何打开的连接,等等)。

要请求后台时间,我们调用我们的 UIApplication 实例的 BeginBackgroundTask 方法:

this.taskID = application.BeginBackgroundTask(delegate {
application.EndBackgroundTask(taskID);
this.taskID = 0;
} );

该方法接受一个类型为 NSAction 的参数,并返回一个整数,该整数对应于任务 ID。NSAction 参数表示在后台时间结束前将要执行的代码块。在这个代码块内部,我们必须调用 EndBackgroundTask 方法,传递已启动的任务 ID,这将通知运行时我们不再需要后台时间。每次调用 BeginBackgroundTask 都应该跟随一个调用 EndBackgroundTask。如果我们不调用此方法并且后台时间结束,应用程序将被终止。

在调用BeginBackgroundTask方法后,我们可以执行我们想要的代码。为了允许DidEnterBackground方法完成并避免阻塞主线程,我们只需将我们的代码封装到异步调用中,或者在一个单独的线程中。在这个例子中,我们使用ThreadPool中的线程。由于这个特定任务将在我们设定的超时时间之前完成,我们调用EndBackgroundTask方法来让系统知道工作已完成。我们传递给BeginBackgroundTask方法的代码块将不会执行,因为我们已经结束了任务。

然而,也有可能用户在后台任务仍在运行时将应用程序带到前台。为了覆盖这种情况,我们需要重写WillEnterForeground方法并适当地处理它。我们可以停止后台任务(通过调用EndBackgroundTask),或者向用户提供某种反馈,表明任务仍在运行。在这种情况下对我们的代码进行异步调用是最佳实践。如果我们的后台任务代码是同步的,当用户将应用程序带到前台且任务仍在运行时,应用程序将冻结,直到任务完成。

更多...

要知道执行后台任务剩余多少时间,我们可以检查BackgroundTimeRemaining属性的值:

Console.WriteLine("Remaining time: {0}", application.BackgroundTimeRemaining);

后台代码的重要注意事项

  • 不要在应用程序后台更新 UI:这样做可能会导致您的应用程序被终止或崩溃。在应用程序后台发生的任何 UI 元素更新都将排队,在它返回前台时执行。这肯定会使得应用程序无响应。

  • 不要通知用户将您的应用程序带到前台,只是给任务更多时间:这样做肯定会使得您的应用程序在应用商店的审批过程中被拒绝。如果一个后台任务正在进行,而用户将应用程序带到前台,再次将应用程序移回后台基本上会重置后台时间。

  • 在后台执行轻量级操作以避免运行时杀死您的应用程序。

  • 避免使用外部资源(例如,通过资源库检索的资源)。

参见

在本章中:

  • 检测应用程序状态

在后台播放音频

在这个菜谱中,我们将学习如何防止应用程序挂起,以便允许音频播放。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为BackgroundAudioApp。在MainController的视图中添加一个按钮。

如何做到...

  1. 打开Info.plist文件,并添加键UIBackgroundModes

  2. 在其下添加音频项。添加MonoTouch.AVFoundation命名空间。

  3. MainController类中输入以下代码:

    private AVAudioPlayer audioPlayer;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    NSError error = null;
    AVAudioSession.SharedInstance ().SetCategory (AVAudioSession.CategoryPlayback, out error);
    if (error != null){
    Console.WriteLine("Error setting audio session category: {0}", error.LocalizedDescription);
    }
    this.audioPlayer = AVAudioPlayer.FromUrl(NSUrl.FromFilename ("audio/sound.m4a"));
    this.buttonStart.TouchUpInside += delegate {
    this.audioPlayer.Play();
    } ;
    }
    
    
  4. 将声音文件添加到项目中,并将其构建操作设置为内容。本例使用一个名为sound.m4a的 20 秒长声音文件。

  5. 在设备上编译并运行应用程序。

  6. 点击开始播放按钮,然后按主页按钮使应用程序进入后台。注意,声音仍在播放。

它是如何工作的...

为了确保我们的应用程序在后台播放音频时能够工作,我们必须在Info.plist文件中的UIBackgroundModes键中设置音频项。

在这个例子中,我们使用AVAudioPlayer类来播放声音文件。然而,仅仅创建类的实例并调用其Play方法是不够的。我们必须为音频会话类别设置一个特定的类型:

NSError error = null;
AVAudioSession.SharedInstance ().SetCategory (AVAudioSession.CategoryPlayback, out error);

静态方法AVAudioSession.SharedInstance返回当前的音频会话对象。音频会话类别设置为AVAudioSession.CategoryPlayback,这允许AVAudioPlayer在应用程序处于后台时播放声音。这个要求是针对MonoTouch.AVFoundation命名空间中的对象。

还有更多...

可用的音频会话类别如下:

  • CategoryAmbient: 在这个类别中,当设备屏幕锁定或设备静音开关开启时,声音会被静音。来自外部资源(如 iPod 应用程序)的声音会与这个类别混合。

  • CategorySoloAmbient: 这是默认类别。使用此类别,来自外部资源的声音会被静音。当设备屏幕锁定或设备静音开关开启时,声音会被静音。

  • CategoryPlayback: 在这个类别中,当屏幕锁定或静音开关开启时,声音不会被静音。来自外部资源的声音会被静音,但如果将MonoTouch.AudioToolbox.AudioSession.OverrideCategoryMixWithOthers属性设置为true,则可以进行混合。

  • CategoryRecord: 这个类别用于录音。所有音频播放都会被静音。即使屏幕锁定,录音也会继续。

  • CategoryPlayAndRecord: 这个类别适用于需要录音和播放音频的应用程序。来自外部资源的声音会被静音,但如果将MonoTouch.AudioToolbox.AudioSession.OverrideCategoryMixWithOthers属性设置为true,则可以进行混合。当屏幕锁定或静音开关开启时,声音会继续播放。

  • CategoryAudioProcessing: 这个类别专门用于处理音频。声音播放和录音被禁用。

音频的背景状态

即使应用程序通过Info.plist文件配置为支持后台音频播放,当播放完成后,应用程序将被挂起。

参见

在本章中:

  • 网络连接维护

在本书中:

第十章, 位置服务和地图:

  • 后台位置服务

网络连接维护

在本食谱中,我们将学习如何定期唤醒应用程序以执行网络连接检查。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为NetCheckApp。在这个示例中不需要视图控制器。

如何操作...

  1. AppDelegate类中添加以下DidEnterBackground重写方法:

    public override void DidEnterBackground (UIApplication application){
    application.SetKeepAliveTimeout(610, delegate {
    Console.WriteLine("App woken up for network connection maintenance!");
    } );
    }
    
    
  2. Info.plist文件中添加UIBackgroundModes键,并包含项目voip

它是如何工作的...

苹果提供了这项多任务功能,允许使用互联网协议语音(VoIP)通信的应用程序与适当的服务器进行周期性的网络连接检查。为了允许应用程序为此功能唤醒,请调用UIApplication类的SetKeepAliveTimeout方法:

application.SetKeepAliveTimeout(610, delegate {
Console.WriteLine("App woken up for network connection maintenance!");
} );

第一个参数是应用程序将被唤醒的时间间隔(以秒为单位)。允许的最小间隔是 600 秒(10 分钟)。设置低于最小值的间隔将导致方法失败,并且应用程序将被挂起。第二个参数是在间隔即将结束时将要执行的处理程序。此处理程序只有 30 秒的时间执行。如果它需要超过 30 秒,则应用程序将被终止。

更多内容...

在保持活动处理程序中可用于网络连接的对象是NSInputStream, NSOutputStreamNSUrlRequest

合并 UIBackgroundModes 键的项目

应用程序可以使用UIBackgroundModes键的任何组合或所有可用项。然而,避免添加与预期功能不同的后台模式。在这种情况下,您的应用程序可能会被应用商店拒绝。

相关内容

在本章中:

  • 后台播放音频

在本书中:

第十章, 位置服务和地图:

  • 后台位置服务

第十三章。本地化

在本章中,我们将介绍:

  • 为不同语言创建应用程序

  • 可本地化资源

  • 区域格式化

简介

随着 iOS 平台和以应用程序商店形式出现的全球软件市场的发布,苹果公司使开发者更容易在全球范围内分发应用程序。

但是,全球用户甚至不会费心下载和使用以他们不理解的语言发布的应用程序。为了扩大其应用程序的用户基础,开发者必须对其进行本地化。本地化是将文本翻译成多种语言,提供针对多个区域的具体资源,从而创建面向不同文化受众的应用程序的过程。

在本章中,我们将讨论提供翻译文本的最佳实践,这些文本将根据每个用户的区域设置偏好显示。我们还将了解如何根据这些偏好提供资源(图像、视频)。最后,我们将使用常见的.NET 实践来格式化日期、货币和数字。

为不同语言创建应用程序

在本食谱中,我们将创建一个支持两种不同语言的应用程序。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为MultipleLanguageApp

如何操作...

  1. MainController的视图中添加两个标签。

  2. 向项目添加两个文件夹。分别命名为en.lprojes.lproj

  3. 使用文本编辑器应用程序创建两个文本文件。在第一个文件中输入以下文本:

    // Localized output on MainController
    "Have a nice day!" = "Have a nice day!";
    
    
  4. 将其保存为 en.lproj 文件夹中的 Localizable.strings。

  5. 在第二个文件中输入以下文本:

    // Localized output on MainController
    "Have a nice day!" = "Tenga un buen día!";
    
    
  6. 这次将文件以相同的名称,Localizable.strings,保存在es.lproj文件夹中。将两个文件的构建操作设置为内容

    注意

    Localizable.strings文件必须以UTF8UTF16编码保存。

  7. MainController类中输入以下代码:

    public override void ViewWillAppear (bool animated){
    base.ViewWillAppear (animated);
    this.lblLocale.Text = string.Format("Locale: {0} - Language: {1}", NSLocale.CurrentLocale.LocaleIdentifier, NSLocale.PreferredLanguages[0]);
    string resourcePath = NSBundle.MainBundle.PathForResource( NSLocale.PreferredLanguages[0], "lproj");
    NSBundle localeBundle = NSBundle.FromPath(resourcePath);
    this.lblLocalizedOutput.Text = localeBundle.LocalizedString("Have a nice day!", "Localized output on MainController");
    }
    
    
  8. 通过模拟器的设置应用程序,将语言设置为英语(如果尚未设置),然后运行应用程序。消息将以英语显示。尝试将模拟器的语言设置为西班牙语,然后再次运行应用程序。消息将以西班牙语显示。

它是如何工作的...

为了使开发者更容易在应用程序中提供对多种语言的支持,iOS 从相应的语言文件夹中读取不同语言文本。在本应用程序中,我们支持英语和西班牙语。它们相应的文件夹分别是en.lprojes.lproj。当我们调用LocalizedString方法时,它会查找并解析Localizable.strings文件以返回适当的文本。

字符串文件的 内容由一组带引号的键/值对定义,采用 C 语言风格语法,每个集合以分号结尾:

// Localized output on MainController
"Have a nice day!" = "Have a nice day!";

如您所见,我们还可以提供注释以协助翻译文本的人的工作,即使我们自己翻译。

NSLocale.PreferredLanguages静态属性返回一个包含用户首选语言标识符的字符串数组。这个数组中的第一个项目是当前选定的语言。如果选定的语言是英语,它将返回en;如果是西班牙语,它将返回es,依此类推。

NSBundle.PathForResource方法返回我们传递给它的参数的应用程序包路径。我们使用这个路径来获取适当的NSBundle实例,根据选定的语言:

string resourcePath = NSBundle.MainBundle.PathForResource( NSLocale.PreferredLanguages[0], "lproj");
NSBundle localeBundle = NSBundle.FromPath(resourcePath);

然后我们调用LocalizedString方法来显示适当的文本:

this.lblLocalizedOutput.Text = localeBundle.LocalizedString("Have a nice day!", "Localized output on MainController");

第一个参数的目的是双重的。它不仅是查找以返回翻译文本的关键,而且在指定的本地化路径未找到时,它还将显示为文本。第二个参数是注释,或者我们想要提供给翻译者的任何指令。它不会显示并且基本上不会被使用。我们可以传递null给这个参数,并且不会发生错误。然而,始终包含注释或指令是明智的,因为它将有助于在翻译多个字符串时避免混淆。

还有更多...

建议始终提供可以作为在英语中显示的回退文本的键,以防用户选定的语言不包括在我们的应用程序中。

然而,LocalizedString方法有多个重载。第二个重载接受三个参数。考虑以下示例:

this.lblLocalizedOutput.Text = localeBundle.LocalizedString("Have a nice day!", "Have a nice day!", "Localizable");

第一个参数是查找的关键。第二个参数是回退值,以防指定的本地化路径未找到。第三个参数是包含字符串的文件的名称,不包括.strings扩展名。这个重载更有帮助,我们可以为我们的字符串使用不同的键,这有助于我们识别特定字符串在代码中的使用位置。例如,在这种情况下,我们可以在字符串文件中将键设置为MainController.lblLocalizedOutput:

// Localized output on MainController
"MainController.lblLocalizedOutput" = "Have a nice day!";

在我们的代码中使用它如下:

this.lblLocalizedOutput.Text = localeBundle.LocalizedString( "MainController.lblLocalizedOutput", "Have a nice day!", "Localizable");

这个重载还帮助我们将字符串分成多个.strings文件,通过将相应的文件名作为参数#3 传递。

最后一个重载包含四个参数。前三个与第二个重载相同。第四个参数只是我们想要特定字符串拥有的注释。

实际应用场景中的本地化

在这个示例中,我们使用PathForResource方法获取当前区域设置包的实例。这是因为从LocalizedString方法返回的值被缓存了。在实际应用场景中,如果应用程序以特定语言下载,并且用户最有可能永远不会更改设备语言来使用它,只需调用NSBundle.MainBundle.LocalizedString就足够了。

可本地化资源

可本地化资源是内容,例如图像、声音文件等,它针对特定区域。在本食谱中,我们将学习如何根据用户的本地化首选项加载和显示资源。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为LocalizableResourcesApp。在MainController的视图中添加一个标签和一个UIImageView

如何操作...

  1. 在项目中添加两个用于英语西班牙语区域的文件夹。在每个文件夹中添加一个具有相同文件名的图像。将它们的构建操作设置为内容

  2. MainController类中输入以下代码:

    public override void ViewWillAppear (bool animated){
    base.ViewWillAppear (animated);
    this.lblLanguage.Text = NSLocale.PreferredLanguages[0];
    this.imageView.Image = UIImage.FromFile(NSBundle. MainBundle.PathForResource("flag", "jpg"));
    }
    
    
  3. 在模拟器上编译并运行应用程序,在设置应用程序中选择英语语言。结果应该类似于以下截图:如何操作...

  4. 现在,将模拟器的语言设置为西班牙语,并再次运行应用程序。应该显示西班牙国旗:

如何操作...

它是如何工作的...

PathForResource方法会自动搜索适当的语言文件夹,并通过其参数加载指定的资源。在这个例子中,我们将方法的结果传递给UIImage.FromFile方法来加载图像,并将其分配给图像视图的Image属性。

还有更多...

除了图像外,我们还可以使用PathForResource方法来加载视频、PDF 文件以及任何其他需要本地化的资源。

更多关于可本地化资源的信息

我们需要确保特定语言的文件夹中的资源存在。如果不存在,将会抛出异常。避免这种情况的一种方法是,在项目中添加一个通用的图像文件,并在每个语言文件夹中添加一个Localizable.strings文件,其中包含资源的路径:

// US flag image
"flag_path"="en.lproj/flag.jpg";

为了加载适当的国旗,我们使用LocalizedString方法加载图像:

this.Image = UIImage.FromFile(NSBundle.MainBundle.LocalizedString( "flag_path", "path/to/universal/image.jpg", "Localizable");

这样,如果找不到相应的语言文件夹,将加载图像image.jpg

相关内容

在本章中:

  • 为不同语言创建应用程序

区域格式化

区域格式化是指根据世界各地的不同区域显示各种信息,如货币、日期和时间等。在本食谱中,我们将讨论如何根据用户的区域格式设置显示格式化的数字和日期。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为RegionalFormattingApp

如何操作...

  1. MainController的视图中添加五个标签。在MainController类中输入以下代码:

    public override void ViewDidAppear (bool animated){
    base.ViewDidAppear (animated);
    this.lblLocale.Text = string.Format("Locale: {0}", NSLocale.CurrentLocale.LocaleIdentifier);
    this.lblDate.Text = string.Format("Date: {0}", DateTime.Now.ToLongDateString());
    this.lblTime.Text = string.Format("Time: {0}", DateTime.Now.ToLongTimeString());
    this.lblCurrency.Text = string.Format("Currency: {0:c}", 250);
    this.lblNumber.Text = string.Format("Number: {0:n}", 1350);
    }
    
    
  2. 在模拟器上编译并运行应用程序,将区域格式设置为美国和西班牙 | 西班牙,在设置 | 通用 | 国际 | 区域格式下。具有两种不同区域格式的输出将类似于以下截图:

如何操作...如何操作...

它是如何工作的...

要格式化日期、货币和数字,我们使用标准的 .NET 代码。对于日期和时间,DateTime.ToLongDateStringDateTime.ToLongTimeString 方法分别根据区域设置返回值。

对于货币和数字,我们使用 C# 数值字符串:

this.lblCurrency.Text = string.Format("Currency: {0:c}", 250);
this.lblNumber.Text = string.Format("Number: {0:n}", 1350);

还有更多...

System.Globalization 命名空间在 MonoTouch 中受支持。要显示当前区域设置,请考虑以下代码行:

Console.WriteLine(CultureInfo.CurrentCulture.Name);

注意,此代码与 NSLocale.CurrentLocale.LocaleIdentifier 之间有一个区别。前者使用破折号 (-),而后者在区域名称中使用下划线 (_)。

第十四章:部署

在本章中,我们将涵盖:

  • 创建配置文件

  • 创建一个临时的分发包

  • 为 App Store 准备应用

  • 提交到 App Store

简介

将应用程序部署到设备或 App Store 的过程被认为相当复杂。当然,这也是为了开发者的利益,因为需要各种证书和配置文件才能将应用程序包从开发 Mac 传输到设备。

在本章中,我们将详细介绍在开发计算机上准备和安装适当证书的所有必要步骤。我们还将学习如何创建配置文件,这将允许我们将应用程序部署到设备上,无论是我们的设备还是发送给测试人员安装到他们的设备上。

最后,我们将了解如何为 App Store 提交准备应用程序以及最终发布到 App Store 的流程。

创建配置文件

在本食谱中,我们将逐步指导您创建和安装必要的证书和配置文件,这些证书和配置文件对于将应用程序部署到设备上是必需的。

如何操作...

以下步骤将指导您创建应用程序的开发者证书和适当的配置文件。

我们将从开发者证书开始。

  1. 登录到 iOS 开发者网站:developer.apple.com/ios

  2. 前往 iOS 配置文件门户。

  3. 从左侧菜单中选择 Certificates

  4. 如果这是您第一次在 Mac 上使用开发者证书,请下载并安装 WWDR 中间证书。您可以在 Development 选项卡下找到链接。

  5. How To 选项卡下,您将找到创建证书签名请求的说明,这是颁发您的开发者证书和下载安装它的必要条件。

  6. 如果开发者证书已正确安装,您将在 MonoDevelop 项目的选项中选中它,在 Identity 下拉列表中的 iPhone Bundle Signing 节点下:

如何操作...

  • 您可以在 iOS Provisioning PortalCertificates 选项下的 Development 选项卡中查看您的开发者证书。

现在我们已经颁发并安装了我们的开发者证书,我们需要注册我们将用于调试的设备。

  1. 点击左侧菜单中的 Devices 选项。

  2. Manage 选项卡下,点击 Add Devices 按钮。

  3. Device Name 字段中,输入一个可以识别特定设备的名称(例如,我的 iPhone)。

  4. 设备 ID字段中,输入设备的唯一设备标识符(UDID)。您可以通过将设备连接到您的 Mac 并打开iTunes来找到设备的 UDID。在设备的摘要选项卡下,点击序列号将切换到 UDID。按Command + C将 UDID 复制到剪贴板。

  5. 点击加号(+)按钮,然后提交。为要添加的所有设备重复所有步骤。

接下来,我们需要一个App ID

  1. 点击左侧菜单上的App IDs选项。

  2. 管理选项卡下,为您的应用程序输入描述/名称。不要使用特殊字符和空格。

  3. Bundle Seed ID (App ID Prefix)选项保留为生成新

  4. 输入包标识符。包标识符的最佳实践是遵循字段上方给出的示例和建议。包标识符很重要,因为在部署过程的至少一个步骤中您将需要它。

  5. 点击提交按钮以创建 App ID。

接下来,是配置文件的时间。

  1. 点击左侧菜单上的配置选项。

  2. 开发选项卡下,点击新建配置文件按钮。

  3. 输入配置文件名称。您可以在此字段中输入任何字符。

  4. 证书选项中,选择配置文件的开发者证书。如果您已成功创建您的开发者证书,您的姓名(或 iOS 开发者账户所属者的姓名)将显示在旁边的复选框旁边。勾选复选框。

  5. App ID选项中,选择您正在创建的配置文件的App ID

  6. 设备选项中,勾选将包含在配置文件中的设备(们)。您的应用程序只能安装在此处选择的设备上。

  7. 点击提交按钮以完成配置文件的创建。

  8. 如果列表中配置文件的状态为挂起,只需刷新页面。

  9. 点击您配置文件所在行的下载按钮以下载它。将下载一个扩展名为 .mobileprovision 的文件。

  10. 将您的设备连接到您的 Mac。如果已打开,请关闭iTunes

  11. 双击您在步骤 9中下载的.mobileprovision文件。Xcode 将打开并在您的设备上安装配置文件。您可以通过在 Xcode 的组织者窗口中查看配置文件状态来检查此操作。要显示组织者窗口,按Shift + Command + 2,或从菜单中选择窗口 | 组织者。您可以通过在左侧选择配置选项来查看每个设备的配置文件:

如何操作...

带有红色X标记的配置文件表示它们已过期。

它是如何工作的...

本食谱中描述的过程将允许您在连接到您的 Mac 的设备上部署和调试您的应用程序。它不会允许您将应用程序分发给测试人员或 App Store。

开发者证书是允许编译将部署到设备上的应用的证书。它仅用于开发,并且一个开发者证书对应一个 iOS 开发者计划注册。在创建并安装到开发机器上之后,您无法再发行新的证书。然而,现有的开发者证书可以被转移到其他 Mac 上。

每个配置文件包含有关它可以安装到哪些设备的信息。注册到 iOS 开发者计划的 Apple 开发者可以添加最多 100 个设备并将它们包含在配置文件中。

App ID 是您应用的标识符。为您的每个应用创建一个 App ID。

配置文件是允许您的应用在设备上部署的电子签名。每个配置文件对应一个应用,并包含所有允许应用在包含在内的设备上执行以及 App ID 信息的适当权限。它也是区分开发或分发应用的关键。配置文件会附带一个过期时间。在撰写本文时,大约是一年。

还有更多...

要在设备上编译和调试应用,请在 MonoDevelop 的项目选项下的iPhone 包签名节点中选择开发者证书和配置文件:

还有更多...

这必须为每个构建配置(调试、发布等)执行。

iPhone 应用节点下,为您的应用设置显示名称、包标识符包版本字段。如果您留空,MonoDevelop 将设置它们的默认值。具体来说,包标识符将被设置为包含在 App ID 中的那个。然而,如果您将包标识符设置为与 App ID 中声明的内容不同的内容,编译时将发生错误。

还有更多...

配置文件过期

当配置文件过期时,应用将无法在设备上工作。您可以续订现有的配置文件或创建一个新的配置文件并将其重新安装到设备上。

参见

在本章中:

  • 创建临时分发包

在本书中:

第一章,

  • 编译

创建临时分发包

在这个菜谱中,我们将学习如何创建我们的应用包,这样我们就可以将其发送给测试人员,让他们在自己的设备上进行测试。

准备工作

要创建一个临时的分发包,请确保您已经在 iOS 配置文件门户为您的应用创建了一个 App ID。

如何操作...

创建临时配置文件的过程与创建开发分发配置文件的过程类似。以下步骤将指导您完成这个过程。

  1. 分发证书:为了将应用程序分发到未连接到您的 Mac 的各种设备,以及提交到应用商店,您需要一个分发证书进行安装。遵循之前任务中描述的创建开发者证书的相同步骤。不过,这次您需要在iOS 配置文件门户证书菜单选项下的分发选项卡下进行操作。

  2. 临时分发配置文件:

    • iOS 配置文件门户中,转到配置 | 分发

    • 分发方法字段中,选择临时

    • 为配置文件输入一个配置文件名称。为了将来参考,最好在名称中添加单词AdHoc,例如,MyAppAdHoc

    • 分发证书将自动选中。

    • 选择App ID

    • 选择配置文件中的设备。

    • 点击提交以创建配置文件。

    • 从显示的列表中下载配置文件。再次点击,你将得到一个 .mobileprovision 文件。双击它以允许 Xcode 安装它。此时不需要连接设备。不要删除文件;我们稍后会用到它。

  3. 创建临时构建:

    • 在 MonoDevelop 中加载你的项目后,转到项目 | 解决方案选项

    • 构建 | 配置下,通过点击添加创建一个新的配置。

    • 为配置输入一个名称。这里使用的名称是 MyAppDistribution

    • 平台选项中选择iPhone。以下截图显示了新配置对话框应该如何显示:如何操作...

    • 确保已勾选“为所有解决方案项创建配置”选项。

    • 点击确定按钮。

    • 将一个新的属性列表文件添加到项目中,并将其命名为Entitlements如何操作...

    • MonoDevelop 将自动使用属性列表编辑器加载 Entitlements.plist 文件。添加键 get-task-allow,并将其类型设置为布尔值。将其值设置为

    • 在项目选项的iPhone Bundle Signing节点下,选择之前创建的配置(MyAppDistribution)。将自定义权限字段设置为之前步骤中创建的 Entitlements.plist 文件。它可以通过点击字段旁边的浏览 ()按钮轻松找到。

    • 在同一窗口中设置分发配置文件和适用于临时分发的适当配置文件。

    • 将项目的当前配置设置为之前创建的分发配置(MyAppDistribution)。

    • 构建项目。

      应用程序现在已准备好进行临时分发!

  4. 分发给测试人员:

    • 打开Finder,导航到项目中的 bin 文件夹。

    • 打开iPhone/MyAppDistribution文件夹。通过右键单击并选择Compress "MyApp"(或应用的实际名称)使用 OS X 的默认压缩工具压缩应用程序包。应用程序包是一个显示以下图标的文件夹:如何操作...

    • 将压缩的应用程序包以及.mobileprovision文件发送给您的测试员。

    • 为了安装应用,您的测试员需要从压缩归档中提取应用程序包。

    • 提取后,将文件拖放到iTunesApps部分,并附带.mobileprovision文件,即可将其导入到iTunes Library。请注意,应用将在iTunes中显示默认图标。如果你没有为应用设置任何图标,这对于临时分发来说是正常的。

    • iTunes中同步设备。

    • 如果测试员在 Windows 机器上使用iTunes,请指示他们不要使用默认的解压缩工具,而应使用第三方应用程序。

它是如何工作的...

为了分发应用,我们需要一个分发证书。就像开发者证书一样,分发证书一旦创建,就可以在需要时转移到另一台 Mac 上。

临时分发配置文件的创建过程与创建开发配置文件的过程相同。唯一的区别是我们有选择分发类型的选项,可以是App StoreAd Hoc

我们创建的配置不是必需的,但它有助于我们更好地组织构建过程。它还使我们免于为每个构建设置不同的配置文件和设置。

Entitlements.plist文件以及get-task-allow键用于防止应用在启动时尝试寻找调试器进行连接。

还有更多...

MonoDevelop 提供了一个直接创建和压缩应用程序包的选项。在菜单中选择项目 | 压缩应用程序包...,然后选择输出位置和文件名,它将编译并压缩应用程序包。

使用 iTunes 同步临时应用程序包

不同用户在他们的 iTunes 应用中设置了不同的设置。如果用户同步设备但无法在设备上找到应用,请确保在iTunes中选中设备的Apps标签页下的应用以进行同步。

相关内容

在本章中:

  • 创建配置文件

准备应用提交到 App Store

在这个菜谱中,我们将讨论为准备应用提交到 App Store 所需的重要步骤。

准备工作

按照前一个菜谱中的步骤为您的应用创建 App Store 分发配置文件。

如何操作...

准备应用提交到 App Store 的一个非常重要的步骤是确定应包含在您应用中的图像。

  • 应用图标:这是将在用户设备上代表你的应用程序的图标。对于 iPhone 和 iPod Touch,为低分辨率屏幕准备 57x57 像素的图标,并为高分辨率屏幕(例如,iPhone 4 及更新版本)准备 114x114 像素的图标。对于 iPad,图标大小应为 72x72 像素。

    将图标文件保存为 PNG 格式。你可以随意命名它们,但保持一致的命名方案是一个好习惯,例如,Icon-57.png, Icon-114.png 等等。

    将图像文件添加到项目中。打开项目选项对话框,转到 iPhone Application 节点下的 Summary 选项卡中的 iPhone Icons 部分,如图所示。

如何操作...

  • 点击适当的按钮以找到图标文件并分配它。请注意,将打开的图像选择对话框仅查找项目中包含的文件。因此,请确保首先将你的图像添加到项目中。无需将它们的 Build Action 设置为 Content。点击 OK 按钮以保存对项目的更改。

  • 启动图像:启动图像是应用程序启动时首先显示的内容。为 iPhone 和 iPod Touch 应用程序准备至少两个维度的启动图像:320x480 像素用于低版本,640x960 像素用于高版本。分别将文件命名为 Default.pngDefault@2x.png,并将它们添加到项目中。将它们的 Build Action 设置为 Content

    如果你的应用程序是通用型且因此可以被 iPad 用户下载,或者它是一个仅限 iPad 的应用程序,那么你应该为应用程序支持的每个方向提供启动图像。对于纵向版本,大小应为 768x1004 像素,对于横向版本,大小应为 1024x748 像素。对于支持纵向和横向右方向的应用程序,文件名分别应为 Default-Portrait.png 和 Default-LandscapeRight.png。

  • 最终设置:最后但同样重要的是,在 project 选项中的 iPhone Application 节点的 Summary 选项卡中填写适当的应用程序信息,如本章开头所述的 Creating Profiles 菜单中所述。构建项目并压缩应用程序包。你的应用程序已准备好提交!

它是如何工作的...

应用程序图标非常重要。这是用户将在设备屏幕上看到并点击以启动您的应用程序的内容。尽管所有应用程序图标都显示为具有圆角和光泽效果的按钮,但您不应在图标中包含这些图形特征。这些图形特征在提交到应用商店时自动渲染。图标应该是完美的正方形。此外,始终为图标提供背景。不要使用透明度,因为图标上的任何透明度都将显示为黑色,可能会破坏您预期的图标外观。

当应用程序启动时,首先显示的是启动图像。如果启动时屏幕变黑,这意味着没有启动图像。根据苹果的iOS 人机界面指南(developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/MobileHIG/Introduction/Introduction.html),这个图像应该是应用程序完成启动过程并准备好接受输入时的第一个屏幕。它应该只包含第一个屏幕的静态内容,而不是可能改变的内容,例如本地化文本。例如,以下截图展示了 g-force 测量应用 GBox 的启动图像。

如何工作...

以下截图显示了应用程序完全加载后的样子。

如何工作...

更多内容...

启动图像的存在是为了在应用程序加载时给用户一种响应感,避免空白屏幕。尽管iOS 人机界面指南正如其名所示,是一份指南,但遵循它们是一个好的实践。苹果建议避免在启动画面、"关于"信息和品牌中使用启动图像。

图标上的灯光效果

如果我们想要为图标提供自己的灯光效果,或者甚至不允许显示默认的 iOS 效果,我们可以在Info.plist文件中添加UIPrerenderedIcon键,并将其类型设置为布尔值并启用它。此设置防止在设备主屏幕上显示应用程序图标时创建光泽效果。

参见

在本章中:

  • 创建配置文件

提交到 App Store

在这个菜谱中,我们将介绍提交应用程序到 App Store 所需的步骤。

准备工作

对于这个任务,您需要准备好您的压缩分发应用程序包。

如何操作...

按照以下步骤提交您的应用程序到 App Store。

  1. 截图:准备显示您应用程序各个方面的截图。对于 iPhone/iPod Touch 应用程序,肖像模式的尺寸应为320x480,横屏模式的尺寸应为480x320。对于 iPad 应用程序,截图的尺寸应为肖像模式的768x1024和横屏模式的1024x768。如果应用程序没有隐藏状态栏,最好将其包含在截图内。对于每个应用程序,我们可以在 App Store 上最多拥有五张截图。

  2. App Store 图标:准备代表应用在 App Store 上的图标。其尺寸必须是512x512像素,并且必须与应用图标相同。

  3. 描述和关键词:准备描述您的应用程序的文本。尽量包括最重要的功能。记住,描述是用户在下载应用程序之前会阅读的内容,所以越吸引人越好。

    准备有助于您的应用程序在搜索结果中排名靠前的关键词。

    应用描述和关键词都是必需的。

  4. 登录到 iTunes Connect:iTunes Connect 是管理提交应用程序(以及其他 App Store 相关内容)的开发者门户。使用您的 Apple 开发者 ID 登录到 iTunes Connect(itunesconnect.apple.com )。点击链接管理您的应用程序。然后,点击左上角的添加新应用按钮。按照步骤在门户上完成应用程序准备。完成后,请确保应用程序状态为等待上传

  5. 上传:在门户上创建新应用程序后,您可以使用应用程序加载器上传压缩的应用程序包。它默认安装在 Xcode 中,可以在路径/Developer/Application/Utilities下找到,或者通过Spotlight搜索。

    当您启动应用程序加载器时,它将要求您使用您的Apple 开发者 ID登录。登录后,您将看到一个以下窗口:

    如何操作...

  6. 点击提交您的应用程序按钮,它将连接到iTunes Connect,找到您处于等待上传状态的应用程序,并将它们加载到列表框中:如何操作...

    • 然后,您将看到您应用程序的摘要视图:

    如何操作...

  7. 点击选择...按钮,将弹出一个对话框,允许您选择压缩的应用程序包。选择后,继续上传。一切就绪!如果所有步骤都正确完成,应用程序将被上传,并将在 App Store 上等待审核发布。

它是如何工作的...

应用截图和 App Store 图标非常重要。它们可以是JPGTIFPNG格式,使用 RGB 颜色,分辨率至少为 72 DPI。

但图片只有在用户已经在 App Store 中查看你的应用时才重要。关键词和描述是允许你的应用在搜索结果中排名更高的参数,并让用户决定应用是否值得下载。特别是关于关键词,要明智地选择。不要包含尽可能多的关键词;反映应用关键方面的关键词越少越好。

iTunes Connect 是管理应用、审查财务数据、应用下载的开发者门户,包括开发者需要签署的合同和协议。请确保你阅读并接受这些合同,否则你将无法继续应用准备流程。在这个过程中,你需要提供之前描述的应用必要信息,如果它是付费应用,还需要提供价格范围、它将在哪些国家可用,以及如果不想自动发布,还需要提供发布日期。

当一切设置正确且应用状态为等待上传时,你可以运行应用加载器来上传它。iOS 和 iOS SDK 版本每次更新时,各种组件或流程都会发生变化。请确保你的 iOS SDK 版本是最新的。

还有更多...

在应用准备过程中某个阶段,你将需要输入一个库存单位(SKU)编号。这个编号是每个产品或服务的唯一标识符。它可以是你想要的任何数字,但请保持一个特定的模式来跟踪标识符,例如,当你开发额外的应用时。

参见

在本章中:

  • 为 App Store 准备应用

第十五章:iOS 5 特性

在本章中,我们将涵盖:

  • 重新生成翻页效果

  • 视图样式

  • Twitter 集成

  • 与分割键盘一起工作

简介

iOS 的第五个主要版本带来了 200 多项新特性。在本章中,我们将只关注其中的一些,这些特性大多与增强用户体验有关。

具体来说,我们将创建一个项目,显示内容分为页面,用户可以使用新引入的 UIPageViewController 类像在普通书籍中一样导航这些页面。

我们将讨论 UIAppearance 类,它允许我们以更灵活和简单的方式对我们的应用程序中的控件进行样式设计。在今天的设备中,社交分享也不可或缺,因此我们将创建一个项目,允许用户使用 Twitter,使用 TWTweetComposeViewController。

在本章的最后一个小节中,我们将使用 iPad 的新分割键盘功能,学习如何根据屏幕上虚拟键盘的位置调整内容。

重新生成翻页效果

在这个菜谱中,我们将创建一个应用,使用UIPageViewController类来显示类似书籍的内容。

准备工作

在 MonoDevelop 中创建一个新的 iPhone 项目,并将其命名为BookApp。除了MainController外,再添加另一个控制器,并将其命名为Page。根据您的喜好配置Page控制器的外观。在这个菜谱的源代码中,它包含一个UIImageView和一个UILabel

如何操作...

  1. MainController类中输入以下代码:

    private UIPageViewController pageViewController;
    private int pageCount = 3;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    Page firstPage = new Page(0);
    this.pageViewController = new UIPageViewController( UIPageViewControllerTransitionStyle.PageCurl, UIPageViewControllerNavigationOrientation.Horizontal, UIPageViewControllerSpineLocation.Min);
    this.pageViewController.SetViewControllers(new UIViewController[] { firstPage }, UIPageViewControllerNavigationDirection.Forward, false, s => { });
    this.pageViewController.GetNextViewController = this.GetNextViewController;
    this.pageViewController.GetPreviousViewController = this.GetPreviousViewController;
    this.pageViewController.View.Frame = this.View.Bounds;
    this.View.AddSubview(this.pageViewController.View);
    }
    private UIViewController GetNextViewController( UIPageViewController pageController, UIViewController referenceViewController){
    Page currentPageController = referenceViewController as Page;
    if (currentPageController.PageIndex >= (this.pageCount - 1)){
    return null;
    } else{
    int nextPageIndex = currentPageController.PageIndex + 1;
    return new Page(nextPageIndex);
    }
    }
    private UIViewController GetPreviousViewController( UIPageViewController pageController, UIViewController referenceViewController){
    Page currentPageController = referenceViewController as Page;
    if (currentPageController.PageIndex <= 0){
    return null;
    } else{
    int previousPageIndex = currentPageController.PageIndex - 1;
    return new Page(previousPageIndex);
    }
    }
    
    
  2. Page类中添加一个属性,并修改其构造函数,如下面的代码所示:

    public Page (int pageIndex) : base ("Page", null){
    this.PageIndex = pageIndex;
    }
    public int PageIndex{
    get;
    private set;
    }
    
    
  3. 最后,在ViewDidLoad方法中配置将在Page中显示的内容:

    this.imageView.Image = UIImage.FromFile(string.Format( "images/{0}.jpg", this.PageIndex + 1));
    this.lblPageNum.Text = string.Format("Page {0}", this.PageIndex + 1);
    
    
  4. 在模拟器上编译并运行应用程序。

  5. 在模拟器的屏幕区域上点击并拖动以更改页面。结果应类似于以下截图:

如何操作...

它是如何工作的...

随着 iOS 5 的推出,UIPageViewController类成为许多开发者的期望组件。它允许我们通过类似真实书籍的效果导航内容,就像在苹果的 iBooks 应用中一样。

我们使用以下行进行初始化:

this.pageViewController = new UIPageViewController( UIPageViewControllerTransitionStyle.PageCurl, UIPageViewControllerNavigationOrientation.Horizontal, UIPageViewControllerSpineLocation.Min);

构造函数的第一个参数确定效果的类型。目前唯一可用的值是PageCurl。第二个参数确定效果的方向。Horizontal是类似于书籍的效果的值,而Vertical是类似于笔记本的效果的值,其中页面在顶部绑定。第三个参数确定书籍绑定的位置。Min表示绑定在屏幕的一侧;在这种情况下,在左侧。

初始化页面控制器后,我们需要通过调用其SetViewControllers方法来设置其第一个页面:

this.pageViewController.SetViewControllers(new UIViewController[] { firstPage }, UIPageViewControllerNavigationDirection.Forward, false, s => { });

其第一个参数是一个UIViewController对象的数组。我们可以为这个参数设置一个或两个控制器,具体取决于设备方向。例如,如果应用程序支持横屏方向,我们可能希望同时显示两个页面。第二个参数基本上决定了包含页面的导航方向。Forward表示如果我们从右向左在屏幕上滑动,将加载下一页,而Reverse表示对于相同的滑动,将加载上一页。最后一个参数是UICompletionHandler类型,代表在控制器被添加后要执行的处理器。在这个例子中,我们不需要它,所以我们只传递一个空的 lambda。

接下来,我们需要为我们的“书籍”其余页面提供数据源。MonoTouch 再次为我们简化了事情,提供了两个非常有用的属性供我们使用:GetNextViewControllerGetPreviousViewController。这两个属性仅仅代表了如果我们要为页面控制器创建一个Delegate对象时需要重写的回调方法。除了它们的名称外,这两个方法的签名是相同的:

UIViewController GetNextViewController(UIPageViewController pageController, UIViewController referenceViewController);
UIViewController GetPreviousViewController(UIPageViewController pageController, UIViewController referenceViewController);

第一个参数给出了页面控制器,而第二个参数给出了在调用此方法时屏幕上当前显示的控制器。

在这些方法的实现中,我们只需返回应该加载的下一个控制器,或者当前控制器之前的一个。如果我们不希望激活效果,我们只需返回null

最后但同样重要的是,我们设置了页面控制器的视图大小,并将其添加到父视图中,以便它将被显示:

this.pageViewController.View.Frame = this.View.Bounds;
this.View.AddSubview(this.pageViewController.View);

还有更多...

如果我们希望我们的应用程序支持横屏方向,我们首先必须在MainController类中实现ShouldAutoRotateToInterfaceOrientation方法,并从它返回我们希望支持的横屏方向。其次,我们必须为UIPageViewController类的SetViewControllers方法提供两个视图控制器。

双面页面

如您在菜谱的截图中所注意到的,当我们翻页时,其内容在页面的背面以相反的方式显示,就像我们通过真实书籍的一页看过去一样。我们有选项通过将UIPageViewController.DoubleSided属性设置为true来创建双面页面。

视图样式

在这个菜谱中,我们将发现如何轻松地在我们应用程序中设置按钮样式。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为StyleButtonsApp。添加MainController和另一个名为ModalController的控制器。

如何做到这一点...

  1. 在每个控制器上添加一个按钮。在MainController类中,实现ViewDidLoad方法,代码如下:

    public override void ViewDidLoad (){
    base.ViewDidLoad ();
     UIButton.Appearance.BackgroundColor = UIColor.Gray;
    UIButton.Appearance.SetTitleColor(UIColor.White, UIControlState.Normal); 
    
    this.buttonPresent.TouchUpInside += delegate(object sender, EventArgs e) {
    this.PresentModalViewController(new ModalController(), true);
    } ;
    }
    
    
  2. 实现ModalController类的ViewDidLoad方法,以便在按钮被点击时关闭它:

    this.buttonDismiss.TouchUpInside += delegate(object sender, EventArgs e) {
    this.DismissModalViewControllerAnimated(true);
    } ;
    
    
  3. 在模拟器上编译并运行应用程序。

  4. 点击显示按钮以显示模态控制器。注意,MainControllerModalController的按钮具有相同的背景颜色和文字颜色。

它是如何工作的...

为了使我们的应用程序中的所有按钮看起来相同,我们使用UIButton类的Appearance静态属性。此属性返回继承自UIAppearance类的对象,这是将 Objective-C 的UIAppearance协议反映到 MonoTouch 中的类。

这样,我们为所有支持它的视图都有一个Appearance静态属性,根据我们想要样式的视图进行强类型化。对于UIButtonAppearance属性返回一个UIButtonAppearance对象。在设置好这个对象中的值后,应用程序中所有UIButton实例将共享相同的样式。

因此,在这个例子中,如高亮代码所示,我们设置了我们希望所有按钮都具有的背景颜色和文字颜色,运行时将为我们处理其余部分。

还有更多...

在我们的应用程序中全局样式化控件是一个非常不错的功能,但有人可能会同意它有点限制。如果我们只想样式化特定的UIButtons而不是所有按钮怎么办?考虑以下代码:

UIButton.UIButtonAppearance buttonStyle = UIButton.AppearanceWhenContainedIn(typeof(ModalController));
buttonStyle.BackgroundColor = UIColor.Red;

AppearanceWhenContainedIn方法返回相应的UIAppearance对象,在这种情况下是UIButtonAppearance,并接受一个类型为System.Type的可变数量的参数(params Type[] containers))。此代码将仅样式化包含在ModalController对象中的UIButton实例。

尽管该方法参数的数量是可变的,但我们传递的Type对象序列决定了其行为。例如,以下调用将只在我们将ModalController包含在MainController中时,将我们设置的样式应用到ModalController中包含的UIButton实例。

UIButton.AppearanceWhenContainedIn(typeof(MainController), typeof(ModalController));

特定属性

继承自UIView的每个类都继承自UIAppearance类。然而,并非每个类的所有属性都支持它。例如,通过UIButtonAppearance对象,我们可以设置应用程序中每个UIButton的背景颜色,但不能设置标题。

Twitter 集成

在这个菜谱中,我们将创建一个实现 Twitter 分享的应用程序,允许用户发送推文。

准备工作

在 MonoDevelop 中创建一个新的项目,并将其命名为TweetApp。将MainController添加到项目中。

如何操作...

  1. MainController的视图中添加一个按钮,并在类中输入MonoTouch.Twitter命名空间。接下来,输入以下代码:

    private TWTweetComposeViewController tweetController;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.buttonTweet.TouchUpInside += delegate(object sender, EventArgs e) {
    if (TWTweetComposeViewController.CanSendTweet){
    this.tweetController = new TWTweetComposeViewController();
    this.tweetController.SetInitialText("Tweet from MonoTouch!");
    this.tweetController.AddUrl(NSUrl.FromString( "http://software.tavlikos.com"));
    this.tweetController.SetCompletionHandler(delegate( TWTweetComposeViewControllerResult tweetResult) {
    if (tweetResult == TWTweetComposeViewControllerResult.Cancelled){
    Console.WriteLine("Tweet cancelled!");
    } else{
    Console.WriteLine("Tweet sent!");
    }
    this.DismissModalViewControllerAnimated(true);
    } );
    this.PresentModalViewController(this.tweetController, true);
    } else{
    Console.WriteLine("Cannot use Twitter on this device!");
    }
    } ;
    }
    
    
  2. 在模拟器上编译并运行应用程序。如果模拟器上尚未设置 Twitter 账户,可以通过Settings应用程序轻松配置。

  3. 点击按钮以显示 Twitter 控制器。结果应该类似于以下截图:

如何操作...

它是如何工作的...

iOS 提供了 TWTweetComposeViewController 类,它提供了共享功能。此控制器与用于从设备相册共享照片的原生界面相同。就像类似的本地控制器一样,我们只能在它呈现之前设置其内容。我们无法在它向用户显示后对其进行修改,用户负责是否发送或丢弃它。

我们可以通过读取 CanSendTweet 静态属性来确定用户是否在设备上配置了 Twitter 账户:

if (TWTweetComposeViewController.CanSendTweet)

如果我们在设备上未设置账户的情况下呈现控制器,将显示一个原生警报,用户可以选择在继续之前配置账户。

然后,我们初始化控制器,并使用 SetInitialText 方法设置要填充的文本,如果需要的话:

this.tweetController = new TWTweetComposeViewController();
this.tweetController.SetInitialText("Tweet from MonoTouch!");

我们还可以通过 AddUrlAddImage 方法分别添加推文中的 URL 或图片。

为了获取用户是否发送或取消推文的反馈,我们调用 SetCompletionHandler 方法,传递要调用的回调:

this.tweetController.SetCompletionHandler(delegate(TWTweetComposeView ControllerResult tweetResult) {

此回调接受枚举类型 TWTweetComposeViewControllerResult 的一个参数,它可以包含两个值中的任意一个 DoneCancelled

最后但同样重要的是,我们应该在回调中关闭控制器。

注意

手动关闭 TWTweetComposeViewController 不是必需的。然而,如果没有手动关闭它,已经注意到尽管在用户点击 Send 时控制器被关闭,但需要两次点击 Cancel 按钮才能关闭它。

还有更多...

除了发送推文外,MonoTouch.Twitter 命名空间还封装了 TWRequest 类,允许我们通过 Twitter API URL 读取 Twitter 信息,例如用户的推文时间线。通过这种方式接收到的数据是以 JSON 对象的形式,正确读取它们是我们的责任。

支持横幅方向

TWTweetComposeViewController 支持横幅方向。要启用它,我们只需覆盖呈现它的控制器中的 ShouldAutoRotateToInterfaceOrientation 方法。

与分割键盘一起工作

在这个菜谱中,我们将创建一个能够感知虚拟键盘位置变化的应用程序,以便相应地调整我们的内容。

准备工作

在 MonoDevelop 中创建一个新的 iPad 项目,并将其命名为 SplitKeyboardApp。将 MainController 添加到项目中。

如何做到这一点...

  1. 添加一个 UITextField,并将其放置在 MainController 的中心位置。调整文本框的大小,使其宽度扩展到屏幕的另一侧。在 MainController 类中添加以下代码:

    private NSObject kbdFrameChangedObserver;
    public override void ViewDidLoad (){
    base.ViewDidLoad ();
    this.kbdFrameChangedObserver = NSNotificationCenter. DefaultCenter.AddObserver(new NSString(" UIKeyboardDidChangeFrameNotification"), this.KeyboardFrameChanged);
    }
    private void KeyboardFrameChanged(NSNotification ntf){
    Console.WriteLine("Keyboard frame changed!");
    if (ntf.UserInfo != null){
    NSObject frameEndObj = null;
    if (ntf.UserInfo.TryGetValue(UIKeyboard.FrameEndUserInfoKey, out frameEndObj)){
    RectangleF keyboardFrame = (frameEndObj as NSValue).RectangleFValue;
    RectangleF textFieldFrame = this.txtInput.Frame;
    if (textFieldFrame.IntersectsWith(keyboardFrame)){
    textFieldFrame.Y = keyboardFrame.Y - (textFieldFrame.Height + 40f);
    UIView.BeginAnimations("");
    this.txtInput.Frame = textFieldFrame;
    UIView.CommitAnimations();
    }
    }
    }
    }
    
    
  2. 在模拟器上编译并运行应用程序。

  3. 点击文本框以显示键盘。如果之前未在 iPad 模拟器中使用过键盘,它将默认处于正常状态,即在底部并合并。

  4. 点击并拖动屏幕右下角的隐藏键盘键来移动键盘,使其分裂,并让它停留在文本字段上方。

如何操作...

  • 观察文本字段在键盘上方动画。结果应该类似于以下截图:

如何操作...

它是如何工作的...

要检测分裂键盘的位置,我们首先需要为UIKeyboardDidChangeFrameNotification键添加一个观察者:

this.kbdFrameChangedObserver = NSNotificationCenter. DefaultCenter.AddObserver(new NSString( "UIKeyboardDidChangeFrameNotification"), this.KeyboardFrameChanged);

KeyboardFrameChanged回调内部,我们从UserInfo字典中获取FrameEndUserInfoKey键的值。这个值,作为一个NSObject返回,实际上是一个包含键盘框架的NSValue对象。我们从它中读取RectangleFValue属性,以获取包含键盘框架值的RectangleF对象:

if (ntf.UserInfo.TryGetValue(UIKeyboard.FrameEndUserInfoKey, out frameEndObj)){
RectangleF keyboardFrame = (frameEndObj as NSValue).RectangleFValue;
}

其余的代码将文本字段移动到键盘上方。请随意更改它!

还有更多...

NSNotification类的UserInfo属性返回一个NSDictionary对象,其中包含有关键盘的各种信息。为了枚举它包含的键,简单的foreach即可:

foreach (NSString eachItem in ntf.UserInfo.Keys){
Console.WriteLine("Key: {0}", eachItem);
}

移动键盘有问题?

隐藏键盘键在我们长按它时会出现一个小的“上下文菜单”。这个菜单提供了停靠和合并(或相反操作)键盘的选项。为了将键盘移动到我们想要的位置,我们必须在点击键的瞬间就开始拖动。

posted @ 2025-10-11 12:55  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报