MonoTouch-IOS-开发秘籍-全-
MonoTouch IOS 开发秘籍(全)
原文:
zh.annas-archive.org/md5/e782db9cc6c1163c07b37906130bf0f1
译者:飞龙
前言
技术正在迅速发展。便携式设备,如媒体播放器、智能手机和平板电脑,为人们的沟通、分享和消费数字内容的方式带来了巨大的进步和变化。开发者需要了解这些设备所运行的可用平台,如果他们想要成为“游戏的一部分”。
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,以及如何在我们的代码中访问应用程序的用户界面,包括 Outlets 和 Actions 的概念。
最后但同样重要的是,我们将学习如何编译我们的应用程序,可用的编译选项,以及如何在模拟器上进行调试。
安装先决条件
有关如何下载和安装使用 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 开发,我们需要按照以下顺序下载和安装必要的组件:
-
在 OS X Snow Leopard 上的 Xcode 和 iOS SDK: 下载镜像文件后,将其挂载,然后在将弹出的窗口中双击 Xcode 和 iOS SDK 图标以开始安装。为了继续安装,必须阅读并接受将显示的两个许可协议。之后,您只需选择安装位置并点击 继续。
-
在 OS X Lion 上的 Xcode 和 iOS SDK: 要安装 Xcode 和 SDK,需要登录 Mac App Store。下载的文件基本上是 Xcode 和 SDK 的安装程序。下载完成后,运行 Install Xcode 应用程序,并按照安装说明进行操作。
-
下载和安装 Mono for Mac: Mono 的 Mac 版本可以通过 Mono 项目网站:
www.mono-project.com
下载。 -
下载和安装 MonoTouch: 可以通过提供电子邮件地址从
ios.xamarin.com/DownloadTrial
下载最新的评估版本。 -
下载并安装 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# 开发者带来了安慰。
有用链接
以下是一份包含安装所需工具和信息的链接列表:
-
苹果 iOS 开发者门户:
developer.apple.com/devcenter/ios/index.action
-
Mono:
www.mono-project.com
-
MonoDevelop:
www.monodevelop.com
-
MonoTouch:
ios.xamarin.com
-
MonoTouch 安装指南:
ios.xamarin.com/Documentation/Installation
-
关于苹果开发者工具的信息:
developer.apple.com/technologies/tools/xcode.html
更新
MonoDevelop 有一个检查可用更新的功能。每次程序启动时,它都会检查 MonoDevelop 本身、MonoTouch 和 Mono 框架的更新。它可以关闭,但并不推荐,因为它有助于保持与最新版本的同步。可以在MonoDevelop | 检查更新下找到。
参见
在本章中:
-
编译
-
调试我们的应用程序
第十四章,部署:
-
在其他设备上调试
-
为 App Store 准备我们的应用程序
使用 MonoDevelop 创建 iPhone 项目
在这个任务中,我们将讨论使用 MonoDevelop IDE 创建我们的第一个 iPhone 项目。
准备中...
现在我们已经安装了所有先决条件,我们将讨论如何使用 MonoDevelop 创建我们的第一个 iPhone 项目。
启动 MonoDevelop。它位于 Applications
文件夹中。MonoDevelop 的默认项目位置是文件夹 /Users/{yourusername}/Projects
。如果硬盘上不存在,则在创建我们的第一个项目时创建。如果我们想更改文件夹,可以从菜单栏选择MonoDevelop | 首选项。
在左侧面板中选择加载/保存,在默认解决方案位置字段中输入项目首选位置,然后点击确定。
如何操作...
-
启动 MonoDevelop 时,首先加载的是其起始页面。从菜单栏选择文件 | 新建 | 解决方案...。将显示一个窗口,提供给我们可用的项目选项。
-
在这个窗口中,在左侧面板中选择C# | MonoTouch | iPhone。iPhone 项目模板将在中间面板中显示。
-
选择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
命名空间还包含用于绑定到原生对象的属性,例如我们之前看到的RegisterAttribute
。MonoTouch.UIKit
命名空间是对原生 Objective-C UIKit
框架的包装。正如其名称所暗示的,该命名空间包含提供界面功能的类、委托和事件。除了两个类DraggingEventArgs
和ZoomingEventArgs
外,所有对象的名称都共享相同的“UI”前缀。此时应该很清楚,这两个命名空间对于所有 MonoTouch 应用都是必不可少的,并且它们的对象将被频繁使用。
该类继承自UIApplicationDelegate
类,使其成为我们应用的UIApplication
Delegate对象。
注意
在 Objective-C 世界中,委托对象的概念与 C# 中的委托有所不同。这将在第二章用户界面:视图中详细解释。
AppDelegate
类包含两个字段和一个方法:
UIWindow window;
MyFirstiPhoneProjectViewController viewController;
public override bool FinishedLaunching (UIApplication app, NSDictionary options) {
UIWindow
对象定义了我们应用程序的主窗口,而 MyFirstiPhoneProjectViewController
是一个变量,它将持有应用程序的视图控制器。
注意
iOS 应用程序通常只有一个窗口,类型为 UIWindow
。UIWindow
的概念与 .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());
事件处理程序,如FinishedLaunching
、ReceiveMemoryWarning
或DidEnterBackground
,只是这些通知中的一些。除了通知派发机制之外,UIApplication
对象包含所有存在的UIWindow
对象的列表;通常是其中一个。iOS 应用程序必须有一个UIApplication
对象,或者从它继承的类,并且该对象必须有一个相应的UIApplicationDelegate
对象。这是我们之前看到的AppDelegate
类实现。
Info.plist
这个文件基本上是应用程序的设置文件。它具有一个简单的属性值结构,定义了 iOS 应用程序的各种设置,例如它支持的朝向、图标、支持的 iOS 版本、它可以安装的设备等等。如果我们在这个文件上双击 MonoDevelop,它将在嵌入的编辑器中打开,该编辑器专门设计用于.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。
-
前往 MonoDevelop 并打开我们之前创建的项目
MyFirstiPhoneProject
。 -
在左侧的解决方案面板上,双击MyFirstiPhoneProjectViewController.xib。MonoDevelop 会启动 Xcode,并在 Interface Builder 中加载文件!
-
在工具栏的右侧,在 Xcode 窗口的顶部,选择适当的编辑和查看选项,如下所示:
- 以下截图展示了打开 XIB 文件时的 Interface Builder 外观:
它是如何工作的...
现在我们已经加载了带有我们应用程序视图控制器的 Interface Builder,让我们熟悉一下它。
用户界面设计师直接连接到 Xcode 项目。当我们添加一个对象时,Xcode 会自动生成代码来反映我们所做的更改。MonoDevelop 会为我们处理这件事,因为当我们双击 XIB 文件时,它会自动创建一个临时 Xcode 项目,以便我们可以在用户界面中进行我们想要的更改。因此,我们除了设计我们应用程序的用户界面之外,没有更多的事情要做:
界面构建器分为三个区域。下面简要描述每个区域。
-
导航区域:在这个区域,我们可以看到 Xcode 项目中的文件。
-
编辑区域:这个区域是我们设计用户界面的地方。
-
实用区域:这个区域包含检查器和库面板。检查器是我们配置每个对象的地方,而库面板是我们查找对象的地方。
编辑区域分为两个部分。左侧的是设计师,而右侧的是辅助编辑器。在辅助编辑器内部,我们可以看到与设计师中选定的项目对应的底层 Objective-C 源代码文件。尽管我们不需要编辑 Objective-C 源代码,但我们稍后需要辅助编辑器。
更多内容...
我们在 Interface Builder 中看到了XIB
文件的样子。但是,关于这些文件还有更多内容。我们之前提到,XIB
文件是 Interface Builder 可读取的带有适当信息的 XML 文件。问题是,当项目编译时,编译器也会编译XIB
文件,将其转换为二进制等效文件:NIB
文件。XIB
和NIB
文件包含完全相同的信息。它们之间的唯一区别是,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
文件,我们将向其中添加一些控件。
添加一个标签
-
如果尚未选择,转到库面板并从下拉列表中选择对象。选择标签对象。
-
将标签拖放到设计器中视图的灰色空间,位于上半部分。
-
从左侧和右侧选择并调整标签的大小,使其吸附到当您接近视图边缘时出现的虚线上。
-
再次,选择标签后,转到检查器面板,选择属性选项卡,然后在布局部分,点击中间的对齐按钮。恭喜你,你已经在应用程序的主视图中添加了一个标签!
添加一个按钮
我们将执行类似的步骤来在我们的界面中添加按钮。
-
再次,在库面板中,在对象部分,选择按钮对象。它位于标签对象旁边。
-
将其拖放到视图的下半部分。将其中心与之前添加的标签的中心对齐。会出现一条虚线,当两个控件的中心几乎对齐时,按钮会自动吸附到它上。
-
将按钮调整到与标签相同的宽度。由于标签具有透明背景,您无法确切地看到它的宽度,因此当您调整大小并出现三条虚线时,您就会知道按钮的宽度相同。
-
现在,让我们向按钮添加一些文本。选择它并转到检查器面板。在属性选项卡中,在标题字段中输入
请点击此处!
。 -
添加按钮后,通过菜单栏中的文件 | 保存保存文档。主视图现在应该看起来像以下截图(在此处显示已调整大小):
它是如何工作的...
如您所见,尽管 Interface Builder 的一些概念似乎很难,但它使用起来相当简单。它还提供了很多反馈。当我们拖动对象时,光标上会出现一个绿色的圆形十字,表示我们可以将对象放在那里。此外,当我们调整控件大小时,我们会在其旁边看到其尺寸。
您也可以通过修改检查器面板中大小选项卡中的值来调整控件的大小和位置。大小选项卡中的另一个有用功能是自动调整大小。自动调整大小为控件提供布局选项,当我们的应用程序需要支持不同的设备方向时,这可以非常有用。您可以选择一个控件,然后点击自动调整大小部分中左边的正方形外部或内部的线条。旁边的图像会动画显示,给您一个印象,当布局改变时控件将如何表现。
更多内容...
现在,让我们尝试在 iOS 模拟器上运行应用程序。回到 MonoDevelop,如果尚未选择,请选择调试 | iPhoneSimulator的项目配置。现在代码中不需要做任何事情;只需点击运行按钮。它是配置组合框右侧的第三个按钮,带有双齿轮图标。当编译完成后,iOS 模拟器将自动启动并运行我们刚刚创建的应用程序!您甚至可以用鼠标点击按钮来“点击”它,并看到它的响应。当然,我们的应用程序目前没有任何其他功能。
设置按钮标题
通过双击并输入首选标题,可以轻松地设置按钮或标签的标题。这样做,并观察 Interface Builder 如何显示要执行的操作。
参见
在本章中:
-
编译
-
调试我们的应用程序
在本书中:
第二章, 用户界面:视图:
-
使用按钮接收用户输入
-
使用标签显示文本
通过出口访问 UI
在本食谱中,我们将讨论出口的概念以及它们与 MonoTouch 的用法。
准备工作
在上一个任务中,我们学习了如何添加控件来形成我们应用程序的基本界面。在本任务中,我们将讨论如何在代码中访问和使用这些控件。启动 MonoDevelop,并打开我们之前创建的项目ButtonInput
。通过在解决方案面板中双击它来在 Interface Builder 中打开项目的ButtonInputViewController.xib
。
如何操作...
-
打开辅助编辑器,Ctrl-drag从标签到 Objective-C 源文件,如图下所示:
- 当您释放鼠标时,会出现一个上下文窗口,类似于以下截图:
-
在上下文窗口的名称字段中输入
labelStatus,
,然后点击连接。 -
对按钮也做同样的操作,命名为
buttonTap
。通过在菜单栏中选择文件 | 保存或按键盘上的Option - S来保存 Interface Builder 文档。 -
回到 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!"; };
-
这段代码片段为按钮的 TouchUpInside 事件添加了一个处理程序。这个事件类似于 System.Windows.Forms 中按钮控制的 Clicked 事件。它还展示了匿名方法的使用,这仅仅显示了 MonoTouch 如何为.NET 开发者提供 C#功能。就是这样!我们的应用程序现在已经准备好了,带有功能性的控件。
-
在模拟器上编译并运行它。当你点击按钮时,你会看到标签文本的变化。
它是如何工作的...
出口机制基本上是一种将 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
项目相同的控件、出口和连接。现在不要在项目中添加任何代码。
如何做...
向界面对象添加动作类似于添加出口。
-
在 Interface Builder 中,Ctrl-drag 从按钮到源代码文件。在将显示的上下文窗口中,将 连接 字段从 出口 更改为 动作。
-
在 名称 字段中输入
OnButtonTap
,并在 事件 字段中选择 触摸抬起,如果尚未选择。 -
点击 连接 按钮,并保存文档。
-
在
ButtonInputActionViewController
类中,添加以下方法:partial void OnButtonTap(NSObject sender){ this.labelStatus.Text = "Button tapped!"; }
-
应用程序已准备就绪!在模拟器中编译并运行。
-
点击按钮,你会看到标签中的文本发生变化,就像我们在上一个应用程序中创建的那样。
它是如何工作的...
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 中的冒号表示参数的存在。例如,doSomething
与 doSomething:
不同。它们的区别在于第一个不接受任何参数,而第二个接受一个参数。
动作名称末尾的冒号表示有一个参数;在这种情况下,参数是MonoTouch.UIKit.NSObject
sender。这是我们在模拟器中点击按钮时应用程序的外观:
还有更多...
之前的示例只是为了展示如何在 MonoTouch 项目中实现动作。用动作替换事件基本上取决于开发者。
参考信息
在本章中:
-
界面构建器
-
创建用户界面
-
通过出口访问用户界面
编译
在本食谱中,我们将讨论如何使用 MonoDevelop 编译项目。
准备工作
MonoDevelop 提供了许多不同的编译选项。在本任务中,我们将讨论这些选项。我们将使用本章中较早创建的ButtonInput
项目进行工作。
如何操作...
-
在 MonoDevelop 中加载项目后,转到项目 | ButtonInput 选项。
-
在出现的窗口中,从左侧面板的构建部分选择iPhone 构建。将项目配置设置为调试,平台设置为iPhoneSimulator。在链接器行为字段中,从下拉菜单中选择链接所有程序集。在SDK 版本字段中,如果尚未选择,请选择默认。
-
现在,转到左侧面板上的iPhone 应用程序。
-
在摘要选项卡中,在应用程序名称字段中输入
Button Input
,在版本字段中输入1.0
。在部署目标下拉菜单中选择3.0版本。以下截图显示了iPhone 应用程序选项窗口: -
点击确定按钮,然后在菜单栏中点击构建 | 构建全部来编译项目。
它是如何工作的...
我们为项目设置了一些选项。让我们看看这些选项为编译定制提供了什么。
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。
如何操作...
-
MonoDevelop 支持断点。要激活一行上的断点,请点击行号左侧的空间来设置它。在
Main.cs
文件的以下行设置断点:this.labelStatus.Text = "Button tapped!";
-
这就是 MonoDevelop 中的断点看起来像:
-
通过点击从左侧开始的第二个带有双齿轮图标的按钮,或通过点击菜单栏上的运行 | 调试来编译和调试项目。MonoDevelop 将显示一个消息框,显示消息,等待调试器连接。当模拟器打开且应用程序加载时,请关注应用程序输出面板中提供的信息。点击应用程序按钮。执行将暂停,MonoDevelop 将以黄色突出显示断点。将鼠标移至断点行中的
labelStatus
变量上。然后 MonoDevelop 将显示一个窗口,其中包含所有已评估变量的成员: -
要停止调试,请点击工具栏上的停止按钮,按钮上有一个红色的圆圈中白色的(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
文件。
如何操作...
-
要向项目中添加视图,请从库垫拖动一个
UIView
对象到主视图上。确保它适合整个窗口区域。 -
要使
UIView
可访问,为其创建一个出口,并将其命名为subView
。注意
关于出口的概念及其使用方法将在第一章中详细讨论,通过出口访问 UI。
-
选择我们刚刚添加的视图,并转到检查器面板。选择属性选项卡,并在背景下拉列表中选择浅灰色。
-
现在,选择大小选项卡,并将视图的高度减少
60
点。 -
保存文档。
-
在模拟器上编译并运行应用程序。结果应该看起来像以下图像:
- 模拟器屏幕的灰色部分是我们刚刚添加的视图。
如何工作...
我们已经成功创建了一个包含一个视图的应用程序。当然,这个应用程序不提供任何功能。它只是为了展示如何添加视图并显示它。
视图是 iOS 应用程序界面的基本组件。每个视觉 UI 对象都继承自UIView
类。这个概念与.NET 中的Form
有所不同。视图管理内容绘制,接受其他视图作为子视图,提供自动调整大小功能,可以接受自身及其子视图的触摸事件,并且许多属性甚至可以动画化。甚至UIWindow
也继承自UIView
。iOS 开发者将最频繁地使用这个类或其继承者。
当使用 Interface Builder 添加的视图在运行时首次实例化时,它会通过检查器窗口的大小选项卡设置其Frame
属性。Frame
属性的类型是RectangleF
,它定义了视图在其父视图的坐标系中的位置,在我们的例子中是主窗口,以及其大小以点为单位。
注意
在 Objective-C 中,UIView
的Frame
属性是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
类继承自 UIResponder
。UIResponder
类负责响应用和处理事件。当一个视图被添加到另一个视图中时,它成为其响应链的一部分。UIView
类公开了 UIResponder
的属性和方法,我们现在感兴趣描述的是以下两个:
-
IsFirstResponder
属性:它返回一个布尔值,指示视图是否是第一个响应者。基本上,它表示视图是否有焦点。 -
ResignFirstResponder():
它会导致视图失去焦点。
以编程方式添加视图
如果我们想以编程方式添加视图,我们将使用 UIView.AddSubview(UIView)
方法:
this.View.AddSubview(this.subView);
AddSubview()
方法将其参数(类型为 UIView
)添加到调用者的子视图列表中,并将其 Superview
参数设置为调用者。除非使用 AddSubview()
方法将其添加到父视图中,否则视图将不会显示。此外,如果视图已经有一个父视图,并且使用其 AddSubview()
方法添加到另一个视图中,则其 Superview
将更改为新调用者的 Superview
。这意味着视图每次只能有一个父视图。
注意
当使用 Interface Builder 添加视图作为子视图时,不需要使用 AddSubview()
方法来显示子视图。但是,当以编程方式添加视图时,必须调用 AddSubview()
方法。
以编程方式从父视图中移除视图,请调用其 RemoveFromSuperview()
方法。如果对没有父视图的视图调用此方法,则没有任何作用。当我们想要重用要删除的视图时,必须注意。我们必须保留对其的引用,否则在方法调用后它可能会被释放。
视图内容布局
UIView
的另一个重要属性是 ContentMode
。ContentMode
接受枚举类型 UIViewContentMode
的值。此属性设置 UIView
如何显示其内容。此属性的默认值是 UIViewContentMode.ScaleToFill
,它将内容缩放到与视图大小完全匹配,如果需要则进行扭曲。
参见
在本章中:
- 创建自定义视图
在本书中:
第一章,通过输出口访问 UI。
-
创建 UI
-
通过输出口访问 UI
第三章,用户界面:视图控制器:
- 视图控制器 和 视图
使用按钮接收用户输入
在本食谱中,我们将学习如何使用按钮接收和响应用户输入。
准备工作
我们在 第一章 中使用了按钮,讨论了如何使用 Interface Builder 向用户界面添加控件。在这个任务中,我们将更详细地描述 UIButton
类。在 MonoDevelop 中打开我们在上一个任务中创建的 FirstViewApp
项目。将主视图的高度增加到在 Interface Builder 中覆盖整个屏幕,并保存文档。
如何做到这一点...
我们将在界面中程序化地添加一个按钮,当它被轻触时,将改变视图的背景颜色。
-
打开
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; } }
-
在
ViewDidLoad()
方法中,添加以下行:this.CreateButton ();
-
在模拟器上编译并运行应用程序。当按钮被轻触时,结果应类似于以下截图:
它是如何工作的...
在这个任务中,我们向用户界面添加了一个按钮,该按钮会改变父视图的背景颜色。此外,我们没有使用任何 Interface Builder 就完成了这个任务。
让我们看看代码是如何工作的。
-
我们创建一个将保存按钮对象的字段:
// A button to change the view's background color UIButton buttonChangeColor;
-
在
CreateButton()
方法中,我们创建按钮并设置一些属性。RectangleF viewFrame = this.subView.Frame; RectangleF buttonFrame = new RectangleF (10f, viewFrame.Bottom - 200f, viewFrame.Width - 20f, 50f);
-
首先,我们将视图的
Frame
分配给一个名为viewFrame
的新变量。然后,我们创建一个名为buttonFrame
的新RectangleF
对象,它将被分配给按钮的Frame
属性。现在我们为按钮有了框架,我们可以初始化它,如下面的代码片段所示://Create the button. this.buttonChangeColor = UIButton.FromType (UIButtonType.RoundedRect); this.buttonChangeColor.Frame = buttonFrame;
-
按钮是通过静态方法
UIButton.FromType(UIButtonType)
初始化的。此方法接受一个UIButtonType
类型的参数,并返回 iOS SDK 中包含的预定义按钮类型。这里使用的UIButtonType.RoundedRect
按钮枚举值是 iOS 按钮的默认类型,具有圆角。在buttonChangeColor
对象初始化后,我们将它的Frame
设置为我们之前创建的RectangleF
值。 -
现在我们已经为按钮提供了初始化代码,我们将设置其标题(没错,不止一个):
// Set the button's titles this.buttonChangeColor.SetTitle ("Tap to change view color", UIControlState.Normal); this.buttonChangeColor.SetTitle ("Changing color...", UIControlState.Highlighted);
-
我们调用了
UIButton.SetTitle(string, UIControlState)
方法两次。此方法负责为每个给定的按钮状态设置按钮的标题。字符串参数是实际要显示的标题。第二个参数是表示应用于控件的不同控件状态的UIControlState
类型的枚举。这些控件状态是:-
Normal:
启用控件的默认空闲状态 -
Highlighted:
控件在触摸事件发生时的状态 -
Disabled:
控件被禁用且不接受任何事件 -
Selected:
控件已被选中。在大多数情况下,此状态不适用。然而,当需要选择状态时,例如在UISegmentedControl
对象中,它是有用的。 -
Application:
用于应用程序用途的附加控件状态值 -
Reserved:
用于内部框架使用因此,使用
UIButton.SetTitle(string, UIControlState)
方法,我们设置了按钮在其默认状态下显示的标题以及在被点击时显示的标题。
-
-
之后,我们设置按钮的
TouchUpInside
事件处理程序,并将其添加为subView:
的子视图this.buttonChangeColor.TouchUpInside += this.buttonChangeColor_TouchUpInside; this.subView.AddSubview (this.buttonChangeColor);
-
在
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
属性。
SetImage
和 SetBackgroundImage
方法提供的功能也可以在 Interface Builder 中 Inspector 面板的 Attributes 选项卡的 Image 和 Background 字段中完成。从下拉列表框中选择要设置所需图像的状态,并设置图像文件的路径,如下面的屏幕截图所示:
参见
在本章中:
-
添加和自定义视图
-
显示图像
-
创建自定义视图
在本书中:
第一章, 开发工具:
-
创建 UI,
-
通过 Outlets 访问 UI
使用标签显示文本
在这个菜谱中,我们将学习如何使用标签向用户显示信息文本。
准备工作
在这个任务中,我们将更详细地描述UILabel
类。再次强调,所有的工作都将在没有 Interface Builder 的帮助下完成。打开我们在上一个菜谱中修改的FirstViewApp
项目。
如何做到这一点...
我们将程序化地创建一个标签,该标签将显示一些静态的指导文本。
-
打开文件
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); }
-
然后在
FinishedLaunching()
方法中添加以下行:this.CreateLabel();
-
在模拟器上编译并运行应用程序。输出应该看起来像以下截图:
它是如何工作的...
我们已经成功创建了一个标签,并在其中放入了一些信息文本。让我们逐步查看代码,看看实际上发生了什么。
-
我们首先在
FirstViewAppViewController
类中创建一个labelInfo
字段,用于存储我们的UILabel
对象。// A label that displays some text UILabel labelInfo;
-
然后我们创建了一个名为
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);
-
我们只需将其高度设置为高于按钮的高度,即
100
点。现在我们已经为标签有了Frame
,我们初始化它://Create the label this.labelInfo = new UILabel (labelFrame); this.labelInfo.Lines = 3; this.labelInfo.TextAlignment = UITextAlignment.Center; this.labelInfo.BackgroundColor = UIColor.Clear;
-
构造函数被使用,这样框架属性将在初始化时立即设置。行属性决定了标签文本上的文本将被分成多少行。文本对齐属性接受枚举类型
UITextAlignment
的值,它包含常用的文本对齐标志:居中、左对齐和右对齐。为了使标签的框架完全不可见,以便只有我们的文本可见,我们将BackgroundColor
属性设置为UIColor.Clear
颜色。 -
下一个部分非常有趣。除了能够设置
label
的字体颜色外,我们还可以为显示的文本设置阴影:// Set text color and shadow this.labelInfo.TextColor = UIColor.White; this.labelInfo.ShadowColor = UIColor.DarkGray; this.labelInfo.ShadowOffset = new SizeF (1f, 1f);
-
TextColor
属性接受UIColor
值。要为标签的文本设置阴影,将UIColor
设置到ShadowColor
属性。然后,将SizeF
结构设置到ShadowOffset
属性。此属性决定了阴影的确切位置。SizeF
的宽度参数定义了阴影的水平位置,而高度参数定义了垂直位置。负值是可以接受的。宽度参数的负值意味着阴影将定位在文本的左侧,而高度参数的负值意味着阴影将定位在文本上方。我们在前面的代码中设置的值意味着阴影将显示在文本右侧 1 点,下方 1 点。 -
我们已经准备好了
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!";
-
如您所见,我们为
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
。
如何做...
-
在 Interface Builder 中打开
ImageViewerAppViewController.xib
文件。 -
在其视图中添加一个
UIImageView
对象。将UIImageView
对象与名为imageDisplay
的出口连接。 -
保存文档。
-
返回到 MonoDevelop,在
ImageViewerAppViewController
类中,输入以下代码:public override ViewDidLoad(){ base.ViewDidLoad(); this.imageDisplay.ContentMode = UIViewContentMode.ScaleAspectFit; this.imageDisplay.Image = UIImage.FromFile("Toroni.jpg"); }
-
在解决方案面板中右键单击项目,然后选择添加 | 添加文件。选择要显示的图像文件,然后单击打开。
-
右键单击您刚刚添加的图像文件,然后单击构建操作 | 内容。
-
最后,在模拟器上编译并运行应用程序。您添加到项目中的图像应显示在屏幕上,如下面的图像所示:
如何工作...
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 设置为 UIImageView
的 Image
属性时,只会显示其第一帧。
使用不同屏幕尺寸的图像
为背景创建图像为开发者提供了制作丰富和优雅的用户界面的能力。创建视图背景的首选图像文件格式是 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
。
如何操作...
-
在 Interface Builder 中打开
TextViewAppViewController.xib
。 -
在其视图的顶部附近添加一个 UIButton,在其下方添加一个 UITextView。将这两个对象连接到它们的出口。
-
保存文档。
-
在 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
-
在模拟器上编译并运行应用程序。
-
在文本视图中轻触某处,键盘就会出现。输入一些文本,然后轻触 完成 按钮以隐藏键盘。
它是如何工作的...
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
。
如何做...
按照以下步骤创建项目:
-
在 Interface Builder 中打开
KeyboardAppViewController.xib
文件。 -
在视图的下半部分添加一个
UITextField
对象,并将其连接到一个出口。 -
保存文档。
-
回到 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 (); } ; }
-
在模拟器上编译并运行应用程序。
-
点击文本字段,并观察它向上移动以避免被键盘遮挡。
-
点击键盘上的完成按钮,并观察当键盘隐藏时文本字段返回到其原始位置。
它是如何工作的...
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:
-
在
ProgressAppViewController
类文件中添加以下using
指令:using System.Drawing; using System.Threading;
-
添加以下字段:
UILabel labelStatus; UIButton buttonStartProgress; UIProgressView progressView; float incrementBy = 0f;
-
在
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);
-
在类中添加以下方法:
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; } } ); } }
-
在模拟器上编译并运行应用程序。
-
点击按钮并观察进度条填充。
它是如何工作的...
UIProgressView
的当前值由其 Progress
属性表示。其可接受值范围始终是从 0
到 1
。因此,在初始化时,我们将它设置为 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 线程上通过调用 UIApplicationDelegate
的 BeginInvokeOnMainThread()
方法来完成,该方法接受 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
。
如何做...
创建项目的步骤如下:
-
在 Interface Builder 中打开
ScrollAppViewController.xib
文件。 -
在其视图中添加一个
UIScrollView
对象,并将其连接到一个出口。然后保存文档。 -
回到 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); }
-
最后,将图像添加到项目中,并将其 Build Action 设置为 Content。一个比模拟器屏幕更大的图像,大小为 320x480 像素,是首选的。
-
在模拟器上编译并运行应用程序。在图像上点击并拖动以显示不同的部分。通过按下 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) 处显示。为了提供内容的缩放功能,我们首先设置 MinimumZoomScale
和 MaximumZoomScale
属性,这些属性设置了内容的缩放比例的最小值和最大值。值为 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
来禁用它。当内容在水平或垂直方向上达到边界时,弹跳内容会立即给用户反馈,告知他们已经达到了边界。此外,可以单独设置 AlwaysBounceHorizontal
和 AlwaysBounceVertical
属性。设置其中一个或两个这些属性将使滚动视图在相应的方向上始终弹跳内容,即使内容的大小等于或小于滚动视图的边界。因此,实际上并不需要滚动。
UIScrollView
事件
UIScrollView
类公开了一些非常有用的事件:
-
Scrolled:
当内容正在滚动时发生此事件 -
DecelerationStarted:
当用户开始滚动内容时发生此事件 -
DecelerationEnded:
当用户完成滚动并且内容停止移动时发生此事件
注意
如果已将处理程序分配给Scrolled
事件,并且设置了ContentOffset
属性,则将触发事件。
参见
在本章中:
-
显示图像
-
显示和编辑文本
-
在分页的内容中导航
在分页的内容中导航
在这个菜谱中,我们将学习如何使用UIPageControl
类来实现页面导航。
准备工作
UIPageControl
为 iOS 应用程序中的多个页面或屏幕提供了一个简单的视觉表示,以点表示。与当前页面相对应的点会被突出显示。它通常与UIScrollView
结合使用。在 MonoDevelop 中创建一个新的iPhone 单视图应用程序项目,并将其命名为PageNavApp
。在项目中添加三个图像文件,并将它们的构建操作设置为内容。
如何做到...
创建此项目的步骤如下:
-
在 Interface Builder 中打开
PageNavAppViewController.xib
文件。 -
在视图底部添加一个
UIPageControl
,在其上方添加UIScrollView
。调整滚动视图的大小以占用视图剩余的所有空间,并保存文档。 -
回到 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); }
-
在类中添加以下方法:
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; } }
-
在模拟器上编译并运行应用程序。在滚动视图中滚动以更改页面。同样,在页面控制上轻触或滚动也可以更改页面。
它是如何工作的...
我们需要做的第一件事是将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
。
如何做...
创建此项目的步骤如下:
-
在 Interface Builder 中打开
ToolbarAppViewController.xib
文件,并在其视图底部添加一个UIToolbar
对象。 -
选择默认包含的按钮,并在 Attributes Inspector 面板中将它的 Identifier 设置为 Save。
-
向工具栏添加一个 Flexible Space Bar Button Item 对象。
-
在工具栏上添加另一个按钮,位于上一个对象的右侧,并将其 Identifier 设置为 Reply。
-
在视图中添加一个
UILabel
对象,并将所有控件(除了灵活空间项)连接到 outlets。 -
保存文档。
-
回到 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!"; } ; }
-
在模拟器上编译并运行应用程序。点击工具栏的两个按钮,并查看状态字符串在标签上显示。
它是如何工作的...
工具栏包含类型为 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
。
如何实现...
完成此项目的步骤如下:
-
在项目中添加一个新的 C# 类文件,并将其命名为
MyView
。 -
使用以下代码实现:
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); } } }
-
在 Interface Builder 中打开
CustomViewAppViewController.xib
文件,并在主视图中添加一个UIView
对象。 -
将其连接到出口,并在身份检查器中设置其类字段为
MyView
。 -
保存文档。
-
在模拟器上编译并运行应用程序。在视图中轻触并拖动,观察触摸坐标在屏幕底部的标签中显示。
它是如何工作的...
创建自定义视图时要注意的第一件事是从 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
。
如何操作...
-
向项目中添加一个新文件。
-
右键点击 Solution 面板中的项目,并选择 Add | New File。
-
在出现的对话框中,从 MonoTouch 部分选择 iPhone View 并带有 Controller,将其命名为
MainViewController
,然后点击 New 按钮。MonoDevelop 将创建一个新的XIB
文件,并自动打开MainViewController.cs
源文件。此文件包含一个覆盖UIViewController
的类,我们可以在其中实现与我们的视图控制器相关的任何代码。 -
在 Interface Builder 中打开
MainViewController.xib
文件。 -
在视图中添加一个
UILabel
。 -
在
MainViewController
类中创建并连接一个出口,并将其命名为myLabel
。 -
在标签中输入文本
View in controller!
。 -
保存
XIB
文档。 -
在 MonoDevelop 中返回,并在
FinishedLaunching()
方法中输入以下代码:MainViewController mainController = new MainViewController (); window.RootViewController = mainController;
-
在模拟器上编译并运行应用程序。
它是如何工作的...
当我们在项目中添加一个新的iPhone View with Controller文件时,在这种情况下是MainViewController
,MonoDevelop 基本上创建并添加了三个文件:
-
MainViewController.xib
:这是包含控制器的 XIB 文件。 -
MainViewController.cs
:这是实现我们控制器类的 C#源文件。 -
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
。
如何做到这一点...
-
在项目中添加三个新的iPhone View with Controller文件,并分别命名为
RootViewController, ViewController1
和ViewController2
。 -
在
AppDelegate
类中添加以下字段:UINavigationController navController;
-
在同一类中,在
FinishedLaunching
方法中,在window.MakeKeyAndVisible()
行之上添加以下代码:RootViewController rootController = new RootViewController(); this.navController = new UINavigationController(rootController); window.RootViewController = this.navController;
-
在 Interface Builder 中打开
RootViewController.xib
文件,并添加两个按钮及其相应的出口。分别设置它们的标题为第一个视图
和第二个视图
。 -
保存文档。
-
打开
ViewController1.xib
和ViewController2.xib
,并在每个视图中添加一个标题为返回根视图
的按钮。不要忘记连接按钮与出口并保存文档。 -
在
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); }; }
-
在
ViewController1
和ViewController2
类中输入以下内容:public override void ViewDidLoad (){ this.buttonPop.TouchUpInside += delegate { this.NavigationController.PopToRootViewController (true); }; }
-
在模拟器上编译并运行应用程序。点击每个按钮以查看和导航到可用的视图。
它是如何工作的...
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 文件。将它们命名为 MainController
和 SettingsController
。
-
在 Interface Builder 中打开两个控制器,并设置它们视图的不同背景颜色,然后保存文档。
-
在
AppDelegate
类中添加以下字段:UITabBarController tabController;
-
在
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); } ;
-
在模拟器上编译并运行应用程序。
-
点击屏幕底部的每个标签,并查看它们各自显示的视图。控制台输出显示在 MonoDevelop 的 应用程序输出 窗格中。以下截图显示了模拟器的屏幕,其中 设置 标签被选中:

它是如何工作的...
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
来显示 UITableView
。UITableView
提供了一个以列表形式显示数据的界面。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 TableControllerApp
。
如何操作...
-
将项目添加一个iPhone 视图控制器,并将其命名为
TableController
。 -
在
AppDelegate
类的FinishedLaunching
方法中添加以下代码:TableController tableController = new TableController(); window.RootViewController = tableController;
-
将
TableController
类的继承从UIViewController
改为UITableViewController:
public partial class TableController : UITableViewController
-
在 Interface Builder 中打开
TableController.xib
,选择并按退格键删除其视图。 -
将
UITableView
拖放到其位置。 -
右键单击
UITableView
以显示输出口面板。 -
按照以下截图所示,从新引用输出口拖动到文件所有者对象:
-
当你释放按钮时,从出现的文件所有者对象的小面板中选择视图。这连接了我们刚刚添加的
UITableView
到文件所有者对象的view
输出口。 -
保存文档。
它是如何工作的...
当我们在 Interface Builder 文档中添加一个 UITableView
时,其视图会显示一些预定义的数据。这些数据仅在设计时出现,而不是在运行时。
UITableViewController
包含一个 UITableView
类型的视图。这个视图负责显示数据,并且可以通过多种方式自定义。
还有更多...
除了 View
属性外,我们还可以通过 TableView
属性访问 UITableViewController
的视图。这两个属性返回相同的对象。
UITableViewController
特定属性
UITableViewController
有一个额外的属性:ClearsSelectionOnViewWillAppear
。当它设置为 true
时,控制器将在视图出现时自动清除所选行。
如何使用 UITableView
填充数据将在 第五章 中详细讨论,显示数据。
参见
在本章中:
- 模态视图控制器
在这本书中:
第五章,显示数据:
- 在表格中显示数据
模态视图控制器
在这个菜谱中,我们将讨论如何以模态方式显示视图控制器。
准备工作
模态视图控制器 是任何显示在其他视图或控制器之上的控制器。这个概念类似于将 WinForm 作为对话框显示,它控制界面并阻止访问应用程序的其他窗口,除非它被关闭。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 ModalControllerApp
。
如何做到这一点...
-
将两个带有控制器的视图添加到项目中,并分别命名为
MainController
和ModalController
。 -
在 Interface Builder 中打开
MainController.xib
文件,并在其视图中添加一个标题为Present
的按钮。 -
创建并连接按钮的适当出口。保存文档并打开
ModalController.xib
文件。 -
在其视图中添加一个标题为
Dismiss
的按钮,并为它创建适当的出口。将其视图的背景颜色设置为非白色。 -
保存文档并在
MainController
类中输入以下代码:public override void ViewDidLoad (){ this.buttonPresent.TouchUpInside += delegate { ModalController modal = new ModalController (); this.PresentModalViewController (modal, true); }; }
-
类似地,覆盖
ModalController
类中的ViewDidLoad()
方法,并在其中输入以下代码:this.buttonDismiss.TouchUpInside += delegate { this.DismissModalViewControllerAnimated (true); };
-
最后,在
FinishedLaunching()
方法中添加代码以显示主控制器:MainController mainController = new MainController (); window.RootViewController = mainController;
-
在模拟器上编译并运行应用程序。
-
点击 Present 按钮并观察模态控制器在主控制器之上显示。
-
点击 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()
方法之前进行操作。
有多少模态控制器?
理论上,我们可以呈现无限数量的模态控制器。当然,对此有两个限制:
-
内存不是无限的:视图控制器会消耗内存,因此我们呈现的视图控制器越多,性能越差。
-
糟糕的用户体验:以模态方式呈现许多控制器会让用户感到不适。
参见
在本章中:
-
在不同视图控制器之间导航
-
在标签页中提供控制器
在本书中:
第十一章,图形和动画:
- 使用动画推送视图控制器
创建自定义视图控制器
在本食谱中,我们将学习如何创建 UIViewController
的子类,并使用它来从 XIB
文件中派生视图控制器。
准备工作
在本任务中,我们将看到如何创建一个自定义视图控制器,它将充当基控制器,为其继承者提供共同的功能。我们将添加到我们的基控制器以与继承类共享的功能是,在 MonoDevelop 的 应用程序输出 面板上输出当前的触摸位置。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 CustomControllerApp
。
如何实现...
-
在项目中添加一个新的空 C# 类,并将其命名为 BaseController。
-
在 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); } }
-
现在,将一个 带有控制器的 iPhone 视图 文件添加到项目中,并将其命名为
DerivedController
。 -
在其类定义中将继承自
UIViewController
的类更改为BaseController
:public partial class DerivedController : BaseController
。 -
最后,将派生控制器的视图添加到主窗口:
DerivedController derivedController = new DerivedController(); window. RootViewController = derivedController;
-
在模拟器上编译并运行应用程序。
-
在白色表面上点击并拖动鼠标指针,观察 MonoDevelop 的 应用程序输出 面板显示指针在模拟器屏幕上的当前位置。
它是如何工作的...
我们在这里所做的是创建一个可以用于多个 MonoTouch 项目的基控制器类。我们添加到这个控制器中的功能是响应用户触摸。任何继承它的控制器都将继承相同的功能。我们添加到创建BaseController
类的代码相当简单。我们实现的构造函数仅仅是 MonoDevelop 在项目中添加新视图控制器时在类实现中创建的构造函数的副本。这里只有一处细微的修改:
public BaseController (string nibName, NSBundle bundle) : base(nibName, bundle){}
这是当通过派生对象的DerivedController()
构造函数使用new
关键字初始化DerivedController
类时将被调用的基构造函数。
derivedController = new DerivedController();
还有更多...
派生控制器也可以添加到另一个XIB
文件中,并通过出口直接在代码中使用。
从 XIB 继承视图控制器
如果我们想要创建一个从包含在XIB
文件中的控制器派生的基控制器,过程是类似的。
参见
在本章中:
-
使用控制器加载视图
-
高效使用视图控制器
在本书中:
第二章,用户界面:视图:
- 添加和自定义视图
高效使用视图控制器
在这个食谱中,我们将学习关于高效使用视图控制器的基本指南。
准备工作
打开我们在本章前面“在标签中提供控制器”食谱中创建的项目TabControllerApp
。
如何做到...
-
在 Interface Builder 中打开
MainController.xib
文件,并添加一个UIButton
和一个UILabel
。通过出口将它们连接起来。 -
在
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 } }
-
在模拟器上编译并运行应用程序。
-
在主标签页上轻触按钮以显示我们列表的内容。
-
切换到设置标签。
-
在模拟器的菜单栏中点击硬件 | 模拟内存警告。
-
在 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
。
如何做到...
创建此项目的步骤如下:
-
将三个 iPhone 视图控制器文件添加到项目中,并分别命名为
MainController, SettingsController
和AfterMainController
。 -
在 Interface Builder 中
MainController
视图中添加一个UIButton
,并保存文档。 -
在
MainController
类中输入以下代码:public override void ViewDidLoad (){ base.ViewDidLoad (); this.Title = "Main"; this.buttonPush.TouchUpInside += delegate { this.NavigationController.PushViewController(new AfterMainController(), true); }; }
-
在
AppDelegate
类中添加以下字段:UINavigationController navController; UITabBarController tabController;
-
在
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;
-
在模拟器上编译并运行应用程序。在
MainController
中轻触按钮,将AfterMainController
推送到导航堆栈中,然后切换到 主 和 设置 标签。
工作原理...
完整的解决方案可以在 CombinedControllerApp
文件夹中找到。我们通过这个项目实现了提供三个不同屏幕的用户界面,这对用户来说不会造成困惑。
标签栏包含两个系统定义的项目,每个项目代表一个不同的视图控制器。我们在标签栏控制器中实现了第一个项目,使用导航控制器。这样,我们可以提供更多与特定部分的应用程序相关的屏幕(主 加 AfterMain),同时让应用程序的另一部分在任何时候都可以直接访问 (设置)。
更多内容...
本项目结合了三个不同的控制器(一个 UITabBarController
、一个 UINavigationController
和一个 UIViewController
)的方式是完全可接受的。我们甚至可以用另一个导航控制器替换第二个标签项,为应用程序的另一个部分提供更多的屏幕,或者甚至添加另一个标签项。
然而,正如本章中 在标签中提供控制器 的配方所述,如果我们在一个 UINavigationController
内添加一个 UITabBarController
,这是不可接受的。如果我们想在导航控制器内提供类似标签的行为,我们应该使用它的 UIToolbar
来实现。
参见
在本章中:
-
在不同视图控制器之间导航
-
在标签中提供控制器
-
为不同设备创建用户界面
iPad 视图控制器
在本配方中,我们将讨论仅适用于 iPad 的控制器。
准备工作
创建一个新的 iPad 空项目,并将其命名为 iPadControllerApp
。
如何实现...
-
将两个带有控制器的 iPad 视图添加到项目中,并分别命名为
FirstController
和SecondController
。为它们的背景视图设置不同的颜色。在SecondController
中,在其视图顶部添加一个UIToolbar
,并将其连接到一个出口。 -
在
AppDelegate
类中添加以下字段:UISplitViewController splitController; FirstController firstController; SecondController secondController;
注意
UISplitViewController
类仅适用于 iPad。 -
在
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;
-
在
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); } }
-
在
SecondController
类中添加一个属性,它返回我们在 步骤 1 中创建的工具栏出口:public UIToolbar SecToolbar{ get { return this.secToolbar; } }
-
最后,在模拟器中编译并运行应用程序。点击工具栏中的按钮,使
FirstController
出现。结果应该类似于以下截图:
它是如何工作的...
完整解决方案可以在 iPadControllerApp
文件夹中找到。有两个特定于 iPad 的控制器:UISplitViewController
和 UIPopoverController
。它们都被使用在这里,尽管 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
。
如何操作...
-
向项目中添加一个新的iPhone 视图控制器,并将其命名为
MainController
。 -
在 Interface Builder 中打开它,并在视图中为其添加一个标签和一个出口。
-
在标签中输入文本
在 iPhone 上运行!
-
将视图的背景颜色改为白色以外的颜色。同样对标签进行操作,并保存文档。
-
在
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!"; } }
-
在
AppDelegate
类的FinishedLaunching()
中添加以下代码:MainController mainController = new MainController(); window.RootViewController = mainController;
-
在模拟器上编译并运行应用程序。
-
读取标签的消息,表明它正在 iPhone 上运行。在 MonoDevelop 中终止执行,并在菜单栏上点击运行 | 运行方式 | iPad 模拟器 x.x(其中x.x是系统上安装的相应 iOS 版本,这里5.0)。
-
读取表明应用程序正在 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
。
如何操作...
-
将项目引用添加到
Mono.Data.Sqlite
程序集,并在命名空间上添加相应的using
指令:using Mono.Data.Sqlite;
-
在
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); } }
-
然后在
ViewDidLoad
方法中添加以下代码:string sqlitePath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "MyDB.db3"); this.btnCreate.TouchUpInside += delegate { this.CreateSQLiteDatabase (sqlitePath); };
它是如何工作的...
iOS 为 SQLite 数据库提供了原生支持。
-
我们可以使用 Mono 的
Mono.Data.Sqlite
命名空间来访问 SQLite 数据库,如下所示:using Mono.Data.Sqlite;
-
在
CreateSQLiteDatabase
方法内部,我们首先检查文件是否已存在:if (!File.Exists (databaseFile))
-
然后,我们可以继续创建数据库。我们首先使用
SqliteConnection.CreateFile(string)
静态方法创建文件,如下所示:SqliteConnection.CreateFile (databaseFile);
-
我们通过初始化一个
SqliteConnection
对象并调用其Open()
方法来连接新创建的文件。SQLite 数据库的连接字符串为Data Source =
,后跟数据库的文件名:using (SqliteConnection sqlCon = new SqliteConnection (String.Format ("Data Source = {0};", databaseFile))) sqlCon.Open();
-
在数据库中创建一个表时,会初始化一个
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
。它包含三个字段。FirstName
和LastName
字段类型为VARCHAR(20)
,而ID
字段类型为INTEGER
,同时也是表的PRIMARY KEY
。
除了使用 SQL 命令创建表之外,我们还可以使用各种商业或免费的 GUI 工具创建 SQLite 数据库。在互联网上简单搜索就会得到各种结果。
参见
在本章中:
-
查询 SQLite 数据库
-
插入和更新数据
-
使用现有的数据库
插入和更新数据
在这个菜谱中,我们将学习如何将数据写入数据库。
准备工作
对于这个任务,我们将扩展我们在上一个任务中创建的CreateSQLiteApp
项目。
如何操作...
-
在视图中添加两个额外的按钮。
-
在
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 表中插入和更新数据,我们分别使用常见的INSERT
和UPDATE
语句。代码中高亮的部分表示了 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 提供了出色的性能和可移植性,但它在很大程度上依赖于其宿主文件系统,无论它存储在哪个平台上。如果您想执行多个并发 INSERT
或 UPDATE
语句,请考虑使用 SqliteTransaction
。除了性能上的好处外,通过将多个语句批处理在一起,事务提供了一种在出现问题时回滚操作的方法。
参见
在本章中:
-
创建文件
-
创建 SQLite 数据库
-
查询 SQLite 数据库
查询 SQLite 数据库
在本配方中,我们将学习如何从 SQLite 数据库中检索数据。
准备工作
再次使用 CreateSQLiteApp
项目。完成此任务后,该项目将是一个完整的 SQLite 管理应用程序。
如何操作...
-
在视图中添加另一个按钮。
-
在
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 数据库不是线程安全的。如果我们想在执行查询的同时,另一个线程可能正在执行 INSERT
或 UPDATE
语句,那么提供某种同步机制会更好。
查询性能
虽然运行 iOS 平台的设备在性能上优于一些较老的桌面计算机,但它们的资源仍然有限。在查询大型数据库中的数据时,请考虑使用 SQL WHERE
语句缩小结果,以获取特定时间所需的数据。此外,如果不需要排序,请避免使用 ORDER BY
语句。
参见
在本章中:
-
创建 SQLite 数据库
-
插入和更新数据
-
使用现有的 SQLite 数据库
使用现有的 SQLite 数据库
在这个菜谱中,我们将讨论如何将现有的 SQLite 数据库文件包含到我们的项目中。
准备工作
使用某种前端创建数据库总是更容易。在这个任务中,我们将了解如何将现有的 SQLite 数据库与 iPhone 项目集成。创建一个新的 iPhone 单视图应用程序 项目,并将其命名为 SqliteIntegrationApp
。
如何做到这一点...
-
在 Interface Builder 中的视图上添加一个
UIButton
和一个UILabel
。将它们与适当的出口连接起来。确保标签足够高,可以显示六行,并在 检查器 选项卡中设置其 # Lines 字段。 -
在 MonoDevelop 中,在 解决方案 面板中右键单击项目,然后点击 添加文件...。
-
在显示的对话框中,导航到数据库文件所在的路径并选择它。文件将被添加到项目中。
-
右键单击它,然后选择 构建操作 | 内容。
-
在
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) { //... }
-
QueryData
方法的功能与之前任务中执行查询的方法相同。主要区别在于,在这个项目中,它将作为按钮的 TouchUpInside 事件处理程序。 -
添加一个类型为
string
的字段,用于存储数据库路径:private string database;
-
最后,在
ViewDidLoad
方法中:this.database = this.CopyDatabase (); this.buttonQuery.TouchUpInside += this.QueryData;
-
编译并运行应用程序。点击按钮,并观察数据库内容在标签中显示。
工作原理...
我们在这里所做的是将之前任务中讨论的各种实践相结合。当我们想在项目中添加各种文件时,我们设置它们的 构建操作 为 内容 非常重要。任何标记为 内容 的文件都会在应用程序包中按原样复制。在这种情况下,文件是一个 SQLite 数据库,因此我们需要在运行时对它有写入权限。我们需要将其复制到应用程序的 Documents 文件夹中。这就是 CopyDatabase()
方法在这一点上所做的事情:
if (!File.Exists (databasePath)){
File.Copy (databaseFile, databasePath);
}
检查文件是否已经存在非常重要,这样它就不会在应用程序下次执行时被覆盖。
还有更多...
如果遇到 SqliteException
,首先检查是否由于某种原因数据库文件没有被复制到正确的文件夹。
参见
在本章中:
-
创建文件
-
创建 SQLite 数据库
使用序列化存储数据
在这个菜谱中,我们将讨论使用 .NET 序列化来存储 C# 对象。
准备工作
在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序 项目。将其命名为 SerializationApp
。
如何做到这一点...
-
在视图上添加两个按钮和一个标签,在 Interface Builder 中。在
SerializationAppViewController.cs
文件中添加以下使用指令:using System.IO; using System.Runtime.Serialization.Formatters.Binary;
-
在
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
类是一个简单的对象,它包含一个整数和两个字符串属性。
-
要序列化对象,我们首先初始化一个
MemoryStream
:using (MemoryStream ms = new MemoryStream ())
-
然后我们使用
BinaryFormatter
类来序列化对象并将其存储到流中: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);
-
要从缓冲区反序列化对象,过程类似,但顺序相反。在
MemoryStream
初始化后,我们将流的位置重置为其开始处,并使用BinaryFormatter
的Deserialize
方法。它返回一个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
。
如何操作...
-
在视图中添加一个
UIButton
和一个UILabel
。将上一个任务中的CustomerData
类添加到项目中。在XMLDataAppViewController.cs
文件中添加以下using
指令:using System.Xml.Serialization; using System.Xml; using System.Text;
-
在
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 存储数据有两个主要优势:
-
输出是纯文本,与二进制序列化不同,这使得它易于阅读。
-
数据可以传输或接收来自不同类型的应用程序、网站等,这些应用程序和网站不一定是用 C#编写的。
反序列化
要从 XML 反序列化我们的对象,我们将使用以下行:
custData = (CustomerData)xmlSer.Deserialize (new StringReader (sb.ToString ()));
相关内容
在本章中:
-
使用序列化存储数据
-
使用 LINQ to XML 管理 XML 数据
使用 LINQ to XML 管理 XML 数据
在这个配方中,我们将学习如何使用语言集成查询(LINQ)来管理 XML 数据。
准备工作
在这个任务中,我们将使用上一个任务中的项目XMLDataApp
。在 MonoDevelop 中打开它。
如何操作...
-
在视图中添加另一个
UIButton
。在XMLDataAppViewController.cs
文件中添加对System.Xml.Linq
程序集的引用和以下using
指令:using System.Linq.Xml;
-
在
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 数据方面非常灵活且直观。
-
我们首先从我们的 XML 数据创建一个
XDocument
:XDocument xDoc = XDocument.Parse (this.sb.ToString ());
-
然后,我们对其
Descendants()
方法创建一个查询,该方法返回一个IEnumerable<XElement>
对象。每个XElement
对象对应一个 XML 元素。 -
使用返回的每个
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 };
-
要检索创建的对象,我们在查询上调用扩展方法
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
。
如何做到这一点...
-
在 Interface Builder 中打开
MainController.xib
文件。 -
在主视图中添加一个
UILabel
和一个UIPickerView
。 -
保存文档。
-
在 MonoDevelop 中,创建一个继承自
UIPickerViewModel:
的MainController
类中的嵌套类:private class PickerModelDelegate : UIPickerViewModel
-
在嵌套类中添加以下构造函数和字段:
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;
-
您现在需要覆盖
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)
-
-
最后,在控制器中的
ViewDidLoad
方法内将创建的模型对象设置为 picker 视图的Model
属性:this.picker.Model = new PickerModelDelegate (this);
完整的代码可以在PickerViewApp
项目中找到。
它是如何工作的...
UIPickerViewModel
类在 Objective-C
中不存在。MonoTouch 提供了这个类,作为原生协议 UIPickerViewDataSource
和 UIPickerViewDelegate
的包装器,并包含这两个类的所有方法,以便我们重写。这非常有帮助,因为我们只需要实现和分配一个类,而不是两个类来为我们的选择视图。这两个协议同时作为类在 MonoTouch 中可用。
在构造函数中,我们初始化将包含要显示在选择器中的数据的列表。我们需要重写的四个类负责显示数据:
-
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):
这个方法返回当用户从选择视图的任何组件/行组合中选择项目时要采取的操作。
我们使用在构造函数中分配的列表来显示数据。例如,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.
如何做到这一点...
-
将带有控制器的视图添加到项目中,并将其命名为
TableController
。 -
将
TableController
类的继承从UIViewController
更改为UITableViewController:
public partial class TableController : UITableViewController
-
在 Interface 中打开
TableController.xib
文件。 -
删除文档的
UIView
,并在其位置添加一个UITableView
。 -
将
TableController
的输出端口view
连接到添加的表格视图。 -
保存文档。
-
在 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; } }
-
重写控制器的
ViewDidLoad
方法,并添加以下代码行:this.TableView.Source = new TableSource ();
完整的代码可以在 TableViewApp
项目中找到。
它是如何工作的...
我们创建的嵌套类充当 UITableView
的数据源。它继承自 MonoTouch 的 UITableViewSource
类:
private class TableSource : UITableViewSource
注意
与之前讨论的示例中的 UIPickerView
类似,UITableViewSource
类在 Objective-C
中不存在。它仅仅是 MonoTouch 提供的围绕 UITableViewDelegate
和 UITableViewSource
协议的包装对象。
在其构造函数中,我们初始化两个变量。一个字符串将作为单元格的标识符,以及一个用于数据源的通用 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.
如何做...
-
将上一个任务中的项目中的
TableSource
类复制并粘贴到TableController
类内部。 -
在
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;
-
删除
cellID
字段并添加一个新的:private Dictionary<int, UITableViewCellStyle> cellStyles;
-
在构造函数中初始化,如下所示:
this.cellStyles = new Dictionary<int, UITableViewCellStyle>() { {0, UITableViewCellStyle.Default}, {1, UITableViewCellStyle.Subtitle}, {2, UITableViewCellStyle.Value1}, {3, UITableViewCellStyle.Value2} };
-
在数据源对象中添加另一个
KeyValuePair
:{3, "Recordings"}
-
在模拟器上编译并运行应用程序。输出应该类似于以下内容,如以下截图所示:
它是如何工作的...
一个表格单元格可以有四种不同的单元格样式,这些样式由UITableViewCellStyle
枚举表示。其值如下:
-
默认:
这是默认单元格样式。只能使用TextLabel
属性来显示文本。 -
副标题:
这是一种提供DetailTextLabel
作为TextLabel
副标题的样式。 -
Value1:
这是显示TextLabel
和DetailTextLabel
文本大小相同、颜色不同且对齐到单元格两侧的样式。 -
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
中进行进一步的自定义。单元格包含的所有视图,包括 TextLabel
和 DetailTextLabel
,都是单元格视图的子视图,该视图通过其 ContentView
属性公开。我们可以创建自定义视图并将其作为子视图添加到其中。
UITableViewCell
类的其他有用属性
除了在默认标签中添加文本外,UITableViewCell
还包含一些其他属性,我们可以设置它们的值,以在单元格中添加更多默认项:
-
ImageView:
这个属性接受一个UIImageView
。我们可以用它来在单元格的左侧显示一个图像。 -
AccessoryView:
这个属性接受任何UIView
实例。它的默认位置是单元格的右侧,在单元格的Accessory
位置,位于单元格的右侧。 -
Accessory:
这个属性接受UITableViewCellAccessory
类型的值。它为单元格的附件提供预定义视图,例如DetailDisclosureButton
或Checkmark
。
参见
在本章中:
-
在表格中显示数据
-
在表格中编辑数据
编辑表格:删除行
在这个菜谱中,我们将讨论如何从 UITableView
中删除行,并提供适当的用户反馈。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 EditingTableDataApp
。
如何操作...
-
将视图控制器添加到项目中,并将其转换为本章中 在表格中显示数据 部分所述的
UITableViewController
,并将其命名为TableController
。 -
在
AppDelegate
类中添加一个UINavigationController
。初始化它,将TableController
设置为其根控制器:TableController tableController; UINavigationController navController; //… this.tableController = new TableController(); this.navController = new UINavigationController(this.tableController); window.RootViewController = this.navController;
-
在 MonoDevelop 中,在
TableController
类中添加三个字段:List<string> tableData
、UIBarButtonItem buttonEdit
和UIBarButtonItem 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); }
-
为表格创建适当的表格视图源,它在构造函数中接受
tableData
泛型List
作为参数。创建处理方法ButtonEdit_Clicked
,并在其中输入以下代码:this.TableView.SetEditing (true, true); this.NavigationItem.SetRightBarButtonItem (this.buttonDone, true);
-
创建处理方法
ButtonDone_Clicked
,并在其中输入以下代码:this.TableView.SetEditing (false, true); this.NavigationItem.SetRightBarButtonItem (this.buttonEdit, true);
-
最后,重写表格源代码中的
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 中打开它。
如何操作...
-
在
TableController
类中添加另一个UIBarButtonItem
字段,并在ViewDidLoad
方法中初始化它:this.buttonAdd = new UIBarButtonItem (UIBarButtonSystemItem.Add, this.ButtonAdd_Clicked);
-
添加处理方法
ButtonAdd_Clicked:
private void ButtonAdd_Clicked (object sender, EventArgs e){ this.tableData.Add ("Recordings"); this.TableView.ReloadData (); }
-
在
ButtonEdit_Clicked
方法中添加以下行:this.NavigationItem.SetLeftBarButtonItem (this.buttonAdd, true);
-
还需要在
ButtonDone_Clicked
方法中添加以下行:this.NavigationItem.SetLeftBarButtonItem (null, true);
-
在模拟器上编译并运行应用程序。
-
点击编辑按钮,你会看到添加按钮出现在导航栏的左侧。点击它,你会看到新行被添加到表格中。
它是如何工作的...
这里使用了一个系统默认的添加按钮。当用户点击按钮时,表格中会添加一行。按钮作为导航栏中的左侧按钮添加到编辑按钮的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
方法中。需要做的只是使用sourceIndexPath
和destinationIndexPath
参数,在数据源中移除并重新插入项目到期望的索引。
参见
在本章中:
-
在表格中显示数据
-
编辑表格:删除行
表索引
在本教程中,我们将学习如何在表格中提供一个索引,使用户能够更快地浏览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
。
如何实现...
-
在 Interface Builder 中打开
TableController.xib
文件。 -
在
UITableView
中添加搜索栏和搜索显示控制器。请注意,在此操作之后,将自动创建并连接一些输出。我们需要大多数它们,所以我们保留它们原样并保存文档。 -
回到 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; } }
-
重写
ViewDidLoad
方法,并在其中分配源和委托对象:this.TableView.Source = new TableSource (this); this.SearchDisplayController.SearchResultsSource = new TableSource(this); this.SearchDisplayController.Delegate = new SearchDelegate(this);
-
你可以在
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
。
如何实现...
-
在 Interface Builder 中打开
MainController.xib
文件,并在主视图中添加一个UIWebView
对象。 -
创建并连接一个名为
webView
的出口。 -
保存文档。
-
在
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); }
-
在模拟器上编译并运行应用程序。观察屏幕上网站加载的情况!
它是如何工作的...
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
。
如何实现...
-
在
MainController
的主视图中添加一个UIWebView
,并保存文档。 -
在项目中添加一个新的文件夹,并将其命名为
html_content
。 -
通过 MonoDevelop 将您的内容添加到该文件夹中。别忘了将每个文件的 构建操作 设置为 内容。
-
在
MainController
类中重写ViewDidAppear
方法,并输入以下代码:NSUrl fileUrl = NSUrl.FromFilename ( "./html_content/T-Shirts.html"); NSUrlRequest urlRequest = new NSUrlRequest (fileUrl); this.webView.ScalesPageToFit = false; this.webView.LoadRequest (urlRequest);
-
在模拟器上编译并运行应用程序。
-
查看屏幕上显示的 HTML 内容。
-
放大以查看更大的内容,就像您在线内容中做的那样。
工作原理...
显示本地内容的过程与显示在线内容相同。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 中打开它。
如何操作...
-
在
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);
-
在模拟器上编译并运行应用程序。
-
观察 HTML 字符串的显示。
工作原理...
如同在 第二章 中讨论的,用户界面:视图,UITextView
可以用来显示大块文本并进行编辑,但它不能显示格式化文本。UIWebView
可以通过将我们的 HTML 格式化文本作为参数传递给 LoadHtmlString
方法来实现这一点:
this.webView.LoadHtmlString (htmlString, null);
第二个参数的类型是 NSUrl
。由于我们在代码中创建了 HTML 字符串,并且没有对其他文件的引用,所以我们不需要它,因此我们只需传递 null
。
还有更多...
如果我们想在 HTML 字符串内部引用外部文件,我们应该将 LoadHtmlString
的 NSUrl
参数设置为包含文件的路径,从而设置 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
。
如何做到这一点...
-
在 Interface Builder 中打开
MainController.xib
文件并添加一个UIButton
。 -
保存文档。
-
在项目中添加一个名为
docs
的文件夹,并将一些文档文件放入其中。项目DocumentPreviewApp
包含三种不同的文档:一个PDF
、一个DOCX
和一个XLSX
。 -
在
MainController.cs
文件中输入以下using
指令:using MonoTouch.QuickLook;
-
在
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]; } }
-
在
MainController
的ViewDidLoad
重写中输入以下代码: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
在此类中,我们必须重写两个属性,这两个属性都代表要预览的项目信息。这些是 ItemTitle
和 ItemUrl
。
当控制器调用 PreviewItemCount
方法并且返回一个大于 1
的数字时,它会添加一个带有两个箭头按钮的 UIToolbar
,允许用户在文档之间导航。当调用 GetPreviewItem
方法时,它会将当前标题设置为 ItemTitle
属性,并根据 ItemUrl
属性加载文档。如果在此应用程序中点击按钮,结果将类似于以下内容:
此截图显示了导航到最后一个文档(XLSX
类型的文件)后的 QLPreviewController
。
更多...
控制器在其导航栏上包含一个系统默认的完成按钮。如果按下该按钮,控制器将自动关闭。我们可以通过其WillDismiss
和/或DidDismiss
事件来提供额外的行为。
参见
在本章中:
-
显示本地内容
-
显示格式化文本
第六章:网络服务
在本章中,我们将涵盖:
-
消费网络服务
-
调用网络服务
-
消费 WCF 服务
-
读取 JSON 数据
简介
向用户提供在线信息是移动开发的关键部分。在本章中,我们将讨论开发与网络服务通信以提供信息的应用程序。我们将看到如何基于 SOAP 消费和调用网络服务,还将讨论如何使用 WCF 网络服务以及如何从网络服务器解析流行的 JSON 数据格式。
本章中的所有示例都使用与 Mono 框架一起提供的 xsp 轻量级网络服务器,因此无需在线上运行一个实时网络服务来使用提供的代码。
消费网络服务
在本配方中,我们将学习如何在 MonoTouch 项目中使用 SOAP 网络服务。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为WebServiceApp
。本章的代码包含一个名为MTWebService
的网络服务项目。这就是将要使用的网络服务。
如何操作...
-
要使用
MTWebService
网络服务,我们需要一个网络服务器。Mono 框架提供了用于测试目的的 xsp 轻量级网络服务器。 -
打开一个终端,并输入以下命令以进入网络服务的目录,将
<code_directory>
替换为下载的代码所在的路径:cd <code_directory>/CH06_code/MTWebService/MTWebService
-
通过在提示符中输入
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.
-
现在,我们需要在项目中添加对网络服务的引用。在解决方案面板中右键单击项目,然后选择添加 | 添加 Web 引用。
-
在将显示的对话框中,添加以下截图提供的信息:
-
在将引用添加到MTTestWebService网络服务后,在
MainController
的视图中添加一个按钮和一个标签。覆盖MainController
类的ViewDidLoad
方法,并在其中输入以下代码:this.buttonFetchData.TouchUpInside += delegate { using (MTTestWebService webService = new MTTestWebService()){ this.lblMessage.Text = webService.GetMessage ("Hello Web Service!"); } } ;
-
最后,为我们的网络服务命名空间提供一个
using
指令:using WebServiceApp.mtWebService;
-
在模拟器上编译并运行应用程序。
-
点击按钮以调用网络服务,并注意标签中的输出消息。
工作原理...
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
网络服务的引用,具体操作如前一个任务所述。
如何做到...
-
在
MainController
的视图中添加一个标签和一个按钮。 -
在 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); } ;
-
最后,添加以下方法:
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); } ); }
-
在模拟器上编译并运行应用程序。
-
点击按钮,查看网络服务的结果在标签上显示。
它是如何工作的...
正如你可能已经注意到的,在前一个任务中,当应用程序与网络服务通信时,它冻结了,直到收到结果。在这个任务中,我们使用异步调用,这样在应用程序联系网络服务时用户界面就不会冻结。
我们希望在应用程序从网络服务收到响应时得到通知,但我们还需要它的结果。在调用我们感兴趣的 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 方法进行重命名。在这种情况下,这些方法被命名为 BeginMultiplyNumbers
和 EndMultiplyNumbers
。
错误处理
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
。
如何操作...
-
将
System.Runtime.Serialization
和System.ServiceModel
的引用添加到项目中,并在MainController.cs
文件中添加它们对应的using
指令。 -
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 项目中。 -
在
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; } ;
-
最后,添加以下事件处理器:
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); } ); }
-
在模拟器上编译并运行应用程序。
-
点击按钮,并观察从服务返回的数据填充到标签中。
它是如何工作的...
MonoTouch 依赖于 Mono Framework 对 WCF 服务的支持,但这并不完整。然而,仅 WCF 服务可以在 iOS 应用程序中使用这一事实就使 MonoTouch 对.NET 开发者更具吸引力。
例如,没有工具可以在 Mac 上创建客户端代理,因此我们必须能够访问 Windows 机器来执行此操作,使用Silverlight 服务模型代理生成工具(slsvcutil.exe)。该工具生成的源文件允许我们在项目中消耗 WCF 服务。它基本上做了 MonoDevelop 在我们添加 Web 引用到 ASMX Web 服务时自动执行的事情,就像在前两个任务中一样。
注意
重要的一点是使用Silverlight 版本 3.0的slsvcutil
来创建客户端代理。
除了 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 服务的项目时,请记住设置服务运行在的机器的地址,而不是localhost
或127.0.0.1
。这是因为当我们将应用程序运行在设备上时,应用程序将无法连接到服务。
关于 MonoDevelop 的 WCF 支持的更多信息
在消耗 Web 服务配方中显示的添加 Web 引用窗口中,可以通过 MonoDevelop 添加 WCF Web 引用。然而,它尚未完成。
WCF 服务创建
从 WcfService
服务返回的对象以及实际的服务本身都是在 Mac 上使用 MonoDevelop 完全创建的。由于没有 WCF 项目模板,使用了 空项目 模板。
相关内容
在本章中:
- 消费 Web 服务
读取 JSON 数据
在本食谱中,我们将学习如何读取 JavaScript 对象表示法 (JSON ) 数据。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 JsonDataApp
。在 MainController
的视图中添加一个按钮和一个标签。
如何实现...
-
将项目添加到
System.Json
程序集的引用中。 -
在
MainController.cs
文件中添加以下using
指令:using System.Json; using System.Net; using System.IO;
-
输入以下方法:
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); }
-
将处理程序附加到按钮的
TouchUpInside
事件,并在其中输入以下代码:JsonValue json = this.GetJsonObject (); this.labelResponse.Text = String.Format ("File name: {0}\n Description: {1}", json ["filename"], json ["description"]);
-
最后,在项目目录中运行
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
。
如何操作...
-
在
MainController
的主视图中添加一个UIImageView
和一个UIButton
。 -
覆盖
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); } ;
-
实现处理
FinishedPickingMedia
和Canceled
事件的处理器方法: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); }
-
在模拟器上编译并运行应用程序。
-
点击按钮以显示图像选择器,并通过点击缩略图选择图像。图像将在图像视图中显示。
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 模拟器上不可用相机功能。此示例只能在设备上运行。有关更多信息,请参阅第十四章部署。
如何做...
-
在
ViewDidLoad
方法内部,替换以下行:this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
-
使用以下代码块:
if (UIImagePickerController.IsSourceTypeAvailable( UIImagePickerControllerSourceType.Camera)){ this.imagePicker.SourceType = UIImagePickerControllerSourceType.Camera; } else{ this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary; }
-
在
FinishedPickingMedia
处理程序中,在关闭图片选择器之前添加以下代码:pickedImage.SaveToPhotosAlbum(delegate( UIImage image, NSError error) { if (null != error){ Console.WriteLine("Image not saved! Message: {0}", error.LocalizedDescription); } } );
-
在设备上编译并运行应用程序。
-
点击按钮打开相机并拍照。照片将被保存到设备相册中。
它是如何工作的...
在展示相机取景器之前,我们必须确保应用程序运行的实际设备确实具有适当的硬件。我们通过调用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
。
如何做...
-
在
MainController
的主视图中添加一个按钮。 -
将视频文件添加到项目中,并将其构建操作设置为内容。
-
在
MainController.cs
文件中输入以下using
指令:using MonoTouch.MediaPlayer;
-
覆盖
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(); } ;
-
在
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."); }
-
在模拟器上编译并运行应用程序。
-
点击按钮,视频将加载并开始播放。在 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);
此属性告诉我们播放状态,例如Paused
、Playing
、SeekingForward
、SeekingBackward
等。
更多...
除了这个例子中使用的之外,我们还可以为MPMoviePlayerController
添加更多通知的观察者,其中一些是:
-
DidEnterFullscreenNotification:
此通知表示用户点击了全屏控制,控制器已进入fullscreen
模式。 -
DidExitFullscreenNotification:
此通知表示控制器已离开fullscreen
模式。 -
DurationAvailableNotification:
此通知表示控制器已收到视频持续时间的信息。 -
LoadStateDidChangeNotification:
此通知对于网络播放很有用,当控制器在缓冲区中完成预加载媒体时被触发。 -
NaturalSizeAvailableNotification:
当电影帧的尺寸可用时,此通知被触发。大小可以通过播放器的NaturalSize
属性检索。 -
NowPlayingMovieDidChangeNotification:
当播放器的视频内容发生变化时,此通知被触发。当前内容可以通过其ContentUrl
属性获取。
无线流媒体
从 iOS 版本 4.3 开始,MPMoviePlayerController
可以用来将视频流式传输到 Apple 的 AirPlay 兼容设备。要启用它,将其 AllowsAirPlay
属性设置为 true
。当 MPMoviePlayerController
显示时,它将提供一个界面,允许用户选择它检测到的设备。
相关内容
在本章中:
- 播放音乐和声音
播放音乐和声音
在本教程中,我们将学习如何播放存储在设备上的简单音频文件和歌曲。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 PlayMusicApp
。
注意
此示例在模拟器上无法工作。您还需要在设备上至少存储一首歌曲。
如何操作...
-
在
MainController
的视图中添加三个按钮。 -
在
MainController.cs
文件中添加以下using
指令:using MonoTouch.MediaPlayer;
-
在类中添加两个字段:
private MPMusicPlayerController musicPlayerController; private MPMediaPickerController mediaPicker;
-
重写
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(); } ;
-
添加以下方法:
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); }
-
在设备上编译并运行应用程序。
-
点击 选择歌曲 按钮,并选择一首或多首歌曲。
它是如何工作的...
MPMediaPickerController
提供与原生 iPod 应用程序相同的用户界面。MPMusicPlayerController
负责播放设备上存储的歌曲。
我们首先初始化媒体选择器,通过其构造函数传递我们想要它查找的媒体类型:
this.mediaPicker = new MPMediaPickerController(MPMediaType.Music);
之后,我们订阅其 ItemsPicked
和 DidCancel
事件,以便我们可以捕获用户的反馈:
this.mediaPicker.ItemsPicked += MediaPicker_ItemsPicked;
this.mediaPicker.DidCancel += MediaPicker_DidCancel;
突出的代码显示了如何初始化音乐播放器对象。这里演示的选项 MPMusicPlayerController.ApplicationMusicPlayer
创建了一个仅针对应用程序的实例。另一个可用的选项 MPMusicPlayerController.iPodMusicPlayer
创建了一个实例,允许在应用程序处于后台时播放媒体,类似于 iPod 应用程序。
在 MediaPicker_ItemsPicked
处理程序中,我们通过其 SetQueue
方法将用户选择的歌曲设置到音乐播放器中:
this.musicPlayerController.SetQueue(e.MediaItemCollection);
之后,我们取消显示模态媒体选择器控制器。播放和停止歌曲分别通过 MPMusicPlayerController
的 Play()
和 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
。
注意
此示例在模拟器上无法工作。
如何操作...
-
在
MainController
的视图中添加两个按钮。 -
在
MainController.cs
文件中输入以下using
指令:using System.IO; using MonoTouch.AVFoundation; using MonoTouch.AudioToolbox;
-
覆盖
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(); } ;
-
在设备上编译并运行应用程序。
-
点击 开始录制 按钮开始录制音频,例如,可以说些话来录制你的声音。
-
点击 停止录制 按钮停止录制并播放回放。
工作原理...
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
。
注意
此示例适用于模拟器。照片相册中必须至少存在一个图像。
如何操作...
-
在
MainController
的主视图中添加一个按钮。 -
在
MainController.cs
文件中输入以下using
指令:using MonoTouch.AssetsLibrary;
-
重写
ViewDidLoad
方法,并在其中输入以下代码:this.buttonEnumerate.TouchUpInside += delegate { this.assetsLibrary = new ALAssetsLibrary(); this.assetsLibrary.Enumerate(ALAssetsGroupType.All, this.GroupsEnumeration, this.GroupsEnumerationFailure); } ;
-
在类中添加以下方法:
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); } }
-
编译并运行应用程序。
-
点击 枚举资产 按钮,并观察保存照片的 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 数据
我们可以通过 ALAssetRepresentation
的 Metadata
属性读取照片的 交换图像文件格式 (EXIF) 元数据,Metadata
属性的类型为 NSDictionary
,如下所示:
NSDictionary metaData = asset.DefaultRepresentation.Metadata;
if (null != metaData){
NSDictionary exifData = (NSDictionary)metaData[ new NSString("{Exif}")];
}
获取单个资产
如果我们知道资产的 URL,我们也可以通过 ALAssetLibrary
的 AssetForUrl
方法检索单个资产。
参见
在本章中:
- 选择图片和视频
第八章。集成 iOS 功能
在本章中,我们将介绍以下内容:
-
开始电话通话
-
发送短信和电子邮件
-
在我们的应用程序中使用短信
-
在我们的应用程序中使用电子邮件消息
-
管理地址簿
-
显示联系人
-
管理日历
简介
移动设备为用户提供了一系列功能。创建一个与应用程序这些功能交互以向用户提供完整体验的应用程序当然可以被视为一种优势。
在本章中,我们将讨论 iOS 的一些最常见功能以及如何将它们的一些或全部功能集成到我们的应用程序中。我们将看到如何使用原生平台应用程序或通过在我们的项目中集成原生用户界面来使用户能够进行电话通话、发送短信和电子邮件。此外,我们还将讨论以下组件:
-
MFMessageComposeViewController:
这个控制器适合发送文本(SMS)消息 -
MFMailComposeViewController:
这是一个用于发送带或不带附件的电子邮件的控制器 -
ABAddressBook:
这是一个提供我们访问地址簿数据库的类 -
ABPersonViewController:
这是一个显示和/或编辑地址簿中联系人信息的控制器 -
EKEventStore:
这是一个负责管理日历事件的类
此外,我们还将学习如何读取和保存联系人信息,如何显示联系人详细信息,以及如何与设备日历交互。
注意,本章中的一些示例可能需要设备。例如,模拟器不包含消息应用程序。要将应用程序部署到设备,您需要通过 Apple 的开发者门户注册为 iOS 开发者,并获得 MonoTouch 的商业许可证。
开始电话通话
在本食谱中,我们将学习如何调用原生电话应用程序,允许用户进行通话。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 PhoneCallApp
。
注意
原生电话应用程序在模拟器上不可用。它仅在 iPhone 设备上可用。
如何操作...
-
在
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); } } ;
-
在设备上编译并运行应用程序。点击 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
。
如何操作...
-
在
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!"); } } ;
-
在设备上编译并运行应用程序。点击其中一个按钮以打开相应的应用。
它是如何工作的...
再次使用 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
。
如何做到这一点...
-
在
MainController
的视图中添加一个按钮。在MainController.cs
文件中输入以下指令:using MonoTouch.MessageUI;
-
使用以下代码实现
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!"); } } ; }
-
添加以下嵌套类:
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); } }
-
在设备上编译并运行应用程序。
-
点击发送消息按钮以打开消息控制器。点击发送按钮发送消息,或点击取消按钮返回应用程序。
它是如何工作的...
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
参数提供功能。它的值可以是以下三个之一:
-
Sent:
这个值表示消息已成功发送。 -
Cancelled:
这个值表示用户点击了取消按钮,消息将不会发送。 -
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
。
如何做到这一点...
-
在
MainController
的视图中添加一个按钮,并在MainController.cs
文件中的MonoTouch.MessageUI
命名空间中。 -
在
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!"); } } ;
-
添加以下方法:
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); }
-
在模拟器或设备上编译并运行应用程序。
-
点击发送电子邮件按钮以显示邮件用户界面。发送或取消消息。应用程序将在模拟器上工作,并且行为与设备上的原生邮件应用相同,只是消息实际上不会发送或保存。
它是如何工作的...
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
。
如何操作...
-
在
MainController
的视图中添加一个按钮。在MainController.cs
文件中输入以下using
指令:using MonoTouch.AddressBook;
-
覆盖
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)); } } ; }
-
在模拟器上编译并运行应用程序。
-
点击 获取联系人 按钮,并观察联系人名称在 MonoDevelop 的 应用程序输出 面板中显示。
注意
在安装 iOS SDK 之后,模拟器不包含任何联系人。您可以像在设备上一样添加联系人。
如何工作...
MonoTouch.AddressBook
命名空间包含所有允许我们管理设备地址簿的类。要直接访问数据,我们需要 ABAddressBook
类的一个实例:
ABAddressBook addressBook = new ABAddressBook();
要获取地址簿中存储的所有联系人,我们调用其 GetPeople()
方法:
ABPerson[] contacts = addressBook.GetPeople();
此方法返回一个 ABPerson
对象数组,其中包含所有单个联系人的信息。要读取联系人的详细信息,我们遍历 ABPerson
数组,并使用 FirstName
和 LastName
属性分别获取每个联系人的名和姓:
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
的视图中添加一个按钮。
如何做到这一点...
-
在
AppDelegate
类中为UINavigationController
创建一个字段:UINavigationController navController;
-
实例化导航控制器,将其根控制器传递为
MainController
的实例:this.navController = new UINavigationController(new MainController());
-
将导航控制器设置为窗口的根视图控制器:
window.RootViewController = this.navController;
-
在
MainController.cs
文件中添加命名空间MonoTouch.AddressBook
和MonoTouch.AddressBookUI
。 -
覆盖
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); } ;
-
在模拟器或设备上编译并运行应用程序。
-
点击显示第一个联系人按钮以显示联系人详细信息。
它是如何工作的...
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
应用程序未安装在模拟器上。
如何操作...
-
在
MainController
的主视图中添加一个按钮。在MainController.cs
文件中添加命名空间MonoTouch.EventKit
。 -
最后,在
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); } } ); } ;
-
在设备上编译并运行应用程序。
-
点击 显示事件 按钮以在 应用程序输出 面板中输出未来 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
方法来保存它。
关于日历的信息
默认情况下,设备已设置两个日历:Home
和 Work
。尽管我们无法在设备上创建新日历,但我们在用于同步设备的计算机上创建的新日历在同步时将自动添加。
相关内容
在本书中:
第七章, 多媒体资源:
- 直接管理专辑项目
第九章.与设备硬件交互
在本章中,我们将涵盖:
-
检测设备方向
-
调整 UI 方向
-
接近传感器
-
获取电池信息
-
处理运动事件
-
处理触摸事件
-
识别手势
-
自定义手势
-
使用加速度计
-
使用陀螺仪
简介
今天的移动设备配备了非常先进的硬件。无论是加速度计来检测运动和方向,接近传感器,GPS 模块,以及许多其他组件,多触控屏幕相当复杂。
在本章中,我们将重点关注如何在我们的应用程序中使用这些硬件,为用户提供一个扩展到 3D 世界的体验。具体来说,我们将讨论如何根据设备的位置调整用户界面方向,如何使用接近传感器,以及读取电池信息。在一系列四个任务中,我们将学习如何捕获屏幕上的用户触摸并识别手势。
最后但同样重要的是,我们将创建高级应用程序,从加速度计和陀螺仪传感器读取原始数据以检测设备运动和旋转,并提供详细且简单的指南。
检测设备方向
在这个菜谱中,我们将学习如何制作一个能够感知设备方向变化的应用程序。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为DeviceOrientationApp
。
如何做到这一点...
-
在
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(); }
-
在模拟器上编译并运行应用程序。
-
通过在 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:
这个值指定设备与地面平行,屏幕向下
FaceUp
和FaceDown
是模拟器上无法复制的两个值。
设备方向和用户界面方向
在这个例子中可以明显看出,设备方向和用户界面方向之间存在差异。如果设备旋转,标签会更新为新方向值,但用户界面不会对变化做出响应。在下一个菜谱中,我们将讨论如何旋转用户界面。
参见
在本章中:
-
调整 UI 方向
-
使用加速度计
调整 UI 方向
在这个菜谱中,我们将学习如何根据屏幕方向旋转用户界面 (UI)。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为UIOrientationApp
。
如何操作...
-
在
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(); }
-
在模拟器上编译并运行应用程序。
-
使用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
本身之外,不需要任何控件。
-
声明一个
NSObject
字段,它将包含通知观察者:private NSObject proximityObserver;
-
在
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); } ); }
-
最后,在
ViewDidUnload
重写方法中输入以下代码:if (UIDevice.CurrentDevice.ProximityMonitoringEnabled){ NSNotificationCenter.DefaultCenter. RemoveObserver(this.proximityObserver); UIDevice.CurrentDevice.ProximityMonitoringEnabled = false; }
-
在设备上编译并运行应用程序。
-
将手指放在接近传感器上,或者就像在通话时那样,将其靠近你的耳朵。观察 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
。
如何做到...
-
在
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); } ); }
-
在设备上编译并运行应用程序。
-
应用程序加载后,断开并/或连接设备的 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
。
如何做到...
-
在
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!"; }
-
在设备上编译并运行应用程序。
-
摇动设备并观察标签上的输出。你还可以在模拟器上测试这个应用程序。
-
加载完成后,点击菜单栏上的 硬件 | 摇动手势。
它是如何工作的...
通过重写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
类的对象才会发送运动事件。这包括UIView
和UIViewController
类。
更多信息:运动事件
运动事件机制相当简单。它仅检测几乎瞬间的设备摇动,而不提供有关其方向或速率的任何信息。为了根据不同的特性处理运动事件,可以将加速度计与组合使用。
相关内容
在本章中:
- 使用加速度计
处理触摸事件
在本食谱中,我们将学习如何拦截和响应用户触摸。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为TouchEventsApp
。
如何实现...
-
在
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); } }
-
在模拟器上编译并运行应用程序。
-
在模拟器屏幕上使用光标进行侧向点击和拖动,并观察视图的背景颜色逐渐从白色变为黑色。请注意,在模拟器屏幕上使用光标点击相当于用手指触摸设备的屏幕。
它是如何工作的...
为了响应用户的触摸,作为触摸接收器的对象必须将其 UserInteractionEnabled
属性设置为 true
。几乎每个对象默认都启用了用户交互,除非其主要用途不是直接用于用户交互,例如,UILabel
和 UIImageView
。我们需要明确地将 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
。
如何做到这一点...
-
在
MainController
的视图中添加一个标签。在MainController
类的源文件中输入以下using
指令:using MonoTouch.ObjCRuntime;
-
在
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; } }
-
在模拟器上编译并运行应用程序。
-
按住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
。
如何操作...
-
在
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; } } }
-
使用前面食谱中展示的自定义手势识别器。
工作原理...
要创建一个手势识别器,声明一个继承自 UIGestureRecognizer
类的类。在这个例子中,我们创建一个手势,通过在屏幕上拖动手指指向左下角的一个 50x50
点矩形来识别这个手势。
private class DragLowerLeftGesture : UIGestureRecognizer
UIGestureRecognizer
类包含我们在视图中拦截触摸事件时使用的相同触摸方法。我们还可以通过其 View
属性访问它所添加的视图。在 TouchesBegan
方法中,我们确定初始触摸位置。如果它在视图的左下部分之外,我们将 State
属性设置为 Began
。如果它在左下部分内部,我们将 State
属性设置为 Failed
,这样就不会调用选择器。
在 TouchesEnded
方法中,如果触摸的位置在视图的左下部分内部,我们考虑手势为 Ended
。如果没有,则手势识别被认为是 Failed
。
TouchesMoved
方法是设置 Changed
状态的地方。对于这个简单的手势识别器,我们不需要在其中添加其他逻辑。
更多内容...
这是一个简单的手势识别器,它依赖于单个触摸。通过触摸方法提供的信息,我们可以创建更复杂的支持多个触摸的手势。
自定义手势识别器的另一种用法
有些视图继承自 UIView
类,根据苹果开发者文档,这些视图不应该被子类化。MKMapView
就是这些视图之一,用于显示地图。如果我们想拦截这些视图的触摸事件,这会带来一个问题。虽然我们可以在其上方使用另一个视图并拦截其触摸事件,但这有点复杂。一个更简单的方法是创建一个简单的自定义手势识别器并将其添加到我们无法子类化的视图中。这样,我们就可以在不子类化的情况下拦截其触摸事件。
相关内容
在本章中:
-
识别手势
-
触摸事件
使用加速度计
在这个食谱中,我们将学习如何接收加速度计事件来创建一个能够感知设备运动的应用程序。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 AccelerometerApp
。
注意
模拟器不支持加速度计硬件。本例中的项目将在设备上正确运行。
如何做...
-
在
MainController
的视图中添加两个按钮和一个标签。 -
覆盖
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; } ;
-
在类中添加以下方法:
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); }
-
在设备上编译并运行应用程序。
-
点击开始加速度计按钮,在移动或摇晃设备时,观察标签上显示的值。
如何工作...
UIAccelerometer
类通过其SharedAccelerometer
静态属性提供对加速度计硬件的访问。要激活它,我们只需要将其Acceleration
事件分配给一个处理程序:
UIAccelerometer.SharedAccelerometer.Acceleration += this.Acceleration_Received;
在处理程序内部,我们通过UIAccelerometerEventArgs.Acceleration
属性接收加速度计的值。该属性返回一个UIAcceleration
类型的对象,它包含三个属性:X
、Y
和Z
,分别表示加速度计在三个轴上的量。
这些属性表示在X
、Y
和Z
轴上的运动。考虑以下图示:
这些值中的每一个都测量设备在每个轴上移动的重力量。例如,如果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
。
注意
模拟器不支持陀螺仪硬件。此外,只有较新的设备才包含陀螺仪。如果在这个没有陀螺仪的设备或模拟器上运行此应用程序,不会发生错误,但不会显示任何数据。
如何操作...
-
在
MainController
的视图中添加两个按钮和一个标签。在MainController.cs
文件中添加命名空间MonoTouch.CoreMotion
。在类中输入以下私有字段:private CMMotionManager motionManager;
-
重写
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(); } ;
-
添加以下方法:
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); }
-
在设备上编译并运行应用程序。
-
点击开始陀螺仪按钮,并在所有轴上旋转设备。观察应用程序输出中显示的值。
它是如何工作的...
陀螺仪是一种测量方向的机制。较新的 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, Y
和Z
轴上,分别由相应的X, Y
和Z
属性表示。每个值是该轴上发生的每秒旋转角度的数量,以弧度为单位。
虽然一开始可能看起来有点复杂,但实际上很简单。例如,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;
-
在
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); }
-
在设备上编译并运行应用程序。
-
点击开始按钮,在屏幕上查看您的位置坐标。
注意
使用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
的视图中添加两个按钮和一个标签。
注意
本任务中的项目无法在模拟器上测试。需要一个带有指南针硬件(磁力计)的设备。
如何操作...
-
航向信息再次通过
CLLocationManager
类检索。在MainController
类中创建并初始化一个实例。 -
在
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."; } ;
-
添加以下方法:
private void LocationManager_UpdatedHeading (object sender, CLHeadingUpdatedEventArgs e){ this.lblOutput.Text = string.Format("Magnetic heading: {0}", Math.Round(e.NewHeading.MagneticHeading, 1)); }
-
在设备上编译并运行应用程序。
-
点击开始按钮并旋转设备以查看不同的航向值。
它是如何工作的...
要检索航向信息,我们首先需要订阅定位管理器的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
类通过MagneticHeading
和TrueHeading
属性提供两种读数。这对开发者来说非常有帮助,因为计算两者之间的差异可能需要昂贵的设备,或者基于年份和其他因素进行非常复杂的计算。
罗盘可用性
磁力计,一个可以确定航向(以度为单位)并为设备提供指南针功能的模块,并非所有设备都可用。要检查设备是否可以提供航向信息,请从CLLocationManager.HeadingAvailable
静态属性中检索值:
if (CLLocationManager.HeadingAvailable) {
// Start updating heading
//...
}
参见
在本章中:
-
确定位置
-
后台位置服务
使用区域监控
在这个菜谱中,我们将讨论如何使用 GPS 来响应特定区域的位置变化。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 RegionApp
。在 MainController
的视图中添加两个按钮和一个标签。
如何操作...
-
在
MainController
类中创建两个字段:private CLLocationManager locationManager; private CLRegion region;
-
在
ViewDidLoad
方法中,对其进行初始化,并订阅UpdatedLocation
、RegionEntered
和RegionLeft
事件:this.locationManager.RegionEntered += this.LocationManager_RegionEntered; this.locationManager.RegionLeft += this.LocationManager_RegionLeft; this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
-
在类中输入以下事件处理程序:
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); }
-
在开始按钮的
TouchUpInside
处理程序中,调用StartUpdatingLocation
方法:this.locationManager.StartUpdatingLocation();
-
在停止按钮的
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
的视图中添加一个标签和两个按钮。
如何做...
-
在
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."; } ;
-
添加以下方法:
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); }
-
在设备上编译并运行应用程序。
-
点击开始按钮以开始监控显著位置变化。
它是如何工作的...
显著变化位置服务监控显著的位置变化,并在这些变化发生时提供位置信息。在功耗方面,它是要求较低的位置服务。它使用设备的蜂窝无线电收发器来确定用户的位置。只有配备了蜂窝无线电收发器的设备才能使用此服务。
使用显著变化位置服务的代码与标准位置服务的代码类似。唯一的不同之处在于启动和停止服务的方法。要启动服务,我们调用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
的视图中添加一个标签和两个按钮。
如何做...
-
在解决方案面板中,双击Info.plist文件以打开它。
-
在高级选项卡下,通过单击加号(+)或通过右键单击并从上下文菜单中选择新建键来添加一个新键。
-
从下拉列表中选择必需的后台模式,或者在字段中直接输入
UIBackgroundModes
。 -
展开键,在其下方的空白项上右键单击。在上下文菜单中单击新建键。在其值字段中输入单词
location
。 -
保存文档。完成后,你应该会有以下截图所示的内容:
-
在
MainController
类中,输入与本章中确定位置菜谱中使用的相同代码。 -
在
LocationManager_UpdatedLocation
方法的底部添加以下行:Console.WriteLine("{0}:\n\t{1} ", DateTime.Now, this.lblOutput.Text);
-
在设备上编译并运行应用程序。
-
点击开始按钮以开始接收位置更新。按设备上的主页按钮使应用程序进入后台。观察 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
。
如何操作...
-
在
MainController
的视图中添加一个MKMapView
。输入以下using
指令:using MonoTouch.MapKit; using MonoTouch.CoreLocation;
-
在
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); } }
-
在模拟器或设备上编译并运行应用程序。如果应用程序在模拟器上运行,默认位置将是苹果公司的总部位于 库比蒂诺:
-
放大或平移地图以在 应用程序输出 中输出当前位置。
它是如何工作的...
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
。
如何操作...
-
在
MainController
视图的上半部分添加一个MKMapView
,在下半部分添加一个标签和一个按钮。添加MonoTouch.MapKit
和MonoTouch.CoreLocation
命名空间。 -
在
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(); } ; }
-
创建以下嵌套类:
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; } }
-
在模拟器或设备上编译并运行应用程序。如果在设备上运行,当你点击按钮时,将在标签上显示你当前所在的国家和地区的位置信息。
它是如何工作的...
地理编码是将地址信息与地理坐标匹配的过程。反向地理编码是将地理坐标与地址信息匹配的过程。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
。
如何操作...
-
在
MainController
的视图中添加一个MKMapView
。在底部留出一些空间,并添加一个按钮。 -
添加命名空间
MonoTouch.MapKit
和Monotouch.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); } ;
-
创建以下嵌套类:
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; } } }
-
在模拟器或设备上编译并运行应用程序。如果在模拟器上运行,当你点击按钮时,结果应该类似于以下截图:
- 点击“添加引脚”会显示带有注释标题和副标题的呼叫气泡。
它是如何工作的...
注释地图对于提供与地图数据相关的各种信息非常有用。我们可以使用 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
。
如何做...
-
在
MainController
的视图中添加一个MKMapView
。在底部留出一些空间,并添加一个按钮。 -
添加命名空间
MonoTouch.MapKit
和Monotouch.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); } ;
-
添加以下嵌套类:
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; } } }
-
在模拟器或设备上编译并运行应用程序。如果在模拟器上运行,在点击按钮后,结果应该类似于以下内容:
工作原理...
虽然 MKAnnotation
代表地图上的一个点,但 MKOverlay
对象可以代表地图上的一个区域。在这个例子中,我们使用继承自 MKOverlay
的 MKCircle
类来在地图上的区域上显示一个圆。
初始化 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;
我们设置适当的属性来定义叠加的外观。在这种情况下,我们设置了FillColor
、StrokeColor
和LineWidth
属性。
更多内容...
地图视图有效地处理叠加。地图视图为我们处理的一个重要事项是,当我们缩放地图时,叠加会自动缩放到匹配每个缩放级别。这样,我们就不需要在代码中手动缩放叠加。
创建自定义叠加
我们可以创建自己的自定义叠加。为此,我们需要为叠加重写MKOverlay
类,为叠加视图重写MKOverlayView
类。
标准叠加对象
除了MKCircle
之外,其他标准叠加对象还包括MKPolygon
用于创建多边形形状和MKPolyline
用于创建折线,如在轨迹记录应用程序中。
参考内容
在本章中:
-
显示地图
-
添加地图注释
第十一章. 图形和动画
在本章中,我们将涵盖:
-
动画化视图
-
变换视图
-
使用图像进行动画
-
动画化层
-
绘制线条和曲线
-
绘制形状
-
绘制文本
-
一个简单的绘图应用程序
-
创建图像上下文
简介
在本章中,我们将讨论自定义绘制和动画。iOS SDK 包含两个用于这些任务的非常有用的框架:Core Graphics 和 Core Animation。
这两个框架简化了在 UI 元素上动画化以及绘制 2D 图形的过程。有效使用这两个框架将在平淡无奇和令人惊叹的应用程序之间产生差异。毕竟,这两个框架在使 iOS 平台在其类别中独一无二方面发挥着非常重要的作用。
我们将学习如何为控件提供简单甚至更复杂的动画,以提供独特的用户体验。我们还将看到如何在屏幕上自定义绘制线条、曲线、形状和文本。最后,通过所有提供的示例,我们将创建两个绘图应用程序。
动画化视图
在这个菜谱中,我们将学习如何利用UIKit
动画在屏幕上移动UILabel
。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为ViewAnimationApp
。在MainController
的视图中添加一个标签和一个按钮。
如何做到这一点...
-
添加
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(); }; }
-
添加以下方法:
[Export("LabelPositionAnimationStopped")] public void LabelAnimationStopped(){ this.lblOutput.Text = "Animation ended!"; this.lblOutput.BackgroundColor = UIColor.Red; }
-
在模拟器上编译并运行应用程序。
-
点击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 版本时,使用由 BeginAnimations
和 CommitAnimations
方法定义的动画块。
可动画属性
UIKit
动画支持一组特定的 UIView
属性。这些属性被称为 可动画属性。以下是可被动画化的 UIView
属性列表:
-
Frame
-
Bounds
-
Center
-
Transform
-
Alpha
-
BackgroundColor
-
ContentStretch
视图变换
在这个菜谱中,我们将通过应用变换来旋转 UILabel
。此外,旋转将会有动画效果。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 TransformViewApp
。在 MainController
的视图中添加一个标签和一个按钮。
如何实现...
-
添加
MonoTouch.CoreGraphics
命名空间:using MonoTouch.CoreGraphics;
-
在
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); }
-
在模拟器上编译并运行应用程序。
-
点击按钮并观察标签旋转。
工作原理...
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
属性不应被考虑。如果需要在变换后更改视图的大小或位置,请分别使用Bounds
和Center
属性。
相关内容
在这一章中:
-
动画视图
-
动画层
使用图像的动画
在这个菜谱中,我们将使用UIImageView
内置的动画功能创建一个简单的图片幻灯片。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为ImageAnimationApp
。在MainController
的视图中添加一个UIImageView
和两个按钮。此任务的示例项目包含三张图片。将两张或更多图片添加到项目中,并将它们的构建操作设置为内容。
如何做到这一点...
-
输入以下
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(); } }; }
-
在模拟器上编译并运行应用程序。
-
点击动画图像按钮以开始动画。
它是如何工作的...
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;
要开始或停止动画,分别调用StartAnimating
或StopAnimating
方法。
还有更多...
UIImageView
的AnimationImages
属性和Image
属性之间没有关系。如果需要在没有动画时显示图像,请在动画前后将图像设置到Image
属性中。
检查动画
要确定是否发生动画,请检查UIImageView
的IsAnimating
属性。
相关内容
在这一章中:
- 动画视图
在这本书中:
第二章,用户界面:视图:
- 显示图像
动画层
在这个菜谱中,我们将学习如何使用Core Animation
框架通过动画其层来复制屏幕上的UILabel
。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 LayerAnimation
。在 MainController
的视图中添加两个标签和一个按钮。为第一个视图设置文本和背景颜色,并为第二个视图设置不同的背景颜色。
如何做...
-
添加
MonoTouch.CoreAnimation
命名空间:using MonoTouch.CoreAnimation;
-
在类中添加一个
CALayer
类型的字段:private CALayer copyLayer;
-
在
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"); } ;
-
在模拟器上编译并运行应用程序。
-
点击 复制 按钮以动画方式将第一个标签的内容复制到第二个标签。
它是如何工作的...
MonoTouch.CoreAnimation
命名空间是 Core Animation
框架的包装器。
每个视图都有一个 Layer
属性,它返回视图的 CALayer
对象。在这个任务中,我们正在创建一个动画,它以图形方式显示从一个标签复制内容到另一个标签。
我们不是创建另一个标签并用 UIView
动画移动它,而是创建一个层并移动它。我们通过设置其 Frame
和 Contents
属性来创建层,后者来自源标签的层。然后我们使用 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
类还有两个用于定义动画的属性:From
和 By
。它们都是 NSObject
类型,但应该分配给它们的实际值应该是 NSValue
类型。NSValue
类包含创建其实例的各种静态方法。
层
层是非常强大且高效的用于绘制和动画的对象。强烈建议使用层在视图上执行动画,而不是使用实际的视图本身。
参见
在本章
- 动画视图
绘制线条和曲线
在这个菜谱中,我们将实现自定义绘制,在 UIView
上绘制两条线。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 DrawLineApp
。
如何操作...
-
向项目中添加一个新类,并将其命名为
DrawingView
。从UIView
派生它:public class DrawingView : UIView
-
在
DrawingView.cs
文件中添加以下using
指令:using MonoTouch.CoreGraphics;
-
覆盖
UIView
的Draw
方法,并使用以下代码实现它: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(); } }
-
在
MainController
的ViewDidLoad
覆盖中,初始化并添加视图:DrawingView drawingView = new DrawingView(this.View.Bounds); drawingView.BackgroundColor = UIColor.Gray; this.View.AddSubview(this.drawingView);
-
在模拟器上编译并运行应用程序。结果应该类似于以下内容:
它是如何工作的...
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
。
如何做...
-
在
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(); }
-
在模拟器上编译并运行应用程序。屏幕上的结果应类似于以下内容:
它是如何工作的...
要在视图中绘制形状,我们需要调用适当的方法。我们首先设置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
类添加到项目中。
如何做...
-
在
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)); }
-
在模拟器上编译并运行应用程序。文本将在屏幕上显示。结果应类似于以下内容:
它是如何工作的...
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
。
如何实现...
-
使用以下代码实现
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); } } } }
-
在模拟器或设备上编译并运行应用程序。
-
用手指触摸并拖动(或使用光标点击并拖动)开始绘画!
它是如何工作的...
在这个任务中,我们将结合触摸事件和自定义绘制来创建一个简单的绘图应用程序。当用户触摸并在屏幕上移动手指时,我们保留触摸位置点的信息,并在 Draw
方法中使用这些信息来绘制线条。
在将触摸位置设置为类字段后,我们调用 SetNeedsDisplay
来强制调用 Draw
方法。fingerDraw
字段用于确定 Draw
方法是由屏幕上的触摸触发的,而不是在视图首次加载时由运行时触发的。
每次我们调用一个方法将某个东西绘制到图形上下文中时,该上下文中的先前绘图都会被清除。为了避免这种行为,我们使用CGPath
对象。我们可以在CGPath
中添加各种绘图对象,并通过将它们添加到图形上下文中来显示这些对象。因此,每次用户在屏幕上移动手指时,由触摸位置点定义的新线就会被添加到路径中,并且路径会在当前上下文中绘制。
注意,我们需要保留当前触摸位置和上一个位置的信息。这是因为AddLineToPoint
方法接受一个点,该点定义了线的终点,假设路径中已经有一个点。每条线的起点是通过调用MoveToPoint
,传递上一个触摸位置点来定义的。
通过在屏幕上滑动手指绘制的路径基本上由一系列连续的直线组成。然而,由于TouchesMoved
方法在手指在屏幕上每次移动时都会被触发,因此结果是平滑的路径,它遵循手指的移动。
在将线添加到路径后,我们将其添加到上下文中并绘制它:
context.AddPath(this.drawPath);
context.DrawPath(CGPathDrawingMode.Stroke);
更多内容...
在本任务中引入了两个新的CGContext
方法:SetLineJoin
和SetLineCap
。SetLineJoin
方法设置每条线如何与前一条线连接,而SetLineCap
设置线的端点外观。
它们接受的值解释如下:
-
SetLineJoin
-
CGLineJoin.Miter:
以斜角连接两条线 -
CGLineJoin.Round:
以圆角连接两条线 -
CGLineJoin.Bevel:
以方形端点连接两条线
-
-
SetLineCap
-
CGLineCap.Butt:
线将以端点上的方形边缘结束 -
CGLineCap.Round:
线将以圆角结束,并扩展到端点之外 -
CGLineCap.Square:
线将以扩展到端点之外的方形边缘结束
-
清除绘图
为了清除绘图,我们只需将fingerDraw
变量设置为false
并调用SetNeedsDisplay
。这样,Draw
方法将不会调用我们的自定义绘图代码,从而清除当前上下文。
参见
在本章中:
-
绘制线条和曲线
-
绘制形状
-
绘制文本
创建图像上下文
在本食谱中,我们将通过为用户提供保存创建的绘图的功能来扩展我们之前创建的手指绘图应用程序。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为ImageContextApp
。将我们在上一个任务中创建的CanvasView
类添加到项目中。
如何操作...
-
在
MainController
的视图中添加两个按钮。一个将用于保存图像,另一个用于清除当前绘图。 -
在
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(); }
-
在
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(); } ; }
-
在模拟器上编译并运行应用程序。
-
在画布上绘制一些东西,然后点击 保存绘图 按钮来保存你的绘图。
-
点击 清除 绘图按钮来清除画布。然后你可以检查模拟器的照片库以查看你的绘图。
工作原理...
使用 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
。在这个例子中不需要视图控制器。
如何操作...
-
在
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); }
-
在模拟器或设备上编译并运行应用程序。
-
按下主页按钮以挂起应用程序,并观察 MonoDevelop 中的应用程序输出面板。
它是如何工作的...
UIApplicationDelegate
类包含由运行时发出的特定通知触发的方法。这些方法是:
-
OnActivated:
当应用程序变为活动状态时,会调用此方法,例如,当解锁屏幕或应用程序启动时。 -
OnResignActivation:
当应用程序即将变为非活动状态时,会调用此方法,例如,当屏幕锁定或显示多任务栏时。 -
DidEnterBackground:
当应用程序进入后台时,会调用此方法,例如,当按下主页按钮。应用程序被挂起。 -
WillEnterForeground:
当应用程序即将返回前台时,会调用此方法。
注意,当应用程序移动到后台时,会调用OnResignActivation
和DidEnterBackground
方法。同样,当应用程序移动到前台时,会调用WillEnterForeground
和OnActivated
方法。
所有这些方法都包含一个参数,该参数包含应用程序的 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
。
如何操作...
-
在
MainController
类中输入以下字段:private NSObject didEnterBgdObserver; private NSObject willEnterFgdObserver;
-
创建以下方法:
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 }); }
-
在
ViewDidLoad
覆盖中,调用AddAppStateObservers
方法:this.AddAppStateObservers();
-
在模拟器上编译并运行应用程序。
-
按下 主页 按钮,并观察 应用程序输出 面板中的输出。它应该类似于以下截图:
它是如何工作的...
除了调用 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.DidBecomeActiveNotification
和 UIApplication.WillResignActiveNotification
键。
移除通知观察者
当不再需要通知观察者时,在 ViewDidUnload
覆盖方法中调用 RemoveAppStateObservers
方法:
this.RemoveAppStateObservers();
参见
在本章中:
- 检测应用程序状态
在后台运行代码
在本食谱中,我们将学习如何在后台执行代码,充分利用 iOS 的多任务功能。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为 BackgroundCodeApp
。在这个例子中不需要视图控制器。
如何做到这一点...
-
在
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!"); } }
-
在模拟器上编译和运行应用程序。
-
按下主页按钮使应用程序进入后台,并观察应用程序输出。
-
在后台任务完成之前(一分钟),可以通过在多任务栏中点击其图标或在其主页屏幕上的图标来将应用程序带到前台。
它是如何工作的...
在前面的任务中,我们学习了如何得知应用程序从前台到后台以及相反的转换。
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
的视图中添加一个按钮。
如何做到...
-
打开
Info.plist
文件,并添加键UIBackgroundModes
。 -
在其下添加音频项。添加
MonoTouch.AVFoundation
命名空间。 -
在
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(); } ; }
-
将声音文件添加到项目中,并将其构建操作设置为内容。本例使用一个名为
sound.m4a
的 20 秒长声音文件。 -
在设备上编译并运行应用程序。
-
点击开始播放按钮,然后按主页按钮使应用程序进入后台。注意,声音仍在播放。
它是如何工作的...
为了确保我们的应用程序在后台播放音频时能够工作,我们必须在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
。在这个示例中不需要视图控制器。
如何操作...
-
在
AppDelegate
类中添加以下DidEnterBackground
重写方法:public override void DidEnterBackground (UIApplication application){ application.SetKeepAliveTimeout(610, delegate { Console.WriteLine("App woken up for network connection maintenance!"); } ); }
-
在
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, NSOutputStream
和NSUrlRequest
。
合并 UIBackgroundModes 键的项目
应用程序可以使用UIBackgroundModes
键的任何组合或所有可用项。然而,避免添加与预期功能不同的后台模式。在这种情况下,您的应用程序可能会被应用商店拒绝。
相关内容
在本章中:
- 后台播放音频
在本书中:
第十章, 位置服务和地图:
- 后台位置服务
第十三章。本地化
在本章中,我们将介绍:
-
为不同语言创建应用程序
-
可本地化资源
-
区域格式化
简介
随着 iOS 平台和以应用程序商店形式出现的全球软件市场的发布,苹果公司使开发者更容易在全球范围内分发应用程序。
但是,全球用户甚至不会费心下载和使用以他们不理解的语言发布的应用程序。为了扩大其应用程序的用户基础,开发者必须对其进行本地化。本地化是将文本翻译成多种语言,提供针对多个区域的具体资源,从而创建面向不同文化受众的应用程序的过程。
在本章中,我们将讨论提供翻译文本的最佳实践,这些文本将根据每个用户的区域设置偏好显示。我们还将了解如何根据这些偏好提供资源(图像、视频)。最后,我们将使用常见的.NET 实践来格式化日期、货币和数字。
为不同语言创建应用程序
在本食谱中,我们将创建一个支持两种不同语言的应用程序。
准备工作
在 MonoDevelop 中创建一个新的项目,并将其命名为MultipleLanguageApp
。
如何操作...
-
在
MainController
的视图中添加两个标签。 -
向项目添加两个文件夹。分别命名为
en.lproj
和es.lproj
。 -
使用文本编辑器应用程序创建两个文本文件。在第一个文件中输入以下文本:
// Localized output on MainController "Have a nice day!" = "Have a nice day!";
-
将其保存为 en.lproj 文件夹中的 Localizable.strings。
-
在第二个文件中输入以下文本:
// Localized output on MainController "Have a nice day!" = "Tenga un buen día!";
-
这次将文件以相同的名称,Localizable.strings,保存在
es.lproj
文件夹中。将两个文件的构建操作设置为内容。注意
Localizable.strings
文件必须以UTF8
或UTF16
编码保存。 -
在
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"); }
-
通过模拟器的设置应用程序,将语言设置为英语(如果尚未设置),然后运行应用程序。消息将以英语显示。尝试将模拟器的语言设置为西班牙语,然后再次运行应用程序。消息将以西班牙语显示。
它是如何工作的...
为了使开发者更容易在应用程序中提供对多种语言的支持,iOS 从相应的语言文件夹中读取不同语言文本。在本应用程序中,我们支持英语和西班牙语。它们相应的文件夹分别是en.lproj
和es.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
。
如何操作...
-
在项目中添加两个用于英语和西班牙语区域的文件夹。在每个文件夹中添加一个具有相同文件名的图像。将它们的构建操作设置为内容。
-
在
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")); }
-
在模拟器上编译并运行应用程序,在设置应用程序中选择英语语言。结果应该类似于以下截图:
-
现在,将模拟器的语言设置为西班牙语,并再次运行应用程序。应该显示西班牙国旗:
它是如何工作的...
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
。
如何操作...
-
在
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); }
-
在模拟器上编译并运行应用程序,将区域格式设置为美国和西班牙 | 西班牙,在设置 | 通用 | 国际 | 区域格式下。具有两种不同区域格式的输出将类似于以下截图:
它是如何工作的...
要格式化日期、货币和数字,我们使用标准的 .NET 代码。对于日期和时间,DateTime.ToLongDateString
和 DateTime.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 的流程。
创建配置文件
在本食谱中,我们将逐步指导您创建和安装必要的证书和配置文件,这些证书和配置文件对于将应用程序部署到设备上是必需的。
如何操作...
以下步骤将指导您创建应用程序的开发者证书和适当的配置文件。
我们将从开发者证书开始。
-
登录到 iOS 开发者网站:
developer.apple.com/ios
。 -
前往 iOS 配置文件门户。
-
从左侧菜单中选择 Certificates。
-
如果这是您第一次在 Mac 上使用开发者证书,请下载并安装 WWDR 中间证书。您可以在 Development 选项卡下找到链接。
-
在 How To 选项卡下,您将找到创建证书签名请求的说明,这是颁发您的开发者证书和下载安装它的必要条件。
-
如果开发者证书已正确安装,您将在 MonoDevelop 项目的选项中选中它,在 Identity 下拉列表中的 iPhone Bundle Signing 节点下:
- 您可以在 iOS Provisioning Portal 的 Certificates 选项下的 Development 选项卡中查看您的开发者证书。
现在我们已经颁发并安装了我们的开发者证书,我们需要注册我们将用于调试的设备。
-
点击左侧菜单中的 Devices 选项。
-
在 Manage 选项卡下,点击 Add Devices 按钮。
-
在 Device Name 字段中,输入一个可以识别特定设备的名称(例如,
我的 iPhone
)。 -
在设备 ID字段中,输入设备的唯一设备标识符(UDID)。您可以通过将设备连接到您的 Mac 并打开iTunes来找到设备的 UDID。在设备的摘要选项卡下,点击序列号将切换到 UDID。按Command + C将 UDID 复制到剪贴板。
-
点击加号(+)按钮,然后提交。为要添加的所有设备重复所有步骤。
接下来,我们需要一个App ID。
-
点击左侧菜单上的App IDs选项。
-
在管理选项卡下,为您的应用程序输入描述/名称。不要使用特殊字符和空格。
-
将Bundle Seed ID (App ID Prefix)选项保留为生成新。
-
输入包标识符。包标识符的最佳实践是遵循字段上方给出的示例和建议。包标识符很重要,因为在部署过程的至少一个步骤中您将需要它。
-
点击提交按钮以创建 App ID。
接下来,是配置文件的时间。
-
点击左侧菜单上的配置选项。
-
在开发选项卡下,点击新建配置文件按钮。
-
输入配置文件名称。您可以在此字段中输入任何字符。
-
在证书选项中,选择配置文件的开发者证书。如果您已成功创建您的开发者证书,您的姓名(或 iOS 开发者账户所属者的姓名)将显示在旁边的复选框旁边。勾选复选框。
-
在App ID选项中,选择您正在创建的配置文件的App ID。
-
在设备选项中,勾选将包含在配置文件中的设备(们)。您的应用程序只能安装在此处选择的设备上。
-
点击提交按钮以完成配置文件的创建。
-
如果列表中配置文件的状态为挂起,只需刷新页面。
-
点击您配置文件所在行的下载按钮以下载它。将下载一个扩展名为 .mobileprovision 的文件。
-
将您的设备连接到您的 Mac。如果已打开,请关闭iTunes。
-
双击您在步骤 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。
如何操作...
创建临时配置文件的过程与创建开发分发配置文件的过程类似。以下步骤将指导您完成这个过程。
-
分发证书:为了将应用程序分发到未连接到您的 Mac 的各种设备,以及提交到应用商店,您需要一个分发证书进行安装。遵循之前任务中描述的创建开发者证书的相同步骤。不过,这次您需要在iOS 配置文件门户的证书菜单选项下的分发选项卡下进行操作。
-
临时分发配置文件:
-
在iOS 配置文件门户中,转到配置 | 分发。
-
在分发方法字段中,选择临时。
-
为配置文件输入一个配置文件名称。为了将来参考,最好在名称中添加单词
AdHoc
,例如,MyAppAdHoc
。 -
分发证书将自动选中。
-
选择App ID。
-
选择配置文件中的设备。
-
点击提交以创建配置文件。
-
从显示的列表中下载配置文件。再次点击,你将得到一个 .mobileprovision 文件。双击它以允许 Xcode 安装它。此时不需要连接设备。不要删除文件;我们稍后会用到它。
-
-
创建临时构建:
-
在 MonoDevelop 中加载你的项目后,转到项目 | 解决方案选项。
-
在构建 | 配置下,通过点击添加创建一个新的配置。
-
为配置输入一个名称。这里使用的名称是
MyAppDistribution
。 -
在平台选项中选择iPhone。以下截图显示了新配置对话框应该如何显示:
-
确保已勾选“为所有解决方案项创建配置”选项。
-
点击确定按钮。
-
将一个新的属性列表文件添加到项目中,并将其命名为
Entitlements
。 -
MonoDevelop 将自动使用属性列表编辑器加载
Entitlements.plist
文件。添加键get-task-allow
,并将其类型设置为布尔值。将其值设置为否。 -
在项目选项的iPhone Bundle Signing节点下,选择之前创建的配置(MyAppDistribution)。将自定义权限字段设置为之前步骤中创建的
Entitlements.plist
文件。它可以通过点击字段旁边的浏览 ()按钮轻松找到。 -
在同一窗口中设置分发配置文件和适用于临时分发的适当配置文件。
-
将项目的当前配置设置为之前创建的分发配置(MyAppDistribution)。
-
构建项目。
应用程序现在已准备好进行临时分发!
-
-
分发给测试人员:
-
打开Finder,导航到项目中的
bin
文件夹。 -
打开
iPhone/MyAppDistribution
文件夹。通过右键单击并选择Compress "MyApp"(或应用的实际名称)使用 OS X 的默认压缩工具压缩应用程序包。应用程序包是一个显示以下图标的文件夹: -
将压缩的应用程序包以及
.mobileprovision
文件发送给您的测试员。 -
为了安装应用,您的测试员需要从压缩归档中提取应用程序包。
-
提取后,将文件拖放到iTunes的Apps部分,并附带
.mobileprovision
文件,即可将其导入到iTunes Library。请注意,应用将在iTunes中显示默认图标。如果你没有为应用设置任何图标,这对于临时分发来说是正常的。 -
在iTunes中同步设备。
-
如果测试员在 Windows 机器上使用iTunes,请指示他们不要使用默认的解压缩工具,而应使用第三方应用程序。
-
它是如何工作的...
为了分发应用,我们需要一个分发证书。就像开发者证书一样,分发证书一旦创建,就可以在需要时转移到另一台 Mac 上。
临时分发配置文件的创建过程与创建开发配置文件的过程相同。唯一的区别是我们有选择分发类型的选项,可以是App Store或Ad 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.png
和Default@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。
-
截图:准备显示您应用程序各个方面的截图。对于 iPhone/iPod Touch 应用程序,肖像模式的尺寸应为
320x480
,横屏模式的尺寸应为480x320
。对于 iPad 应用程序,截图的尺寸应为肖像模式的768x1024
和横屏模式的1024x768
。如果应用程序没有隐藏状态栏,最好将其包含在截图内。对于每个应用程序,我们可以在 App Store 上最多拥有五张截图。 -
App Store 图标:准备代表应用在 App Store 上的图标。其尺寸必须是
512x512
像素,并且必须与应用图标相同。 -
描述和关键词:准备描述您的应用程序的文本。尽量包括最重要的功能。记住,描述是用户在下载应用程序之前会阅读的内容,所以越吸引人越好。
准备有助于您的应用程序在搜索结果中排名靠前的关键词。
应用描述和关键词都是必需的。
-
登录到 iTunes Connect:iTunes Connect 是管理提交应用程序(以及其他 App Store 相关内容)的开发者门户。使用您的 Apple 开发者 ID 登录到 iTunes Connect(
itunesconnect.apple.com
)。点击链接管理您的应用程序。然后,点击左上角的添加新应用按钮。按照步骤在门户上完成应用程序准备。完成后,请确保应用程序状态为等待上传。 -
上传:在门户上创建新应用程序后,您可以使用应用程序加载器上传压缩的应用程序包。它默认安装在 Xcode 中,可以在路径
/Developer/Application/Utilities
下找到,或者通过Spotlight搜索。当您启动应用程序加载器时,它将要求您使用您的Apple 开发者 ID登录。登录后,您将看到一个以下窗口:
-
点击提交您的应用程序按钮,它将连接到iTunes Connect,找到您处于等待上传状态的应用程序,并将它们加载到列表框中:
- 然后,您将看到您应用程序的摘要视图:
-
点击选择...按钮,将弹出一个对话框,允许您选择压缩的应用程序包。选择后,继续上传。一切就绪!如果所有步骤都正确完成,应用程序将被上传,并将在 App Store 上等待审核发布。
它是如何工作的...
应用截图和 App Store 图标非常重要。它们可以是JPG
、TIF
或PNG
格式,使用 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
。
如何操作...
-
在
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); } }
-
在
Page
类中添加一个属性,并修改其构造函数,如下面的代码所示:public Page (int pageIndex) : base ("Page", null){ this.PageIndex = pageIndex; } public int PageIndex{ get; private set; }
-
最后,在
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);
-
在模拟器上编译并运行应用程序。
-
在模拟器的屏幕区域上点击并拖动以更改页面。结果应类似于以下截图:
它是如何工作的...
随着 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 再次为我们简化了事情,提供了两个非常有用的属性供我们使用:GetNextViewController
和GetPreviousViewController
。这两个属性仅仅代表了如果我们要为页面控制器创建一个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
的控制器。
如何做到这一点...
-
在每个控制器上添加一个按钮。在
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); } ; }
-
实现
ModalController
类的ViewDidLoad
方法,以便在按钮被点击时关闭它:this.buttonDismiss.TouchUpInside += delegate(object sender, EventArgs e) { this.DismissModalViewControllerAnimated(true); } ;
-
在模拟器上编译并运行应用程序。
-
点击显示按钮以显示模态控制器。注意,
MainController
和ModalController
的按钮具有相同的背景颜色和文字颜色。
它是如何工作的...
为了使我们的应用程序中的所有按钮看起来相同,我们使用UIButton
类的Appearance
静态属性。此属性返回继承自UIAppearance
类的对象,这是将 Objective-C 的UIAppearance
协议反映到 MonoTouch 中的类。
这样,我们为所有支持它的视图都有一个Appearance
静态属性,根据我们想要样式的视图进行强类型化。对于UIButton
,Appearance
属性返回一个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
添加到项目中。
如何操作...
-
在
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!"); } } ; }
-
在模拟器上编译并运行应用程序。如果模拟器上尚未设置 Twitter 账户,可以通过
Settings
应用程序轻松配置。 -
点击按钮以显示 Twitter 控制器。结果应该类似于以下截图:
它是如何工作的...
iOS 提供了 TWTweetComposeViewController
类,它提供了共享功能。此控制器与用于从设备相册共享照片的原生界面相同。就像类似的本地控制器一样,我们只能在它呈现之前设置其内容。我们无法在它向用户显示后对其进行修改,用户负责是否发送或丢弃它。
我们可以通过读取 CanSendTweet
静态属性来确定用户是否在设备上配置了 Twitter 账户:
if (TWTweetComposeViewController.CanSendTweet)
如果我们在设备上未设置账户的情况下呈现控制器,将显示一个原生警报,用户可以选择在继续之前配置账户。
然后,我们初始化控制器,并使用 SetInitialText
方法设置要填充的文本,如果需要的话:
this.tweetController = new TWTweetComposeViewController();
this.tweetController.SetInitialText("Tweet from MonoTouch!");
我们还可以通过 AddUrl
和 AddImage
方法分别添加推文中的 URL 或图片。
为了获取用户是否发送或取消推文的反馈,我们调用 SetCompletionHandler
方法,传递要调用的回调:
this.tweetController.SetCompletionHandler(delegate(TWTweetComposeView ControllerResult tweetResult) {
此回调接受枚举类型 TWTweetComposeViewControllerResult
的一个参数,它可以包含两个值中的任意一个 Done
或 Cancelled
。
最后但同样重要的是,我们应该在回调中关闭控制器。
注意
手动关闭 TWTweetComposeViewController
不是必需的。然而,如果没有手动关闭它,已经注意到尽管在用户点击 Send 时控制器被关闭,但需要两次点击 Cancel 按钮才能关闭它。
还有更多...
除了发送推文外,MonoTouch.Twitter
命名空间还封装了 TWRequest
类,允许我们通过 Twitter API URL 读取 Twitter 信息,例如用户的推文时间线。通过这种方式接收到的数据是以 JSON 对象的形式,正确读取它们是我们的责任。
支持横幅方向
TWTweetComposeViewController
支持横幅方向。要启用它,我们只需覆盖呈现它的控制器中的 ShouldAutoRotateToInterfaceOrientation
方法。
与分割键盘一起工作
在这个菜谱中,我们将创建一个能够感知虚拟键盘位置变化的应用程序,以便相应地调整我们的内容。
准备工作
在 MonoDevelop 中创建一个新的 iPad 项目,并将其命名为 SplitKeyboardApp
。将 MainController
添加到项目中。
如何做到这一点...
-
添加一个
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(); } } } }
-
在模拟器上编译并运行应用程序。
-
点击文本框以显示键盘。如果之前未在 iPad 模拟器中使用过键盘,它将默认处于正常状态,即在底部并合并。
-
点击并拖动屏幕右下角的隐藏键盘键来移动键盘,使其分裂,并让它停留在文本字段上方。
- 观察文本字段在键盘上方动画。结果应该类似于以下截图:
它是如何工作的...
要检测分裂键盘的位置,我们首先需要为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);
}
移动键盘有问题?
隐藏键盘键在我们长按它时会出现一个小的“上下文菜单”。这个菜单提供了停靠和合并(或相反操作)键盘的选项。为了将键盘移动到我们想要的位置,我们必须在点击键的瞬间就开始拖动。