MonoTouch-IOS-开发秘籍-全-
<h1 id="monotouch-ios-开发秘籍全">MonoTouch IOS 开发秘籍(全)</h1><blockquote>
<p>原文:<code>zh.annas-archive.org/md5/e782db9cc6c1163c07b37906130bf0f1</code></p>
<p>译者:飞龙</p>
<p>协议:CC BY-NC-SA 4.0</p>
</blockquote>
<h1 id="前言">前言</h1>
<p>技术正在迅速发展。便携式设备,如媒体播放器、智能手机和平板电脑,为人们的沟通、分享和消费数字内容的方式带来了巨大的进步和变化。开发者需要了解这些设备所运行的可用平台,如果他们想要成为“游戏的一部分”。</p>
<p>iOS,苹果为其便携式设备开发的操作系统,无疑是当今领先的便携式平台之一。如果没有 MonoTouch,.NET 开发者将不得不花费时间学习一门新的编程语言,以扩展他们的创造力到 iOS 生态系统。</p>
<p>本书通过一系列多个食谱和几乎相等数量的完整项目,将帮助你成为这个生态系统的成员,借助 MonoTouch 和 C#。当你阅读完它,你将成为一名合格的 iOS 开发者,准备好将你的创造力释放到当今最受欢迎的便携式平台之一。</p>
<h1 id="本书涵盖的内容">本书涵盖的内容</h1>
<p>第一章,<em>开发工具</em>,将带你了解你将需要用于使用 MonoTouch 进行 iOS 开发的全部 IDE 和 SDK。你将创建你的第一个 MonoTouch 项目,并学习如何在模拟器上进行调试。</p>
<p>第二章,<em>用户界面:视图</em>,介绍了视图的概念以及它们是如何成为完整 iOS 应用程序的一部分的。通过探索大量不同的视图组件,你将创建不同的应用程序,这将帮助你理解每个组件的工作方式。</p>
<p>第三章,<em>用户界面:视图控制器</em>,讨论了<strong>模型-视图-控制器</strong>(MVC)模式以及如何使用它来创建适合增强用户体验的应用程序。通过本章,你还将了解最有用的控制器,这些控制器将是你未来许多项目的组成部分,以及如何创建针对 iPad 的特定应用程序和通用应用程序。</p>
<p>第四章,<em>数据管理</em>,将带你了解一系列技术,这些技术将帮助你将数据管理集成到你的应用程序中。你将学习如何使用 SQLite 数据库、XML、LINQ-to-XML 和序列化,这些以前仅适用于.NET 桌面和 Web 项目。</p>
<p>第五章,<em>显示数据</em>,扩展了在 iOS 设备比桌面屏幕更小的屏幕上有效显示数据的可用组件。你将习惯于使用<code>UITableView</code>来显示数据列表,以及使用<code>UIWebView</code>来显示 HTML(以及更多)内容。</p>
<p>第六章,<em>网络服务</em>,讨论了创建在线通信以交换数据的应用程序。借助 MonoTouch,你不仅将学习如何在 iOS 应用程序中使用常见的.NET 和 WCF 网络服务,还将学习如何读取和解析 JSON 对象。</p>
<p>第七章,<em>多媒体资源</em>,将教会你如何通过设备的硬件创建应用程序来捕捉、重现和管理多媒体内容。你不仅将学会如何使用相机捕捉图像和视频,还将学习如何播放和录制音频。</p>
<p>第八章,<em>集成 iOS 特性</em>,将指导你如何将平台的原生应用程序和组件集成。你将学习如何在你的应用程序中提供电子邮件、短信和通讯录功能,以及如何使用原生日历来创建事件。</p>
<p>第九章,<em>与设备硬件交互</em>,讨论了创建能够完全感知其周围环境的应用程序,通过设备的传感器。你将学习如何根据设备方向调整用户界面,以及如何响应加速度计和陀螺仪事件。</p>
<p>第十章,<em>位置服务和地图</em>,是使用内置位置服务创建提供位置信息给用户的详细指南。你不仅将学习如何使用 GPS 硬件,还将学习如何显示地图和布局信息。</p>
<p>第十一章,<em>图形和动画</em>,介绍了 2D 图形和动画。你将学习如何动画化组件和创建简单的图形。到本章结束时,你将创建一个简单的手指绘画应用程序。</p>
<p>第十二章,<em>多任务处理</em>,将指导你如何在 iOS 应用程序中实现多任务处理,这有助于通过在幕后执行代码来增强用户体验。</p>
<p>第十三章,<em>本地化</em>,讨论了在应用程序中提供本地化内容。你将学习如何准备你的应用程序以面向全球用户。</p>
<p>第十四章,<em>部署</em>,不仅将指导你完成将完成的应用程序部署到设备上的必要步骤,还将指导你准备和分发它到 App Store。</p>
<p>第十五章,<em>iOS 5 特性</em>,讨论了最新 iOS 版本中引入的一些许多新特性,例如页面翻页内容导航、iPad 的分割键盘以及轻松样式化多个视图。</p>
<h1 id="你需要这本书什么">你需要这本书什么</h1>
<p>这本书的最低要求是运行 Mac OS X Snow Leopard (10.6.<em>) 或 Lion (10.7.</em>) 的 Mac 电脑。几乎你将使用这本书帮助创建的所有项目都可以在 iOS 模拟器上运行。然而,一些项目将需要设备才能正确工作。你将在第一章,开发工具中找到所有适当的信息。</p>
<h1 id="这本书面向谁">这本书面向谁</h1>
<p>这本书对于没有 iOS 开发和 Objective-C 开发经验的 C# 和 .NET 开发者以及希望过渡到 MonoTouch 和 C# 语言的 Objective-C 开发者,以创建完整、引人入胜的 iPhone、iPod 和 iPad 应用程序并将它们部署到 App Store 的好处至关重要。</p>
<h1 id="术语表">术语表</h1>
<p>在这本书中,您将找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。</p>
<p>文本中的代码单词显示如下:"Apple 提供了另一个基类,即 <code>UIViewController</code>,它负责管理视图。"</p>
<p>代码块设置如下:</p>
<pre><code class="language-swift">public override void ViewDidLoad (){
base.ViewDidLoad();
this.myLabel.Text = "View loaded!";
}
</code></pre>
<p>当我们希望将您的注意力引到代码块的一个特定部分时,相关的行或项目将以粗体显示:</p>
<pre><code class="language-swift">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);
} ;
}
</code></pre>
<p>新术语和重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:"任何标记为 <strong>内容</strong> 的文件将在应用程序包中按原样复制"。</p>
<h3 id="注意">注意</h3>
<p>警告或重要注意事项以如下方框显示。</p>
<h3 id="小贴士">小贴士</h3>
<p>小技巧和技巧显示如下。</p>
<h1 id="读者反馈">读者反馈</h1>
<p>我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大价值的标题非常重要。</p>
<p>要发送一般反馈,只需发送电子邮件到 <code><feedback@packtpub.com></code>,并在邮件主题中提及书名。</p>
<p>如果您需要一本书并且希望我们出版,请通过 www.packtpub.com 上的 <strong>建议标题</strong> 表格或发送电子邮件到 <code><suggest@packtpub.com></code>。</p>
<p>如果你在某个主题上有所专长,并且对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。</p>
<h1 id="客户支持">客户支持</h1>
<p>现在您已经是 Packt Publishing 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。</p>
<h2 id="下载示例代码">下载示例代码</h2>
<p>您可以从您在 <code>www.PacktPub.com</code> 的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 <code>www.PacktPub.com/support</code> 并注册,以便将文件直接通过电子邮件发送给您。</p>
<h2 id="错误">错误</h2>
<p>尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问<code>www.packtpub.com/support</code>,选择您的书籍,点击<strong>勘误提交表单</strong>链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的现有勘误列表中,在“勘误”部分下。任何现有勘误都可以通过从<code>www.packtpub.com/support</code>选择您的标题来查看。</p>
<h2 id="盗版">盗版</h2>
<p>在互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt Publishing,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即向我们提供位置地址或网站名称,以便我们可以寻求补救措施。</p>
<p>请通过<code>< copyright@packtpub.com></code>与我们联系,并提供疑似盗版材料的链接。</p>
<p>我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。</p>
<h2 id="问题和">问题和</h2>
<p>如果你在本书的任何方面遇到问题,可以通过<code>< questions@packtpub.com></code>联系我们,我们将尽力解决。</p>
<h1 id="第一章-开发工具">第一章. 开发工具</h1>
<p>在本章中,我们将涵盖以下内容:</p>
<ul>
<li>
<p>安装先决条件</p>
</li>
<li>
<p>使用 MonoDevelop 创建 iPhone 项目</p>
</li>
<li>
<p>界面构建器</p>
</li>
<li>
<p>创建 UI</p>
</li>
<li>
<p>使用 outlets 访问 UI</p>
</li>
<li>
<p>添加动作</p>
</li>
<li>
<p>编译</p>
</li>
<li>
<p>调试我们的应用程序</p>
</li>
</ul>
<h1 id="简介">简介</h1>
<p>专业人士最关心的一件重要事情是完成工作所需的工具。正如木匠需要凿子来刮木料,或者摄影师需要相机来捕捉光线一样,我们作为开发者需要某些工具,没有这些工具我们无法工作。</p>
<p>在本章中,我们将提供有关开发 iOS 应用程序所需的信息,iOS 是苹果公司移动设备的操作系统。我们将描述每个工具在开发周期中的作用,并逐步介绍每个工具的重要特性,这些特性对于开发我们的第一个应用程序至关重要。</p>
<p>开发 iOS 应用程序所需的工具如下:</p>
<ul>
<li>
<p><strong>基于英特尔处理器的运行 Snow Leopard (10.6.<em>) 或 Lion (10.7.</em>) 操作系统的 Mac 计算机:</strong> 我们需要的必要程序无法安装在其他计算机平台上。</p>
</li>
<li>
<p><strong>iOS SDK 版本 3.2 或更高:</strong> 要能够下载 iOS SDK,开发者必须注册为苹果开发者。iOS SDK 包括两个基本组件。</p>
<ul>
<li>
<p><strong>Xcode:</strong> 苹果公司使用 Objective-C 编程语言开发 iOS 和 Mac 原生应用程序的 IDE。</p>
</li>
<li>
<p><strong>iOS 模拟器:</strong> 在计算机上调试 iOS 应用程序的一个基本程序,无需设备。请注意,许多 iOS 功能在模拟器上无法工作;因此,如果应用程序使用这些功能,则需要设备。</p>
<p>在苹果开发者门户(<code>developer.apple.com</code>)上注册和 SDK 下载都是免费的。如果我们想在设备上运行和调试我们的应用程序或在 App Store 上分发它们,我们需要注册 iOS 开发者计划,该计划需要订阅费。</p>
</li>
</ul>
</li>
<li>
<p><strong>Mono for Mac:</strong> Mono 是微软 .NET 框架的开源实现。它提供了一套跨平台工具、库和编译器,用于在所有主流计算机操作系统(Linux、Mac 和 Windows)上开发 .NET 应用程序。我们需要从 Mono 的网站上获取的最新 Mac 安装程序。</p>
</li>
<li>
<p><strong>MonoTouch:</strong> MonoTouch 是基于 Mono 的 SDK,它为 .NET 开发者提供了使用 C# 作为编程语言开发 iOS 应用程序的能力。MonoTouch 网站上提供免费评估版本(<code>ios.xamarin.com</code>),具有商业版本的所有功能,可以在 iOS 模拟器上调试和运行应用程序,没有过期限制。要在设备上部署应用程序或在 Apple 的 App Store 上分发,需要购买商业许可证。</p>
</li>
<li>
<p><strong>MonoDevelop:</strong> MonoDevelop 是一个用于 .NET 开发的开源 IDE。它为开发者提供了许多功能,例如代码补全、数据库浏览、调试器等。Mac 版本提供了 iOS 项目模板和 MonoTouch 集成。</p>
</li>
</ul>
<p>本章还将介绍如何使用 MonoDevelop 创建我们的第一个 iPhone 项目,使用 Interface Builder 构建其 UI,以及如何在我们的代码中访问应用程序的用户界面,包括 <strong>Outlets</strong> 和 <strong>Actions</strong> 的概念。</p>
<p>最后但同样重要的是,我们将学习如何编译我们的应用程序,可用的编译选项,以及如何在模拟器上进行调试。</p>
<h1 id="安装先决条件">安装先决条件</h1>
<p>有关如何下载和安装使用 MonoTouch 进行开发的必要工具的信息。</p>
<h2 id="准备工作">准备工作</h2>
<p>我们需要在我们的计算机上下载所有必要的组件。首先,需要在 <code>developer.apple.com</code> 上注册为苹果开发者。注册是免费且简单的,并提供访问所有必要开发资源的权限。通过电子邮件确认注册后,我们可以登录并从地址 <code>https://developer.apple.com/devcenter/ios/index.action#downloads</code> 下载 iOS SDK。在撰写本文时,Xcode 的最新版本是 4.2,iOS SDK 的最新版本是 5.0。</p>
<p>有时,当苹果推出其组件的测试版时,它们将通过其门户提供。尽管所有注册用户都可以下载和使用这些组件,但我们的已安装的 MonoTouch 版本可能无法正确与 iOS SDK 或 Xcode 的测试版兼容。因此,在从苹果开发者门户下载和安装新测试版时必须考虑这一点。</p>
<h2 id="如何操作">如何操作...</h2>
<p>为了准备我们的计算机进行 iOS 开发,我们需要按照以下顺序下载和安装必要的组件:</p>
<ol>
<li>
<p><strong>在 OS X Snow Leopard 上的 Xcode 和 iOS SDK:</strong> 下载镜像文件后,将其挂载,然后在将弹出的窗口中双击 Xcode 和 iOS SDK 图标以开始安装。为了继续安装,必须阅读并接受将显示的两个许可协议。之后,您只需选择安装位置并点击 <strong>继续</strong>。<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_01.jpg"></p>
</li>
<li>
<p><strong>在 OS X Lion 上的 Xcode 和 iOS SDK:</strong> 要安装 Xcode 和 SDK,需要登录 Mac App Store。下载的文件基本上是 Xcode 和 SDK 的安装程序。下载完成后,运行 Install Xcode 应用程序,并按照安装说明进行操作。</p>
</li>
<li>
<p><strong>下载和安装 Mono for Mac:</strong> Mono 的 Mac 版本可以通过 Mono 项目网站:<code>www.mono-project.com</code> 下载。</p>
</li>
<li>
<p><strong>下载和安装 MonoTouch:</strong> 可以通过提供电子邮件地址从 <code>ios.xamarin.com/DownloadTrial</code> 下载最新的评估版本。</p>
</li>
<li>
<p><strong>下载并安装 MonoDevelop 2.8+</strong>:尽管使用 MonoTouch 创建 iOS 应用程序不需要 MonoDevelop,但安装它会使开发变得更加容易。可以从 <code>monodevelop.com/Download</code> 下载。</p>
</li>
</ol>
<h2 id="如何工作">如何工作...</h2>
<p>现在我们已经准备好了所有东西,让我们看看每个组件需要什么。</p>
<p>如本章引言所述,iOS SDK 包含三个重要组件。第一个组件是 <strong>Xcode</strong>,它是 Apple 为 iOS 和 Mac 平台开发应用程序的 IDE。它针对 Objective-C 编程语言,这是在 iOS SDK 中编程的主要语言。由于 MonoTouch 是 C# 语言的 SDK,人们可能会 wonder 我们为什么需要 Xcode。除了提供各种调试 iOS 应用程序的工具外,Xcode 还为我们提供了三个重要的组件。第一个是一个设备信息窗口,称为 <strong>组织者</strong>,如图所示,这是在设备上部署我们的应用程序或通过 App Store 分发所必需的证书和配置文件。在组织者内部,我们可以查看我们应用程序的调试信息、崩溃日志,甚至从设备上获取截图!当然,这些只是 Xcode 提供的许多功能中的一部分,但它们超出了本书的讨论范围。</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_02.jpg"></p>
<p>第二个组件是 <strong>界面构建器</strong>。这是一个用户界面设计器,以前是一个独立的应用程序。从 Xcode 4.0 开始,它被集成到 IDE 中。界面构建器提供了构建应用程序用户界面所需的所有必要功能。它也与 .NET 开发者所习惯的不同。</p>
<p>第三个组件是 <strong>iOS 模拟器</strong>。正如其名称所暗示的那样:一个设备模拟器,我们可以用它来运行我们的应用程序,而无需实际设备。iOS 模拟器的重要之处在于它可以选择模拟较旧的 iOS 版本(如果它们已安装在计算机上);包括 iPhone 和 iPad 的界面和设备方向。但是,模拟器缺少一些依赖于硬件的设备功能,例如指南针或加速度计。使用这些功能的应用程序必须在实际设备上进行测试和调试。</p>
<p>Mono 是 .NET 框架的开源实现。它已经存在一段时间了,并为 .NET 开发者提供了使用 .NET 语言编程应用程序的能力,同时针对所有主流操作系统:Linux、Mac 和 Windows。MonoTouch 和 MonoDevelop 都高度依赖于 Mono,使其成为一项必要的资产。</p>
<p>MonoDevelop 是一个开源 IDE,用于使用 Mono(以及 Windows 上的 .NET 框架)开发应用程序。它提供代码补全、数据库浏览器、GTK# 设计器、调试器,以及在我们的情况下,开发 iOS 应用程序所需的各种组件,以便轻松有效地开发。它与 MonoTouch 完美集成,将两者都归类为完整的 iOS 开发环境。</p>
<p>MonoTouch 是一个 SDK,允许 .NET 开发者使用 C# 编程语言为 iOS 开发应用程序。有一个常见的误解,尤其是在新开发者中:由于 MonoTouch 提供了使用 C# 编程的能力,因此可以在 Windows 计算机上安装并使用它。这是完全错误的,因为 MonoTouch 是围绕 iOS SDK 的库构建的,这些库只能在 Mac 计算机上安装。另一个误解是,使用 MonoTouch 开发的 ".NET 兼容" 应用程序需要在设备上安装某种类型的虚拟机才能运行,并且由于这个虚拟机,它们将运行得更慢。这也是错误的,因为 MonoTouch 的先进编译器通过将我们的 C# ".NET 驱动的" 代码编译成原生机器代码来处理这个问题。此外,在设备上安装虚拟机违反了苹果的指导方针。</p>
<h2 id="还有更多">还有更多...</h2>
<p>使用 MonoTouch 开发的应用程序进入 App Store 的机会与其他使用原生 Objective-C 编程语言开发的应用程序一样!这意味着,如果一个应用程序不符合苹果关于应用程序接受度的严格政策,它将失败,无论它是用 Objective-C 还是 C# 编写的。MonoTouch 团队在创建一个让开发者只需担心代码的设计和最佳实践,而无需担心其他事情的 SDK 方面做得非常出色。2010 年 4 月,苹果对其应用程序提交政策进行了修改,实际上禁止了所有未使用公司开发工具创建的应用程序提交到 App Store。MonoTouch 就是其中之一。除了已经投资于 MonoTouch 的开发者中出现的担忧之外,使用它创建的应用程序通常会被 App Store 接受。2010 年 9 月,苹果修改了其政策,放宽了这一规定,为 C# 开发者带来了安慰。</p>
<h3 id="有用链接">有用链接</h3>
<p>以下是一份包含安装所需工具和信息的链接列表:</p>
<ul>
<li>
<p><strong>苹果 iOS 开发者门户</strong>:<code>developer.apple.com/devcenter/ios/index.action</code></p>
</li>
<li>
<p><strong>Mono</strong>:<code>www.mono-project.com</code></p>
</li>
<li>
<p><strong>MonoDevelop</strong>:<code>www.monodevelop.com</code></p>
</li>
<li>
<p><strong>MonoTouch</strong>:<code>ios.xamarin.com</code></p>
</li>
<li>
<p><strong>MonoTouch 安装指南</strong>:<code>ios.xamarin.com/Documentation/Installation</code></p>
</li>
<li>
<p><strong>关于苹果开发者工具的信息</strong>:<code>developer.apple.com/technologies/tools/xcode.html</code></p>
</li>
</ul>
<h3 id="更新">更新</h3>
<p>MonoDevelop 有一个检查可用更新的功能。每次程序启动时,它都会检查 MonoDevelop 本身、MonoTouch 和 Mono 框架的更新。它可以关闭,但并不推荐,因为它有助于保持与最新版本的同步。可以在<strong>MonoDevelop | 检查更新</strong>下找到。</p>
<h2 id="参见">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>编译</em></p>
</li>
<li>
<p><em>调试我们的应用程序</em></p>
</li>
</ul>
<p>第十四章,部署:</p>
<ul>
<li>
<p><em>在其他设备上调试</em></p>
</li>
<li>
<p><em>为 App Store 准备我们的应用程序</em></p>
</li>
</ul>
<h1 id="使用-monodevelop-创建-iphone-项目">使用 MonoDevelop 创建 iPhone 项目</h1>
<p>在这个任务中,我们将讨论使用 MonoDevelop IDE 创建我们的第一个 iPhone 项目。</p>
<h2 id="准备中">准备中...</h2>
<p>现在我们已经安装了所有先决条件,我们将讨论如何使用 MonoDevelop 创建我们的第一个 iPhone 项目。</p>
<p>启动 MonoDevelop。它位于 <code>Applications</code> 文件夹中。MonoDevelop 的默认项目位置是文件夹 <code>/Users/{yourusername}/Projects</code>。如果硬盘上不存在,则在创建我们的第一个项目时创建。如果我们想更改文件夹,可以从菜单栏选择<strong>MonoDevelop | 首选项</strong>。</p>
<p>在左侧面板中选择<strong>加载/保存</strong>,在<strong>默认解决方案位置</strong>字段中输入项目首选位置,然后点击<strong>确定</strong>。</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_03.jpg"></p>
<h2 id="如何操作-1">如何操作...</h2>
<ol>
<li>
<p>启动 MonoDevelop 时,首先加载的是其<strong>起始</strong>页面。从菜单栏选择<strong>文件 | 新建 | 解决方案...</strong>。将显示一个窗口,提供给我们可用的项目选项。</p>
</li>
<li>
<p>在这个窗口中,在左侧面板中选择<strong>C# | MonoTouch | iPhone</strong>。iPhone 项目模板将在中间面板中显示。</p>
</li>
<li>
<p>选择<strong>iPhone 单视图应用程序</strong>。最后,为<strong>解决方案名称</strong>输入 <code>MyFirstiPhoneProject</code> 并点击<strong>前进</strong>。以下截图显示了<strong>新解决方案</strong>窗口:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_04.jpg"></p>
<ul>
<li>就这样!你已经创建了你的第一个 iPhone 项目!你可以构建并运行它。<strong>iOS 模拟器</strong>将启动,但屏幕上仍然只有一个空白浅灰色屏幕。</li>
</ul>
<h3 id="注意-1">注意</h3>
<p>如果由于某种原因左侧面板中没有显示 MonoTouch 部分,这意味着 MonoTouch 和/或 MonoDevelop 的安装出了问题。请参考前面的配方进行正确安装。</p>
<p>如果中间的模板与这个截图中的不同,那是因为你使用的 MonoTouch 和/或 MonoDevelop 版本与本书中使用的版本不同。</p>
<h2 id="它是如何工作的">它是如何工作的...</h2>
<p>让我们看看幕后发生了什么。</p>
<p>当 MonoDevelop 创建一个新的 iPhone 项目,或者更好的,iOS 项目时,它会创建一系列文件。解决方案的结构与创建 .NET/Mono 项目时相同,但有一些额外的文件。解决方案文件可以在 MonoDevelop 窗口的左侧的 <strong>Solution</strong> 面板上查看。如果 <strong>Solution</strong> 面板不可见,可以通过从菜单栏选择 <strong>View | Pads | Solution</strong> 来激活它。</p>
<p>这些文件是构成 iPhone 项目的必要文件:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_05.jpg"></p>
<h3 id="myfirstiphoneprojectviewcontrollerxib">MyFirstiPhoneProjectViewController.xib</h3>
<p>这是包含应用程序视图的文件。<code>XIB</code> 文件基本上是具有特定结构的 XML 文件,可以从 Interface Builder 中读取。它们包含有关用户界面的各种信息,例如它包含的控件类型、它们的属性、出口等。</p>
<h3 id="注意-2">注意</h3>
<p>如果双击 <code>MyFirstiPhoneProjectViewController.xib</code> 或任何具有 <code>.xib</code> 后缀的文件,MonoDevelop 将启动 Xcode,并在 Interface Builder 中打开 <code>XIB</code> 文件的内容。</p>
<p>当我们使用 Interface Builder 创建一个新的界面并保存时,它将以 XIB 格式保存。</p>
<h3 id="myfirstiphoneprojectviewcontrollercs">MyFirstiPhoneProjectViewController.cs</h3>
<p>这是实现视图功能的文件。创建时,文件的内容如下:</p>
<pre><code class="language-swift">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);
}
}
}
</code></pre>
<p>此文件中的代码包含与将要加载的视图相对应的类,以及一些默认方法的重写。这些是我们创建视图控制器时将更频繁使用的方法。以下是每个方法的简要描述:</p>
<ul>
<li>
<p><code>ViewDidLoad:</code> 当控制器视图加载时,会调用此方法。这是我们用来初始化任何附加组件的方法。</p>
</li>
<li>
<p><code>ViewDidUnload:</code> 当视图从内存中卸载时,会调用此方法。</p>
</li>
<li>
<p><code>DidReceiveMemoryWarning:</code> 当应用程序收到内存警告时,会调用此方法。此方法负责卸载视图。</p>
</li>
<li>
<p><code>ShouldAutorotateToInterfaceOrientation:</code> 当我们希望我们的应用程序支持多种方向时,我们会实现此方法。</p>
</li>
</ul>
<h3 id="myfirstiphoneprojectviewcontrollerdesignercs">MyFirstiPhoneProjectViewController.designer.cs</h3>
<p>这是包含我们的主窗口类信息的文件,使用 C# 代码编写。MonoDevelop 为项目中添加的每个 <code>XIB</code> 创建一个 <code>.designer.cs</code> 文件。每次我们通过 Interface Builder 保存对 <code>XIB</code> 的更改时,该文件都会自动生成。这是由 MonoDevelop 负责的,以确保我们在界面中做出的更改能够立即反映到代码中。我们不得直接修改此文件,因为当相应的 XIB 使用 Interface Builder 保存时,这些更改将会丢失。另外,如果通过 Interface Builder 没有保存任何内容,如果手动修改它,很可能会导致编译错误。</p>
<p>创建新项目时,文件的内容如下:</p>
<pre><code class="language-swift">namespace MyFirstiPhoneProject{
partial class MyFirstiPhoneProjectViewController{}
}
</code></pre>
<p>就像任何其他 .NET 项目一样,会创建一个与解决方案名称相同的命名空间:</p>
<pre><code class="language-swift">namespace MyFirstiPhoneProject
</code></pre>
<p>此文件包含我们<code>MyFirstiPhoneProjectViewController</code>类的其他部分声明。它被<code>RegisterAttribute</code>装饰。</p>
<p><code>RegisterAttribute</code>用于将类暴露给底层的 Objective-C 运行时。字符串参数声明了我们的类将以什么名称暴露给运行时。它可以是我们想要的任何名称,但始终将其设置为我们的 C#类名称是一个好习惯。该属性在 MonoTouch 的内部使用得非常频繁,因为它将所有本地的<code>NSObject</code>类与其 C#对应类绑定在一起。</p>
<h3 id="注意-3">注意</h3>
<p><code>NSObject</code>是一个根类或基类。在.NET 世界中,它相当于<code>System.Object</code>。两者之间的唯一区别是,所有.NET 对象都继承自<code>System.Object</code>,但在 Objective-C 中,大多数(而不是所有)Objective-C 对象继承自<code>NSObject</code>。所有继承自<code>NSObject</code>的本机对象的 C#对应类也继承自其 MonoTouch <code>NSObject</code>对应类。</p>
<h3 id="appdelegatecs">AppDelegate.cs</h3>
<p>此文件包含<code>AppDelegate</code>类。文件内容如下:</p>
<pre><code class="language-swift">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.
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;
}
}
}
</code></pre>
<p>第一部分对.NET 开发者来说很熟悉,它包含适当的<code>using</code>指令,用于导入使用所需的命名空间。</p>
<pre><code class="language-swift">using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
</code></pre>
<p>前三个<code>using</code>指令使我们能够使用.NET/Mono 世界的特定和熟悉的命名空间与 MonoTouch 一起使用!</p>
<h3 id="注意-4">注意</h3>
<p>虽然这三个命名空间(System, <code>System.Collections.Generic, System.Linq</code>)提供的功能几乎与它们知名的.NET/Mono 对应项相同,但它们被包含在专门为与 MonoTouch 一起使用而创建的程序集内,并且当然与它一起分发。使用.NET 或 Mono 编译的程序集不能直接用于 MonoTouch 项目。</p>
<p><code>MonoTouch.Foundation</code>命名空间是对原生 Objective-C 基础框架的包装,其中包含提供基本功能的类。这些对象的名称与原生基础框架中找到的相同“NS”前缀相同。一些例子是<code>NSObject, NSString, NSValue</code>等等。除了<code>NS-前缀</code>对象外,<code>MonoTouch.Foundation</code>命名空间还包含用于绑定到原生对象的属性,例如我们之前看到的<code>RegisterAttribute</code>。<code>MonoTouch.UIKit</code>命名空间是对原生 Objective-C <code>UIKit</code>框架的包装。正如其名称所暗示的,该命名空间包含提供界面功能的类、委托和事件。除了两个类<code>DraggingEventArgs</code>和<code>ZoomingEventArgs</code>外,所有对象的名称都共享相同的“UI”前缀。此时应该很清楚,这两个命名空间对于所有 MonoTouch 应用都是必不可少的,并且它们的对象将被频繁使用。</p>
<p>该类继承自<code>UIApplicationDelegate</code>类,使其成为我们应用的<code>UIApplication</code> <strong>Delegate</strong>对象。</p>
<h3 id="注意-5">注意</h3>
<p>在 Objective-C 世界中,委托对象的概念与 C# 中的委托有所不同。这将在第二章用户界面:视图中详细解释。</p>
<p><code>AppDelegate</code> 类包含两个字段和一个方法:</p>
<pre><code class="language-swift">UIWindow window;
MyFirstiPhoneProjectViewController viewController;
public override bool FinishedLaunching (UIApplication app, NSDictionary options) {
</code></pre>
<p><code>UIWindow</code> 对象定义了我们应用程序的主窗口,而 <code>MyFirstiPhoneProjectViewController</code> 是一个变量,它将持有应用程序的视图控制器。</p>
<h3 id="注意-6">注意</h3>
<p>iOS 应用程序通常只有一个窗口,类型为 <code>UIWindow</code>。<code>UIWindow</code> 的概念与 .NET <code>System.Windows.Form</code> 有所不同。<code>UIWindow</code> 是应用程序启动时首先显示的控制,所有后续视图都按层次添加在其下方。</p>
<p>如其名称所示,<code>FinishedLaunching</code> 方法在应用程序完成初始化过程时被调用。这是我们必须向用户展示用户界面的方法。此方法的实现必须轻量级,因为如果它不能在调用后及时返回,iOS 将终止应用程序。这是为了通过防止开发者在初始化时执行复杂和长时间运行的任务(例如连接到网络服务以接收数据)来为用户提供更快的用户界面加载时间。应用程序参数是应用程序的 <code>UIApplication</code> 对象,也可以通过静态属性 <code>UIApplication.SharedApplication</code> 访问。选项参数可能包含有关应用程序启动方式的信息,也可能不包含。目前我们不需要它。</p>
<p>如其名称所示,<code>FinishedLaunching</code> 方法在应用程序完成初始化过程时被调用。这是我们必须向用户展示用户界面的方法。此方法的实现必须轻量级,因为如果它不能在调用后及时返回,iOS 将终止应用程序。这是为了通过防止开发者在初始化时执行复杂和长时间运行的任务(例如连接到网络服务以接收数据)来为用户提供更快的用户界面加载时间。应用程序参数是应用程序的 <code>UIApplication</code> 对象,也可以通过静态属性 <code>UIApplication.SharedApplication</code> 访问。选项参数可能包含有关应用程序启动方式的信息,也可能不包含。目前我们不需要它。</p>
<p>此类型项目的 <code>FinishedLaunching</code> 方法的默认实现如下:</p>
<pre><code class="language-swift">window = new UIWindow (UIScreen.MainScreen.Bounds);
</code></pre>
<p><code>UIWindow</code> 对象使用屏幕大小进行初始化。</p>
<pre><code class="language-swift">viewController = new MyFirstiPhoneProjectViewController ("MyFirstiPhoneProjectViewController", null);
window.RootViewController = viewController;
</code></pre>
<p>视图控制器被初始化并设置为窗口的根视图控制器。</p>
<pre><code class="language-swift">window.MakeKeyAndVisible ();
return true;
</code></pre>
<p>窗口通过<code>window.MakeKeyAndVisible()</code>调用显示在屏幕上,并且该方法返回。此方法必须在<code>FinishedLaunching</code>方法内部调用,否则应用程序的用户界面将不会按预期向用户展示。最后但同样重要的是,<code>return true</code>行通过标记其执行完成来返回方法。</p>
<h3 id="maincs">Main.cs</h3>
<p>在<code>Main.cs</code>文件中是程序运行时生命周期的开始:</p>
<pre><code class="language-swift">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");
}
}
}
</code></pre>
<p>类似于在.NET <code>System.Windows.Forms</code>应用程序中的以下调用,<code>UIApplication.Main</code>方法启动消息循环,或运行循环,负责通过<code>AppDelegate</code>类将通知派发到应用程序,其中包含我们可以重写的处理程序。</p>
<pre><code class="language-swift">// In a .NET application
Application.Run(new Form1());
</code></pre>
<p>事件处理程序,如<code>FinishedLaunching</code>、<code>ReceiveMemoryWarning</code>或<code>DidEnterBackground</code>,只是这些通知中的一些。除了通知派发机制之外,<code>UIApplication</code>对象包含所有存在的<code>UIWindow</code>对象的列表;通常是其中一个。iOS 应用程序必须有一个<code>UIApplication</code>对象,或者从它继承的类,并且该对象必须有一个相应的<code>UIApplicationDelegate</code>对象。这是我们之前看到的<code>AppDelegate</code>类实现。</p>
<h3 id="infoplist">Info.plist</h3>
<p>这个文件基本上是应用程序的设置文件。它具有一个简单的属性值结构,定义了 iOS 应用程序的各种设置,例如它支持的朝向、图标、支持的 iOS 版本、它可以安装的设备等等。如果我们在这个文件上双击 MonoDevelop,它将在嵌入的编辑器中打开,该编辑器专门设计用于<code>.plist</code>文件。这是新项目中我们的文件看起来像:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_06.jpg"></p>
<p><code>Info.plist</code>是一个 XML 文件。虽然我们可以在文本编辑器中手动编辑该文件,例如,但不建议这样做。嵌入的编辑器是编辑的最佳方式。</p>
<h2 id="更多内容">更多内容...</h2>
<p>MonoDevelop 为开发 iOS 应用程序提供了许多不同的项目模板。以下是一个列表,描述了每个项目模板的用途:</p>
<ul>
<li>
<p><strong>空项目</strong>: 它是一个没有任何视图的空项目。</p>
</li>
<li>
<p><strong>实用应用程序</strong>: 实用应用程序是一种特殊的 iOS 应用程序,它提供一屏功能,在许多情况下还提供另一屏进行配置。</p>
</li>
<li>
<p><strong>主从应用</strong>: 这种类型的项目创建了一个支持在多个屏幕间导航的模板。它包含两个视图控制器。</p>
</li>
<li>
<p><strong>单视图应用</strong>: 这种模板类型是我们在这个菜谱中使用的。</p>
</li>
<li>
<p><strong>标签页应用</strong>: 这是一个添加标签栏控制器以在类似标签的界面中管理两个视图控制器的模板。</p>
</li>
<li>
<p><strong>OpenGL 应用</strong>: 这是一个用于创建由 OpenGL 驱动的应用程序或游戏的模板。</p>
</li>
</ul>
<p>这些模板适用于 iPhone、iPad 和通用(iPhone 和 iPad)项目。它们也适用于 Interface Builder 的<strong>Storyboarding</strong>应用程序设计。</p>
<h3 id="注意-7">注意</h3>
<p>除非另有说明,所有针对 iPhone 的项目模板也适用于 iPod Touch。</p>
<h3 id="monotouch-组件列表">MonoTouch 组件列表</h3>
<p>MonoTouch 支持的组件可以在以下链接中找到:<code>ios.xamarin.com/Documentation/Assemblies</code>。</p>
<h2 id="相关内容">相关内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>创建 UI</em></p>
</li>
<li>
<p><em>通过出口访问 UI</em></p>
</li>
</ul>
<p>在这本书中:</p>
<p>第二章,用户界面:视图:</p>
<ul>
<li><em>添加和自定义视图</em></li>
</ul>
<h1 id="界面构建器">界面构建器</h1>
<p>苹果用户界面设计师简介。</p>
<h2 id="如何做">如何做...</h2>
<p>如果您已安装 iOS SDK,那么您已经在计算机上安装了带有 Interface Builder 的 Xcode。</p>
<ol>
<li>
<p>前往 MonoDevelop 并打开我们之前创建的项目<code>MyFirstiPhoneProject</code>。</p>
</li>
<li>
<p>在左侧的<strong>解决方案</strong>面板上,双击<strong>MyFirstiPhoneProjectViewController.xib</strong>。MonoDevelop 会启动 Xcode,并在 Interface Builder 中加载文件!</p>
</li>
<li>
<p>在工具栏的右侧,在 Xcode 窗口的顶部,选择适当的编辑和查看选项,如下所示:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_07.jpg"></p>
<ul>
<li>以下截图展示了打开 XIB 文件时的 Interface Builder 外观:</li>
</ul>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_08.jpg"></p>
<h2 id="它是如何工作的-1">它是如何工作的...</h2>
<p>现在我们已经加载了带有我们应用程序视图控制器的 Interface Builder,让我们熟悉一下它。</p>
<p>用户界面设计师直接连接到 Xcode 项目。当我们添加一个对象时,Xcode 会自动生成代码来反映我们所做的更改。MonoDevelop 会为我们处理这件事,因为当我们双击 XIB 文件时,它会自动创建一个临时 Xcode 项目,以便我们可以在用户界面中进行我们想要的更改。因此,我们除了设计我们应用程序的用户界面之外,没有更多的事情要做:</p>
<p>界面构建器分为三个区域。下面简要描述每个区域。</p>
<ol>
<li>
<p><strong>导航区域</strong>:在这个区域,我们可以看到 Xcode 项目中的文件。</p>
</li>
<li>
<p><strong>编辑区域</strong>:这个区域是我们设计用户界面的地方。</p>
</li>
<li>
<p><strong>实用区域</strong>:这个区域包含<strong>检查器</strong>和<strong>库</strong>面板。<strong>检查器</strong>是我们配置每个对象的地方,而<strong>库</strong>面板是我们查找对象的地方。</p>
</li>
</ol>
<p>编辑区域分为两个部分。左侧的是<strong>设计师</strong>,而右侧的是<strong>辅助</strong>编辑器。在辅助编辑器内部,我们可以看到与设计师中选定的项目对应的底层 Objective-C 源代码文件。尽管我们不需要编辑 Objective-C 源代码,但我们稍后需要辅助编辑器。</p>
<h2 id="更多内容-1">更多内容...</h2>
<p>我们在 Interface Builder 中看到了<code>XIB</code>文件的样子。但是,关于这些文件还有更多内容。我们之前提到,<code>XIB</code>文件是 Interface Builder 可读取的带有适当信息的 XML 文件。问题是,当项目编译时,编译器也会编译<code>XIB</code>文件,将其转换为二进制等效文件:<code>NIB</code>文件。<code>XIB</code>和<code>NIB</code>文件包含完全相同的信息。它们之间的唯一区别是,<code>XIB</code>文件是可读的,而<code>NIB</code>文件则不是。例如,当我们编译创建的项目时,<code>MyFirstiPhoneProjectViewController.xib</code>文件将在输出文件夹中变为<code>MyFirstiPhoneProjectViewController.nib</code>。除了二进制转换外,编译器还会对<code>NIB</code>文件进行压缩。因此,<code>NIB</code>文件的大小将比<code>XIB</code>文件小得多。</p>
<p>关于<code>XIB</code>文件,还有更多内容。开发者如何管理项目中的<code>XIB</code>文件对于应用程序的性能和稳定性非常重要。最好有多个、较小的<code>XIB</code>文件,而不是一个或两个大的文件。这可以通过将用户界面分割成多个<code>XIB</code>文件来实现。这可能看起来有点困难,但正如我们将在本书后面看到的,这实际上非常简单。我们需要许多较小的<code>XIB</code>文件而不是少数几个大的文件,这是因为 iOS 管理内存的方式。当应用程序启动时,iOS 会将<code>NIB</code>文件作为一个整体加载到内存中,然后,其中的所有对象都会被实例化。因此,保留那些不一定总是会被使用的对象在<code>NIB</code>文件中是一种内存浪费。此外,请记住,你正在为移动设备开发,其可用资源与桌面计算机相比并不匹配,无论其能力如何。</p>
<h3 id="更多信息">更多信息</h3>
<p>你可能已经注意到,在<strong>检查器</strong>面板的<strong>属性</strong>选项卡中,有一个名为<strong>模拟度量</strong>的部分。该部分下的选项帮助我们直接在设计器中看到我们的界面在设备的状态栏、工具栏或导航栏下的样子。尽管这些选项保存在<code>XIB</code>文件中,但它们与实际运行时的应用程序无关。例如,如果我们将<strong>状态栏</strong>选项设置为<strong>无</strong>,这并不意味着我们的应用程序将没有状态栏启动。</p>
<h3 id="注意-8">注意</h3>
<p><strong>状态栏</strong>是显示在设备屏幕顶部的一部分,向用户显示某些信息,如当前时间、电池状态、iPhone 上的运营商名称等。</p>
<h2 id="相关内容-1">相关内容</h2>
<p>在本章:</p>
<ul>
<li>
<p><em>创建 UI</em></p>
</li>
<li>
<p><em>通过输出访问 UI</em></p>
</li>
<li>
<p><em>添加动作</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第二章, 用户界面:视图:</p>
<ul>
<li><em>添加和自定义视图</em></li>
</ul>
<p>第三章: 用户界面:视图控制器:</p>
<ul>
<li><em>视图控制器和视图</em></li>
</ul>
<h1 id="创建-ui">创建 UI</h1>
<p>在这个菜谱中,我们将学习如何在用户界面中添加和管理控件。</p>
<h2 id="准备工作-1">准备工作</h2>
<p>让我们在界面中添加一些控件。首先,在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序项目。将项目命名为<code>ButtonInput</code>。当它打开时,双击<strong>解决方案</strong>面板中的<code>ButtonInputViewController.xib</code>以使用 Interface Builder 打开它。</p>
<h2 id="如何操作-2">如何操作...</h2>
<p>现在我们有一个新项目,Interface Builder 已打开<code>ButtonInputViewController.xib</code>文件,我们将向其中添加一些控件。</p>
<h3 id="添加一个标签">添加一个标签</h3>
<ol>
<li>
<p>如果尚未选择,转到<strong>库</strong>面板并从下拉列表中选择<strong>对象</strong>。选择<strong>标签</strong>对象。</p>
</li>
<li>
<p>将<strong>标签</strong>拖放到设计器中视图的灰色空间,位于上半部分。</p>
</li>
<li>
<p>从左侧和右侧选择并调整<strong>标签</strong>的大小,使其吸附到当您接近视图边缘时出现的虚线上。</p>
</li>
<li>
<p>再次,选择<strong>标签</strong>后,转到<strong>检查器</strong>面板,选择<strong>属性</strong>选项卡,然后在<strong>布局</strong>部分,点击中间的对齐按钮。恭喜你,你已经在应用程序的主视图中添加了一个<strong>标签</strong>!</p>
</li>
</ol>
<h3 id="添加一个按钮">添加一个按钮</h3>
<p>我们将执行类似的步骤来在我们的界面中添加按钮。</p>
<ol>
<li>
<p>再次,在<strong>库</strong>面板中,在<strong>对象</strong>部分,选择<strong>按钮</strong>对象。它位于<strong>标签</strong>对象旁边。</p>
</li>
<li>
<p>将其拖放到视图的下半部分。将其中心与之前添加的<strong>标签</strong>的中心对齐。会出现一条虚线,当两个控件的中心几乎对齐时,<strong>按钮</strong>会自动吸附到它上。</p>
</li>
<li>
<p>将<strong>按钮</strong>调整到与<strong>标签</strong>相同的宽度。由于<strong>标签</strong>具有透明背景,您无法确切地看到它的宽度,因此当您调整大小并出现三条虚线时,您就会知道<strong>按钮</strong>的宽度相同。</p>
</li>
<li>
<p>现在,让我们向<strong>按钮</strong>添加一些文本。选择它并转到<strong>检查器</strong>面板。在<strong>属性</strong>选项卡中,在<strong>标题</strong>字段中输入<code>请点击此处!</code>。</p>
</li>
<li>
<p>添加按钮后,通过菜单栏中的<strong>文件 | 保存</strong>保存文档。主视图现在应该看起来像以下截图(在此处显示已调整大小):</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_09.jpg"></p>
<h2 id="它是如何工作的-2">它是如何工作的...</h2>
<p>如您所见,尽管 Interface Builder 的一些概念似乎很难,但它使用起来相当简单。它还提供了很多反馈。当我们拖动对象时,光标上会出现一个绿色的圆形十字,表示我们可以将对象放在那里。此外,当我们调整控件大小时,我们会在其旁边看到其尺寸。</p>
<p>您也可以通过修改<strong>检查器</strong>面板中<strong>大小</strong>选项卡中的值来调整控件的大小和位置。<strong>大小</strong>选项卡中的另一个有用功能是<strong>自动调整大小</strong>。<strong>自动调整大小</strong>为控件提供布局选项,当我们的应用程序需要支持不同的设备方向时,这可以非常有用。您可以选择一个控件,然后点击<strong>自动调整大小</strong>部分中左边的正方形外部或内部的线条。旁边的图像会动画显示,给您一个印象,当布局改变时控件将如何表现。</p>
<h2 id="更多内容-2">更多内容...</h2>
<p>现在,让我们尝试在 iOS 模拟器上运行应用程序。回到 MonoDevelop,如果尚未选择,请选择<strong>调试 | iPhoneSimulator</strong>的项目配置。现在代码中不需要做任何事情;只需点击<strong>运行</strong>按钮。它是配置组合框右侧的第三个按钮,带有双齿轮图标。当编译完成后,iOS 模拟器将自动启动并运行我们刚刚创建的应用程序!您甚至可以用鼠标点击<strong>按钮</strong>来“点击”它,并看到它的响应。当然,我们的应用程序目前没有任何其他功能。</p>
<h3 id="设置按钮标题">设置按钮标题</h3>
<p>通过双击并输入首选标题,可以轻松地设置<strong>按钮</strong>或<strong>标签</strong>的标题。这样做,并观察 Interface Builder 如何显示要执行的操作。</p>
<h2 id="参见-1">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>编译</em></p>
</li>
<li>
<p><em>调试我们的应用程序</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第二章, 用户界面:视图:</p>
<ul>
<li>
<p><em>使用按钮接收用户输入</em></p>
</li>
<li>
<p><em>使用标签显示文本</em></p>
</li>
</ul>
<h1 id="通过出口访问-ui">通过出口访问 UI</h1>
<p>在本食谱中,我们将讨论<strong>出口</strong>的概念以及它们与 MonoTouch 的用法。</p>
<h2 id="准备工作-2">准备工作</h2>
<p>在上一个任务中,我们学习了如何添加控件来形成我们应用程序的基本界面。在本任务中,我们将讨论如何在代码中访问和使用这些控件。启动 MonoDevelop,并打开我们之前创建的项目<code>ButtonInput</code>。通过在<strong>解决方案</strong>面板中双击它来在 Interface Builder 中打开项目的<code>ButtonInputViewController.xib</code>。</p>
<h2 id="如何操作-3">如何操作...</h2>
<ol>
<li>
<p>打开<strong>辅助编辑器</strong>,<em>Ctrl-drag</em>从标签到 Objective-C 源文件,如图下所示:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_10.jpg"></p>
<ul>
<li>当您释放鼠标时,会出现一个上下文窗口,类似于以下截图:</li>
</ul>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_11.jpg"></p>
</li>
<li>
<p>在上下文窗口的<strong>名称</strong>字段中输入<code>labelStatus,</code>,然后点击<strong>连接</strong>。</p>
</li>
<li>
<p>对按钮也做同样的操作,命名为<code>buttonTap</code>。通过在菜单栏中选择<strong>文件 | 保存</strong>或按键盘上的<em>Option</em> - <em>S</em>来保存 Interface Builder 文档。</p>
</li>
<li>
<p>回到 MonoDevelop,在<code>ButtonInputViewController</code>类的<code>ViewDidLoad</code>方法中输入以下代码:</p>
<pre><code class="language-swift">// 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!";
};
</code></pre>
</li>
<li>
<p>这段代码片段为按钮的 TouchUpInside 事件添加了一个处理程序。这个事件类似于 System.Windows.Forms 中按钮控制的 Clicked 事件。它还展示了匿名方法的使用,这仅仅显示了 MonoTouch 如何为.NET 开发者提供 C#功能。就是这样!我们的应用程序现在已经准备好了,带有功能性的控件。</p>
</li>
<li>
<p>在模拟器上编译并运行它。当你点击按钮时,你会看到标签文本的变化。</p>
</li>
</ol>
<h2 id="它是如何工作的-3">它是如何工作的...</h2>
<p>出口机制基本上是一种将 Interface Builder 对象与代码连接起来的方式。它们是必要的,因为这是我们访问使用 Interface Builder 创建的用户界面对象的唯一方式。这就是 Interface Builder 的工作方式,它不仅仅是 MonoTouch 的解决方案。一个对象的出口提供了一个变量,这样我们就能在项目中使用它。MonoTouch 使开发者的生活变得更加容易,因为当我们创建 Interface Builder 中的出口并将它们连接时,MonoDevelop 会在后台自动生成与这些出口相关的代码。这就是<code>ButtonInputViewController.designer.cs</code>添加的内容,为我们提供了访问我们创建的控件:</p>
<pre><code class="language-swift">
MonoTouch.UIKit.UILabel labelStatus { get; set; }
MonoTouch.UIKit.UIButton buttonTap { get; set; }
</code></pre>
<p>这些属性为我们提供了访问控制的功能。它们被装饰了<code>OutletAttribute</code>。你可以看到属性的名称与我们为出口输入的确切名称完全相同。这非常重要,因为我们只需要为出口提供一次名称,就不必担心在代码的不同部分重复相同的命名约定。注意,控制变量的类型与我们在用户界面中拖放的控制类型完全相同。这些信息存储在<code>XIB</code>文件中,MonoDevelop 会相应地读取这些信息。</p>
<h2 id="还有更多-1">还有更多...</h2>
<p>要删除出口,你首先必须断开连接。例如,要删除<code>buttonTap</code>出口,在按钮上<em>Ctrl</em> - 点击。在出现的面板中,点击出口旁边的小<strong>(x)</strong>。这将断开出口的连接。</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_12.jpg"></p>
<p>之后,从 Objective-C 源文件中删除以下代码:</p>
<pre><code class="language-swift">@property (retain, nonatomic) IBOutlet UIButton *buttonTap;
</code></pre>
<p>当你保存文档时,出口将从 MonoDevelop 项目中移除。</p>
<h3 id="通过代码添加出口">通过代码添加出口</h3>
<p>添加出口的另一种方式是在你的 C#类中创建一个属性,并用<code>OutletAttribute</code>装饰它:</p>
<pre><code class="language-swift">
UIButton ButtonTap { get; set; }
</code></pre>
<p>当你在 Interface Builder 中打开<code>XIB</code>文件时,出口已经被添加到用户界面中。然而,你仍然需要将其连接到相应的控制。最简单的方法是在控制上<em>Ctrl</em> - 点击,然后从<strong>新引用出口</strong>拖动到设计区域左侧的<strong>文件所有者</strong>对象:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_13.jpg"></p>
<p>当你释放光标时,从出现的迷你上下文菜单中选择<strong>ButtonTap</strong>出口。</p>
<h3 id="注意-9">注意</h3>
<p>注意,是 MonoDevelop 监控在 Interface Builder 中所做的更改,而不是反过来。当在 MonoDevelop 项目中做更改时,请确保始终从 MonoDevelop 内部打开 <code>XIB</code> 文件。</p>
<h2 id="参见-2">参见</h2>
<p>在这一章中:</p>
<ul>
<li>
<p><em>界面构建器</em></p>
</li>
<li>
<p><em>创建 UI</em></p>
</li>
<li>
<p><em>添加动作</em></p>
</li>
</ul>
<p>在这本书中:</p>
<p>第二章,用户界面:视图:</p>
<ul>
<li><em>添加和自定义视图</em></li>
</ul>
<h1 id="添加动作">添加动作</h1>
<p>在这个菜谱中,我们讨论了 <strong>动作</strong> 的概念及其与 MonoTouch 的使用。</p>
<h2 id="准备工作-3">准备工作</h2>
<p>在这个任务中,我们将讨论如何使用用户界面控件中的动作。在 MonoDevelop 中创建一个新的 iPhone 单视图应用程序项目,并将其命名为 <code>ButtonInputAction</code>。在 Interface Builder 中打开 <code>ButtonInputActionViewController.xib</code>,并添加与上一个任务中 <code>ButtonInput</code> 项目相同的控件、出口和连接。现在不要在项目中添加任何代码。</p>
<h2 id="如何做-1">如何做...</h2>
<p>向界面对象添加动作类似于添加出口。</p>
<ol>
<li>
<p>在 Interface Builder 中,<em>Ctrl-drag</em> 从按钮到源代码文件。在将显示的上下文窗口中,将 <strong>连接</strong> 字段从 <strong>出口</strong> 更改为 <strong>动作</strong>。</p>
</li>
<li>
<p>在 <strong>名称</strong> 字段中输入 <code>OnButtonTap</code>,并在 <strong>事件</strong> 字段中选择 <strong>触摸抬起</strong>,如果尚未选择。</p>
</li>
<li>
<p>点击 <strong>连接</strong> 按钮,并保存文档。</p>
</li>
<li>
<p>在 <code>ButtonInputActionViewController</code> 类中,添加以下方法:</p>
<pre><code class="language-swift">partial void OnButtonTap(NSObject sender){
this.labelStatus.Text = "Button tapped!";
}
</code></pre>
</li>
<li>
<p>应用程序已准备就绪!在模拟器中编译并运行。</p>
</li>
<li>
<p>点击按钮,你会看到标签中的文本发生变化,就像我们在上一个应用程序中创建的那样。</p>
</li>
</ol>
<h2 id="它是如何工作的-4">它是如何工作的...</h2>
<p>Objective-C 中的动作相当于 C# 中的控制事件。它们负责传递各种对象的通告信号。在这个例子中,我们不是在按钮的 <code>TouchUpInside</code> 事件上连接一个处理程序,而是为它添加了一个动作。你可能已经注意到了,我们添加的作为动作处理程序的方法被声明为 <code>partial</code>。这是因为 MonoDevelop 已经为我们声明了一个部分方法声明。这是我们在 Interface Builder 中保存文档时产生的代码:</p>
<pre><code class="language-swift">
partial void OnButtonTap (MonoTouch.Foundation.NSObject sender);
</code></pre>
<p>方法的部分声明用 <code>ActionAttribute</code> 标记。这是来自 <code>MonoTouch.Foundation</code> 命名空间的其他属性之一,它允许我们将方法公开为 Objective-C 动作。你看到传递给属性的字符串参数与我们输入到 Interface Builder 中的动作名称完全相同,并在其后面附加了一个冒号(:)。</p>
<h3 id="注意-10">注意</h3>
<p>Objective-C 中的冒号表示参数的存在。例如,<code>doSomething</code> 与 <code>doSomething:</code> 不同。它们的区别在于第一个不接受任何参数,而第二个接受一个参数。</p>
<p>动作名称末尾的冒号表示有一个参数;在这种情况下,参数是<code>MonoTouch.UIKit.NSObject</code> sender。这是我们在模拟器中点击按钮时应用程序的外观:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_14.jpg"></p>
<h2 id="还有更多-2">还有更多...</h2>
<p>之前的示例只是为了展示如何在 MonoTouch 项目中实现动作。用动作替换事件基本上取决于开发者。</p>
<h2 id="参考信息">参考信息</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>界面构建器</em></p>
</li>
<li>
<p><em>创建用户界面</em></p>
</li>
<li>
<p><em>通过出口访问用户界面</em></p>
</li>
</ul>
<h1 id="编译">编译</h1>
<p>在本食谱中,我们将讨论如何使用 MonoDevelop 编译项目。</p>
<h2 id="准备工作-4">准备工作</h2>
<p>MonoDevelop 提供了许多不同的编译选项。在本任务中,我们将讨论这些选项。我们将使用本章中较早创建的<code>ButtonInput</code>项目进行工作。</p>
<h2 id="如何操作-4">如何操作...</h2>
<ol>
<li>
<p>在 MonoDevelop 中加载项目后,转到<strong>项目 | ButtonInput 选项</strong>。</p>
</li>
<li>
<p>在出现的窗口中,从左侧面板的<strong>构建</strong>部分选择<strong>iPhone 构建</strong>。将项目配置设置为<strong>调试</strong>,平台设置为<strong>iPhoneSimulator</strong>。在<strong>链接器行为</strong>字段中,从下拉菜单中选择<strong>链接所有程序集</strong>。在<strong>SDK 版本</strong>字段中,如果尚未选择,请选择<strong>默认</strong>。</p>
</li>
<li>
<p>现在,转到左侧面板上的<strong>iPhone 应用程序</strong>。</p>
</li>
<li>
<p>在<strong>摘要</strong>选项卡中,在<strong>应用程序名称</strong>字段中输入<code>Button Input</code>,在<strong>版本</strong>字段中输入<code>1.0</code>。在<strong>部署目标</strong>下拉菜单中选择<strong>3.0</strong>版本。以下截图显示了<strong>iPhone 应用程序</strong>选项窗口:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_15.jpg"></p>
</li>
<li>
<p>点击<strong>确定</strong>按钮,然后在菜单栏中点击<strong>构建 | 构建全部</strong>来编译项目。</p>
</li>
</ol>
<h2 id="它是如何工作的-5">它是如何工作的...</h2>
<p>我们为项目设置了一些选项。让我们看看这些选项为编译定制提供了什么。</p>
<h3 id="iphone-构建选项">iPhone 构建选项</h3>
<p>我们设置的第一个选项是关于链接器。<strong>链接器</strong>是由 MonoTouch 团队开发的一个工具,并在 SDK 中提供。每次编译 MonoTouch 项目时,编译器不仅编译项目,还会编译它需要的所有 MonoTouch 框架的程序集,这样最终的应用程序才能在设备(或模拟器)上运行。这意味着每个应用程序都附带其自己的 MonoTouch 框架编译版本。这样做意味着最终的应用程序包大小相当大。这就是链接器的作用所在。它所做的就是删除所有未使用的代码的程序集,这样编译器就只会编译应用程序需要和使用的代码。这导致应用程序包的大小大大减小:在移动应用程序中这是一个宝贵的资产。特别是,由于苹果通过蜂窝网络对每个文件有 20MB 的下载限制。链接器选项如下:</p>
<p>我们首先设置的选项是关于链接器的。<strong>链接器</strong>是由 MonoTouch 团队开发的一个工具,并在 SDK 中提供。每次编译 MonoTouch 项目时,编译器不仅编译项目,还会编译它需要的所有 MonoTouch 框架的程序集,以便最终应用程序能够在设备(或模拟器)上运行。这意味着每个应用程序都附带其自己的编译版本的 MonoTouch 框架。这样做意味着最终的应用程序包大小相当大。这就是链接器发挥作用的地方。它所做的就是精简所有未使用的代码,以便编译器只编译应用程序需要和使用的代码。这导致应用程序包的大小大大减小:在移动应用程序中这是一笔宝贵的财富。特别是,由于苹果通过蜂窝网络对每个文件有 20 MB 的下载限制。链接器选项如下:</p>
<ul>
<li>
<p><strong>不链接</strong>:当在模拟器上调试时使用此选项。链接器被关闭,所有程序集都按原样编译。这提供了更快的编译时间。</p>
</li>
<li>
<p><strong>仅链接 SDK 程序集</strong>:在这里,链接器仅精简 MonoTouch 框架的程序集。项目程序集保持完整。此选项也有效地减小了最终的应用程序大小。</p>
</li>
<li>
<p><strong>链接所有程序集</strong>:在这里,链接器在所有程序集上被激活。这会稍微减少一些大小。当在代码中使用<strong>反射</strong>或<strong>序列化</strong>时,需要小心使用此选项。代码中通过反射使用的类型和方法对链接器是透明的。如果代码中存在这种情况,请使用<code>PreserveAttribute</code>对这些类型或方法进行装饰。此属性基本上是通知链接器不要参与精简过程。</p>
</li>
</ul>
<p>在 SDK 版本字段中,我们设置了编译应用程序将使用的<strong>iOS SDK 版本</strong>。将其设置为<strong>默认</strong>将自动选择系统上安装的最高 SDK 版本。</p>
<h3 id="iphone-应用程序选项">iPhone 应用程序选项</h3>
<p>在项目选项中“构建”部分的<strong>iPhone 应用程序</strong>窗口中,我们设置了三个选项。第一个选项是<strong>应用程序名称</strong>。这是将在模拟器、设备和 App Store 上显示的应用程序包的名称。正如我们所看到的,我们通常可以在名称中添加空格。第二个选项<strong>版本</strong>定义了应用程序的版本。这是当应用程序最终通过 App Store 分发时将显示的版本。第三个选项<strong>部署目标</strong>是应用程序可以安装的最小 iOS 版本。</p>
<h2 id="还有更多-3">还有更多...</h2>
<p>还有另外两个选项窗口。这些是<strong>iPhone 包签名</strong>和<strong>iPhone IPA</strong>选项。它们将在第十四章的配方中详细讨论,第十四章。部署。</p>
<h3 id="链接器使用">链接器使用</h3>
<h3 id="注意-11">注意</h3>
<p>当为模拟器编译时,不建议开启链接器。这是因为编译器不会在<code>iPhoneSimulator</code>平台上编译 MonoTouch 组件;因此它们是直接使用的。开启链接器只会导致编译完成所需的时间更长,对减少最终应用程序包的大小没有影响。</p>
<h2 id="参见-3">参见</h2>
<p>在本书中:</p>
<p>第十四章, 部署:</p>
<ul>
<li><em>为应用商店准备我们的应用程序</em></li>
</ul>
<h1 id="调试我们的应用程序">调试我们的应用程序</h1>
<p>在本食谱中,我们将讨论在模拟器上调试 MonoTouch 应用程序的信息。</p>
<h2 id="准备工作-5">准备工作</h2>
<p>MonoTouch 与 MonoDevelop 结合使用,为在模拟器或设备上调试应用程序提供调试器。在本任务中,我们将了解如何使用调试器调试 MonoTouch 应用程序。打开 MonoDevelop,并加载<code>ButtonInput</code>项目。确保通过在菜单栏上检查<strong>视图 | 工具栏 | 调试器</strong>来激活调试器工具栏。此外,将项目配置设置为<strong>调试 | iPhoneSimulator</strong>。</p>
<h2 id="如何操作-5">如何操作...</h2>
<ol>
<li>
<p>MonoDevelop 支持断点。要激活一行上的断点,请点击行号左侧的空间来设置它。在<code>Main.cs</code>文件的以下行设置断点:</p>
<pre><code class="language-swift">this.labelStatus.Text = "Button tapped!";
</code></pre>
</li>
<li>
<p>这就是 MonoDevelop 中的断点看起来像:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_16.jpg"></p>
</li>
<li>
<p>通过点击从左侧开始的第二个带有双齿轮图标的按钮,或通过点击菜单栏上的<strong>运行 | 调试</strong>来编译和调试项目。MonoDevelop 将显示一个消息框,显示消息,<strong>等待调试器连接</strong>。当模拟器打开且应用程序加载时,请关注<strong>应用程序输出</strong>面板中提供的信息。点击应用程序按钮。执行将暂停,MonoDevelop 将以黄色突出显示断点。将鼠标移至断点行中的<code>labelStatus</code>变量上。然后 MonoDevelop 将显示一个窗口,其中包含所有已评估变量的成员:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_01_17.jpg"></p>
</li>
<li>
<p>要停止调试,请点击工具栏上的<strong>停止</strong>按钮,按钮上有一个红色的圆圈中白色的(X)标记。</p>
</li>
</ol>
<h2 id="它是如何工作的-6">它是如何工作的...</h2>
<p>MonoTouch 与 MonoDevelop 结合使用,使用一个名为<strong>Soft Debugger</strong>的调试器。之所以称为这个名字,是因为它依赖于运行时和 MonoDevelop 的组合,以提供一个统一的调试平台。当调试过程开始时,MonoDevelop 开始监听来自应用程序的调试信息。在模拟器和设备上调试也是如此。当应用程序执行时,它开始将信息发送回 MonoDevelop,然后 MonoDevelop 在<strong>应用程序输出</strong>面板中显示这些信息,该面板会自动激活。调试时的典型应用程序输出是加载的组件信息、开始执行的所有线程,以及如果有,可用的断点。</p>
<h2 id="更多内容-3">更多内容...</h2>
<p><code>Console.WriteLine()</code> 方法也可以用于调试目的。调试器会处理这个问题,并将方法的输出重定向到 MonoDevelop 的 <strong>应用程序输出</strong> 窗格。</p>
<h3 id="调试时的应用程序性能">调试时的应用程序性能</h3>
<p>当为了调试目的进行编译时,编译器会产生更大、更慢的代码。这是因为它生成了额外的代码,这些代码是提供适当调试信息所需的。这就是为什么在调试应用程序时,应用程序的执行速度比简单运行情况慢得多。在生成应用程序的发布副本之前,请记住使用 <strong>Release | iPhone 项目配置</strong> 编译它,以避免运行时执行缓慢。</p>
<h3 id="finishedlaunching-方法中的断点"><code>FinishedLaunching</code> 方法中的断点</h3>
<p>不在 <code>FinishedLaunching</code> 方法中编写复杂代码的另一个原因是,在大多数情况下,你将无法对其进行调试。如果你在 <code>FinishedLaunching</code> 中设置断点,应用程序执行将暂停,但当时间限制达到时,iOS 将终止应用程序。</p>
<h2 id="参见-4">参见</h2>
<p>在本书中:</p>
<p>第十四章, 部署:</p>
<ul>
<li>
<p><em>创建配置文件</em></p>
</li>
<li>
<p><em>在其他设备上进行调试</em></p>
</li>
</ul>
<h1 id="第二章用户界面视图">第二章:用户界面:视图</h1>
<p>在本章中,我们将涵盖以下内容:</p>
<ul>
<li>
<p>添加和自定义视图</p>
</li>
<li>
<p>使用按钮接收用户输入</p>
</li>
<li>
<p>使用标签显示文本</p>
</li>
<li>
<p>显示图像</p>
</li>
<li>
<p>显示和编辑文本</p>
</li>
<li>
<p>使用键盘</p>
</li>
<li>
<p>显示进度</p>
</li>
<li>
<p>显示比屏幕更大的内容</p>
</li>
<li>
<p>在分页的内容中导航</p>
</li>
<li>
<p>显示工具栏</p>
</li>
<li>
<p>创建自定义视图</p>
</li>
</ul>
<h1 id="简介-1">简介</h1>
<p>应用程序的用户界面对于为用户提供与设备(无论是计算机、移动电话还是平板电脑)进行简单通信的方式至关重要。在移动设备上,用户界面不仅至关重要,而且是与用户交互的唯一方式。开发者在为移动设备开发时必须应对各种限制和约束。处理能力不匹配桌面 CPU,屏幕较小,这使得每次选择要显示的信息类型的过程变得更加困难。</p>
<p>在本章中,我们将讨论 iOS 应用程序 UI 的关键组件。我们将了解如何使用和自定义这些组件以创建丰富的应用程序用户界面,并讨论它们与桌面等价物的相似之处和不同之处。以下是这些组件的列表:</p>
<ul>
<li>
<p><code>UIView:</code> 它是一个可定制的容器,是大多数 iOS 用户界面控件的基础对象</p>
</li>
<li>
<p><code>UIButton:</code> 它是.NET 世界中的<code>Button</code>的等价物</p>
</li>
<li>
<p><code>UILabel:</code> 它是.NET 世界中的<code>Label</code>的等价物</p>
</li>
<li>
<p><code>UIImageView:</code> 它是一个允许我们使用图像显示和创建基本动画的视图</p>
</li>
<li>
<p><code>UITextView:</code> 它是一个允许我们显示可编辑文本的视图</p>
</li>
<li>
<p><code>UITextField:</code> 它类似于.NET 的<code>TextBox</code>控件</p>
</li>
<li>
<p><code>UIProgressView:</code> 它显示已知长度的进度</p>
</li>
<li>
<p><code>UIScrollView:</code> 它提供了显示可滚动内容的能力</p>
</li>
<li>
<p><code>UIPageControl:</code> 它为内容提供导航功能,内容分为不同的页面或屏幕</p>
</li>
<li>
<p><code>UIToolbar:</code> 它在屏幕底部提供一个接受可自定义按钮的工具栏</p>
</li>
</ul>
<p>我们还将讨论如何以编程方式创建这些组件的实例并有效地使用它们。</p>
<h1 id="添加和自定义视图">添加和自定义视图</h1>
<p>在这个任务中,我们将讨论如何使用<strong>Interface Builder</strong>添加和自定义<code>UIView</code>。</p>
<h2 id="准备工作-6">准备工作</h2>
<p>使用 Interface Builder 添加视图是一个简单的任务。让我们首先在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>项目。将项目命名为<code>FirstViewApp</code>,并使用 Interface Builder 打开<code>FirstViewAppViewController.xib</code>文件。</p>
<h2 id="如何操作-6">如何操作...</h2>
<ol>
<li>
<p>要向项目中添加视图,请从<strong>库</strong>垫拖动一个<code>UIView</code>对象到主视图上。确保它适合整个窗口区域。</p>
</li>
<li>
<p>要使<code>UIView</code>可访问,为其创建一个出口,并将其命名为<code>subView</code>。</p>
<h3 id="注意-12">注意</h3>
<p>关于出口的概念及其使用方法将在第一章中详细讨论,通过出口访问 UI。</p>
</li>
<li>
<p>选择我们刚刚添加的视图,并转到<strong>检查器</strong>面板。选择<strong>属性</strong>选项卡,并在<strong>背景</strong>下拉列表中选择<strong>浅灰色</strong>。</p>
</li>
<li>
<p>现在,选择<strong>大小</strong>选项卡,并将视图的高度减少<code>60</code>点。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在模拟器上编译并运行应用程序。结果应该看起来像以下图像:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_01.jpg"></p>
<ul>
<li>模拟器屏幕的灰色部分是我们刚刚添加的视图。</li>
</ul>
<h2 id="如何工作-1">如何工作...</h2>
<p>我们已经成功创建了一个包含一个视图的应用程序。当然,这个应用程序不提供任何功能。它只是为了展示如何添加视图并显示它。</p>
<p>视图是 iOS 应用程序界面的基本组件。每个视觉 UI 对象都继承自<code>UIView</code>类。这个概念与.NET 中的<code>Form</code>有所不同。视图管理内容绘制,接受其他视图作为子视图,提供自动调整大小功能,可以接受自身及其子视图的触摸事件,并且许多属性甚至可以动画化。甚至<code>UIWindow</code>也继承自<code>UIView</code>。iOS 开发者将最频繁地使用这个类或其继承者。</p>
<p>当使用 Interface Builder 添加的视图在运行时首次实例化时,它会通过<strong>检查器</strong>窗口的<strong>大小</strong>选项卡设置其<code>Frame</code>属性。<code>Frame</code>属性的类型是<code>RectangleF</code>,它定义了视图在其父视图的坐标系中的位置,在我们的例子中是主窗口,以及其大小以点为单位。</p>
<h3 id="注意-13">注意</h3>
<p>在 Objective-C 中,<code>UIView</code>的<code>Frame</code>属性是<code>CGRect</code>类型。这个类型在 MonoTouch 中没有绑定,而是使用了更熟悉的<code>System.Drawing.RectangleF</code>。</p>
<p>父视图是一个视图的父视图,而子视图是其子视图。具有相同父视图的视图被称为兄弟视图。</p>
<p>iOS 的默认坐标系起源于左上角,并向底部和右侧延伸。坐标系的原点始终相同,并且不能通过编程方式更改。</p>
<p>iOS 的坐标系在以下图像中显示:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_02.jpg"></p>
<p>当设置<code>Frame</code>属性时,它会调整<code>Bounds</code>属性。<code>Bounds</code>属性定义了视图在其自身坐标系中的位置以及其大小以点为单位。它也是<code>RectangleF</code>类型。<code>Bounds</code>属性的默认位置是<code>(0,0)</code>,其大小始终与视图的<code>Frame</code>值相同。这两个属性的尺寸相互关联,因此当您更改<code>Frame</code>的大小,<code>Bounds</code>的大小也会相应地更改,反之亦然。您可以将<code>Bounds</code>属性更改为显示视图的不同部分。视图的<code>Frame</code>可以在位置和位置上超出屏幕。也就是说,具有值(x = -50, y = -50, width = 1500, height = 1500)的视图框架是完全可接受的。</p>
<h2 id="还有更多-4">还有更多...</h2>
<p>另一点需要注意的是,<code>UIView</code> 类继承自 <code>UIResponder</code>。<code>UIResponder</code> 类负责响应用和处理事件。当一个视图被添加到另一个视图中时,它成为其响应链的一部分。<code>UIView</code> 类公开了 <code>UIResponder</code> 的属性和方法,我们现在感兴趣描述的是以下两个:</p>
<ol>
<li>
<p><code>IsFirstResponder</code> <strong>属性</strong>:它返回一个布尔值,指示视图是否是第一个响应者。基本上,它表示视图是否有焦点。</p>
</li>
<li>
<p><code>ResignFirstResponder():</code> 它会导致视图失去焦点。</p>
</li>
</ol>
<h3 id="以编程方式添加视图">以编程方式添加视图</h3>
<p>如果我们想以编程方式添加视图,我们将使用 <code>UIView.AddSubview(UIView)</code> 方法:</p>
<pre><code class="language-swift">this.View.AddSubview(this.subView);
</code></pre>
<p><code>AddSubview()</code> 方法将其参数(类型为 <code>UIView</code>)添加到调用者的子视图列表中,并将其 <code>Superview</code> 参数设置为调用者。除非使用 <code>AddSubview()</code> 方法将其添加到父视图中,否则视图将不会显示。此外,如果视图已经有一个父视图,并且使用其 <code>AddSubview()</code> 方法添加到另一个视图中,则其 <code>Superview</code> 将更改为新调用者的 <code>Superview</code>。这意味着视图每次只能有一个父视图。</p>
<h3 id="注意-14">注意</h3>
<p>当使用 Interface Builder 添加视图作为子视图时,不需要使用 <code>AddSubview()</code> 方法来显示子视图。但是,当以编程方式添加视图时,必须调用 <code>AddSubview()</code> 方法。</p>
<p>以编程方式从父视图中移除视图,请调用其 <code>RemoveFromSuperview()</code> 方法。如果对没有父视图的视图调用此方法,则没有任何作用。当我们想要重用要删除的视图时,必须注意。我们必须保留对其的引用,否则在方法调用后它可能会被释放。</p>
<h3 id="视图内容布局">视图内容布局</h3>
<p><code>UIView</code> 的另一个重要属性是 <code>ContentMode</code>。<code>ContentMode</code> 接受枚举类型 <code>UIViewContentMode</code> 的值。此属性设置 <code>UIView</code> 如何显示其内容。此属性的默认值是 <code>UIViewContentMode.ScaleToFill</code>,它将内容缩放到与视图大小完全匹配,如果需要则进行扭曲。</p>
<h2 id="参见-5">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>创建自定义视图</em></li>
</ul>
<p>在本书中:</p>
<p>第一章,通过输出口访问 UI。</p>
<ul>
<li>
<p><em>创建 UI</em></p>
</li>
<li>
<p><em>通过输出口访问 UI</em></p>
</li>
</ul>
<p>第三章,<em>用户界面:视图控制器:</em></p>
<ul>
<li><em>视图控制器</em> 和 <em>视图</em></li>
</ul>
<h1 id="使用按钮接收用户输入">使用按钮接收用户输入</h1>
<p>在本食谱中,我们将学习如何使用按钮接收和响应用户输入。</p>
<h2 id="准备工作-7">准备工作</h2>
<p>我们在 第一章 中使用了按钮,讨论了如何使用 Interface Builder 向用户界面添加控件。在这个任务中,我们将更详细地描述 <code>UIButton</code> 类。在 MonoDevelop 中打开我们在上一个任务中创建的 <code>FirstViewApp</code> 项目。将主视图的高度增加到在 Interface Builder 中覆盖整个屏幕,并保存文档。</p>
<h2 id="如何做到这一点">如何做到这一点...</h2>
<p>我们将在界面中程序化地添加一个按钮,当它被轻触时,将改变视图的背景颜色。</p>
<ol>
<li>
<p>打开 <code>FirstViewAppViewController.cs</code> 文件,并在类中输入以下代码:</p>
<pre><code class="language-swift">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;
}
}
</code></pre>
</li>
<li>
<p>在 <code>ViewDidLoad()</code> 方法中,添加以下行:</p>
<pre><code class="language-swift">this.CreateButton ();
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。当按钮被轻触时,结果应类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_03.jpg"></p>
<h2 id="它是如何工作的-7">它是如何工作的...</h2>
<p>在这个任务中,我们向用户界面添加了一个按钮,该按钮会改变父视图的背景颜色。此外,我们没有使用任何 Interface Builder 就完成了这个任务。</p>
<p>让我们看看代码是如何工作的。</p>
<ol>
<li>
<p>我们创建一个将保存按钮对象的字段:</p>
<pre><code class="language-swift">// A button to change the view's background color
UIButton buttonChangeColor;
</code></pre>
</li>
<li>
<p>在 <code>CreateButton()</code> 方法中,我们创建按钮并设置一些属性。</p>
<pre><code class="language-swift">RectangleF viewFrame = this.subView.Frame;
RectangleF buttonFrame = new RectangleF (10f, viewFrame.Bottom - 200f, viewFrame.Width - 20f, 50f);
</code></pre>
</li>
<li>
<p>首先,我们将视图的 <code>Frame</code> 分配给一个名为 <code>viewFrame</code> 的新变量。然后,我们创建一个名为 <code>buttonFrame</code> 的新 <code>RectangleF</code> 对象,它将被分配给按钮的 <code>Frame</code> 属性。现在我们为按钮有了框架,我们可以初始化它,如下面的代码片段所示:</p>
<pre><code class="language-swift">//Create the button.
this.buttonChangeColor = UIButton.FromType (UIButtonType.RoundedRect);
this.buttonChangeColor.Frame = buttonFrame;
</code></pre>
</li>
<li>
<p>按钮是通过静态方法 <code>UIButton.FromType(UIButtonType)</code> 初始化的。此方法接受一个 <code>UIButtonType</code> 类型的参数,并返回 iOS SDK 中包含的预定义按钮类型。这里使用的 <code>UIButtonType.RoundedRect</code> 按钮枚举值是 iOS 按钮的默认类型,具有圆角。在 <code>buttonChangeColor</code> 对象初始化后,我们将它的 <code>Frame</code> 设置为我们之前创建的 <code>RectangleF</code> 值。</p>
</li>
<li>
<p>现在我们已经为按钮提供了初始化代码,我们将设置其标题(没错,不止一个):</p>
<pre><code class="language-swift">// Set the button's titles
this.buttonChangeColor.SetTitle ("Tap to change view color", UIControlState.Normal);
this.buttonChangeColor.SetTitle ("Changing color...", UIControlState.Highlighted);
</code></pre>
</li>
<li>
<p>我们调用了 <code>UIButton.SetTitle(string, UIControlState)</code> 方法两次。此方法负责为每个给定的按钮状态设置按钮的标题。字符串参数是实际要显示的标题。第二个参数是表示应用于控件的不同控件状态的 <code>UIControlState</code> 类型的枚举。这些控件状态是:</p>
<ul>
<li>
<p><code>Normal:</code> 启用控件的默认空闲状态</p>
</li>
<li>
<p><code>Highlighted:</code> 控件在触摸事件发生时的状态</p>
</li>
<li>
<p><code>Disabled:</code> 控件被禁用且不接受任何事件</p>
</li>
<li>
<p><code>Selected:</code> 控件已被选中。在大多数情况下,此状态不适用。然而,当需要选择状态时,例如在 <code>UISegmentedControl</code> 对象中,它是有用的。</p>
</li>
<li>
<p><code>Application:</code> 用于应用程序用途的附加控件状态值</p>
</li>
<li>
<p><code>Reserved:</code> 用于内部框架使用</p>
<p>因此,使用 <code>UIButton.SetTitle(string, UIControlState)</code> 方法,我们设置了按钮在其默认状态下显示的标题以及在被点击时显示的标题。</p>
</li>
</ul>
</li>
<li>
<p>之后,我们设置按钮的 <code>TouchUpInside</code> 事件处理程序,并将其添加为 <code>subView:</code> 的子视图</p>
<pre><code class="language-swift">this.buttonChangeColor.TouchUpInside += this.buttonChangeColor_TouchUpInside;
this.subView.AddSubview (this.buttonChangeColor);
</code></pre>
</li>
<li>
<p>在 <code>buttonChangeColor_TouchUpInside</code> 事件中,我们根据我们声明的布尔字段更改视图的背景颜色:</p>
<pre><code class="language-swift">if (this.isRed) {
this.subView.BackgroundColor = UIColor.LightGray;
this.isRed = false;
} else {
this.subView.BackgroundColor = UIColor.Red;
this.isRed = true;
}
</code></pre>
</li>
</ol>
<p>这是通过将视图的 <code>BackgroundColor</code> 属性设置为所需的 <code>UIColor</code> 类实例来完成的,如之前突出显示的代码所示。<code>UIColor</code> 对象是一个具有许多不同静态方法和属性的类,允许我们创建不同的颜色对象。</p>
<p>当你在模拟器上编译并运行应用程序时,请注意当你点击按钮时视图的颜色变化,以及当鼠标光标(或设备上的手指)接触按钮时按钮标题的变化。</p>
<h2 id="更多">更多...</h2>
<p>在这个任务中,我们使用了 <code>UIButton.FromType(UIButtonType)</code> 静态方法来初始化按钮。以下是 <code>UIButtonType</code> 枚举标志的简要描述:</p>
<ul>
<li>
<p><code>Custom:</code> 它是一个无边框的透明按钮。在创建具有图像背景的自定义按钮时使用此标志。按钮的标题不是透明的。</p>
</li>
<li>
<p><code>RoundedRect:</code> 它是具有圆角按钮的默认类型。</p>
</li>
<li>
<p><code>DetailDisclosure:</code> 它是一个圆形的蓝色按钮,用于显示与项目相关的附加信息。</p>
</li>
<li>
<p><code>InfoLight:</code> 它是一个带有字母(i)的浅色按钮,代表信息显示。</p>
</li>
<li>
<p><code>InfoDark:</code> 它与 <code>InfoLight</code> 相同,以深色显示。</p>
</li>
<li>
<p><code>ContactAdd:</code> 它是一个带有白色加号(+)的圆形蓝色按钮。通常用于显示要添加到项目中的联系信息。</p>
</li>
</ul>
<h3 id="创建自定义按钮">创建自定义按钮</h3>
<p>要创建具有类型 <code>UIButtonType.Custom</code> 的自定义按钮,请使用 <code>UIButton</code> 类的 <code>SetBackgroundImage()</code> 和 <code>SetImage()</code> 方法。它们都接受一个 <code>UIImage</code> 和一个 <code>UIControlState</code> 参数,因此可以为不同的控件状态设置不同的图像。在设置按钮的图像时,无论是创建自定义按钮还是不是,务必相应地设置 <code>UIButton.ContentMode</code> 属性。</p>
<p><code>SetImage</code> 和 <code>SetBackgroundImage</code> 方法提供的功能也可以在 Interface Builder 中 <strong>Inspector</strong> 面板的 <strong>Attributes</strong> 选项卡的 <strong>Image</strong> 和 <strong>Background</strong> 字段中完成。从下拉列表框中选择要设置所需图像的状态,并设置图像文件的路径,如下面的屏幕截图所示:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_04.jpg"></p>
<h2 id="参见-6">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>添加和自定义视图</em></p>
</li>
<li>
<p><em>显示图像</em></p>
</li>
<li>
<p><em>创建自定义视图</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第一章, <em>开发工具:</em></p>
<ul>
<li>
<p><em>创建 UI</em>,</p>
</li>
<li>
<p><em>通过 Outlets 访问 UI</em></p>
</li>
</ul>
<h1 id="使用标签显示文本">使用标签显示文本</h1>
<p>在这个菜谱中,我们将学习如何使用标签向用户显示信息文本。</p>
<h2 id="准备工作-8">准备工作</h2>
<p>在这个任务中,我们将更详细地描述<code>UILabel</code>类。再次强调,所有的工作都将在没有 Interface Builder 的帮助下完成。打开我们在上一个菜谱中修改的<code>FirstViewApp</code>项目。</p>
<h2 id="如何做到这一点-1">如何做到这一点...</h2>
<p>我们将程序化地创建一个标签,该标签将显示一些静态的指导文本。</p>
<ol>
<li>
<p>打开文件<code>FirstViewAppViewcontroller.cs</code>,并在类中输入以下代码:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>然后在<code>FinishedLaunching()</code>方法中添加以下行:</p>
<pre><code class="language-swift">this.CreateLabel();
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。输出应该看起来像以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_05.jpg"></p>
<h2 id="它是如何工作的-8">它是如何工作的...</h2>
<p>我们已经成功创建了一个标签,并在其中放入了一些信息文本。让我们逐步查看代码,看看实际上发生了什么。</p>
<ol>
<li>
<p>我们首先在<code>FirstViewAppViewController</code>类中创建一个<code>labelInfo</code>字段,用于存储我们的<code>UILabel</code>对象。</p>
<pre><code class="language-swift">// A label that displays some text
UILabel labelInfo;
</code></pre>
</li>
<li>
<p>然后我们创建了一个名为<code>CreateLabel()</code>的方法,该方法将实例化和定制标签。就像我们在上一个任务中创建的按钮一样,我们的标签需要一个框架。因此,我们再次创建一个,这取决于视图的<code>Frame</code>属性:</p>
<pre><code class="language-swift">//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);
</code></pre>
</li>
<li>
<p>我们只需将其高度设置为高于按钮的高度,即<code>100</code>点。现在我们已经为标签有了<code>Frame</code>,我们初始化它:</p>
<pre><code class="language-swift">//Create the label
this.labelInfo = new UILabel (labelFrame);
this.labelInfo.Lines = 3;
this.labelInfo.TextAlignment = UITextAlignment.Center;
this.labelInfo.BackgroundColor = UIColor.Clear;
</code></pre>
</li>
<li>
<p>构造函数被使用,这样框架属性将在初始化时立即设置。行属性决定了标签文本上的文本将被分成多少行。文本对齐属性接受枚举类型<code>UITextAlignment</code>的值,它包含常用的文本对齐标志:居中、左对齐和右对齐。为了使标签的框架完全不可见,以便只有我们的文本可见,我们将<code>BackgroundColor</code>属性设置为<code>UIColor.Clear</code>颜色。</p>
</li>
<li>
<p>下一个部分非常有趣。除了能够设置<code>label</code>的字体颜色外,我们还可以为显示的文本设置阴影:</p>
<pre><code class="language-swift">// Set text color and shadow
this.labelInfo.TextColor = UIColor.White;
this.labelInfo.ShadowColor = UIColor.DarkGray;
this.labelInfo.ShadowOffset = new SizeF (1f, 1f);
</code></pre>
</li>
<li>
<p><code>TextColor</code>属性接受<code>UIColor</code>值。要为标签的文本设置阴影,将<code>UIColor</code>设置到<code>ShadowColor</code>属性。然后,将<code>SizeF</code>结构设置到<code>ShadowOffset</code>属性。此属性决定了阴影的确切位置。<code>SizeF</code>的宽度参数定义了阴影的水平位置,而高度参数定义了垂直位置。负值是可以接受的。宽度参数的负值意味着阴影将定位在文本的左侧,而高度参数的负值意味着阴影将定位在文本上方。我们在前面的代码中设置的值意味着阴影将显示在文本右侧 1 点,下方 1 点。</p>
</li>
<li>
<p>我们已经准备好了<code>label</code>如何渲染其文本。要分配<code>label</code>将显示的文本,设置其<code>Text</code>属性:</p>
<pre><code class="language-swift">// 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!";
</code></pre>
</li>
<li>
<p>如您所见,我们为 <code>label</code> 定义了一个相当长的字符串以供显示。最后,将 <code>label</code> 添加到要显示的视图中:</p>
<pre><code class="language-swift">this.mainView.AddSubview (this.labelInfo);
</code></pre>
</li>
</ol>
<p>此代码给出了本任务 <em>如何做</em> 部分所示的结果。</p>
<h2 id="还有更多-5">还有更多...</h2>
<p>在前面的代码中,还有一行注释掉的代码:</p>
<pre><code class="language-swift">//this.labelInfo.AdjustsFontSizeToFitWidth = true;
</code></pre>
<p><code>AdjustsFontSizeToFitWidth</code> 属性接受一个布尔值。如果设置为 <code>true</code>,则指示 <code>label</code> 自动更改字体大小,以便它能够适应 <code>label</code> 的宽度。如果 <code>label</code> 支持多行,则将此属性设置为 <code>true</code> 完全没有效果。所以,为了看看这个属性是如何工作的,取消注释它并将 <code>Lines</code> 属性设置为 <code>1</code>。结果将类似于以下内容:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_06.jpg"></p>
<p>如您所见,<code>label</code> 上的文本需要放大镜才能阅读,所以在这里它不能正常工作。然而,当屏幕上 <code>label</code> 的宽度有限且我们希望文本适应该空间时,<code>AdjustsFontSizeToFitWidth</code> 属性非常有用。为了防止这种情况,将 <code>MinimumFontSize</code> 属性设置为所需的值。正如其名称所暗示的,字体的大小不会小于此属性的值。</p>
<h3 id="uilabel-字体"><code>UILabel</code> 字体</h3>
<p>在 <code>label</code> 中设置显示文本的字体很简单。使用 <code>UIFont.FromName(string, float)</code> 静态方法设置其 <code>Font</code> 属性。<code>string</code> 参数表示要设置的字体名称,可以包括字体家族和样式,而 <code>float</code> 参数确定其大小。例如,要将 <code>label</code> 的字体设置为 <code>Helvetica Bold</code>,请调用以下方法:</p>
<pre><code class="language-swift">this.labelInfo.Font = UIFont.FromName("Helvetica-Bold", 17f);
</code></pre>
<p>如果找不到字体名称,<code>FromName</code> 静态方法将返回 <code>null</code>。对此必须小心,因为当 <code>UILabel.Font</code> 属性被设置为 <code>null</code> 时,将会抛出异常。可以通过调用 <code>UIFont.FontNamesForFamilyName(string)</code> 方法来确定字体家族的可用样式,该方法返回一个包含所有可用字体的 <code>string[]</code> 数组。如果将 <code>Helvetica</code> 字体家族传递给此方法,它将返回一个包含以下项的 <code>string[]</code> 数组:</p>
<ul>
<li>
<p>Helvetica-BoldOblique</p>
<p>Helvetica</p>
<p>Helvetica-Bold</p>
<p>Helvetica-Oblique</p>
</li>
</ul>
<h2 id="参见-7">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>显示和编辑文本</em></li>
</ul>
<p>在本书中:</p>
<p>第一章, 使用连接访问 UI:</p>
<ul>
<li><em>创建 UI</em></li>
</ul>
<p><em>第十一章,图形和动画:</em></p>
<ul>
<li><em>显示闪烁的文本</em></li>
</ul>
<h1 id="显示图片">显示图片</h1>
<p>在本食谱中,我们将学习如何使用 <code>UIImageView</code> 类在屏幕上显示图片。</p>
<h2 id="准备工作-9">准备工作</h2>
<p>在这个任务中,我们将了解如何在项目中打包和显示图片。显示需要使用一个图片文件。这里使用的图片文件名为 <code>Toroni.jpg</code>。在 MonoDevelop 中创建一个新的 <strong>iPhone 单视图应用程序</strong> 项目,并将其命名为 <code>ImageViewerApp</code>。</p>
<h2 id="如何做-2">如何做...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开 <code>ImageViewerAppViewController.xib</code> 文件。</p>
</li>
<li>
<p>在其视图中添加一个<code>UIImageView</code>对象。将<code>UIImageView</code>对象与名为<code>imageDisplay</code>的出口连接。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>返回到 MonoDevelop,在<code>ImageViewerAppViewController</code>类中,输入以下代码:</p>
<pre><code class="language-swift">public override ViewDidLoad(){
base.ViewDidLoad();
this.imageDisplay.ContentMode = UIViewContentMode.ScaleAspectFit;
this.imageDisplay.Image = UIImage.FromFile("Toroni.jpg");
}
</code></pre>
</li>
<li>
<p>在<strong>解决方案</strong>面板中右键单击项目,然后选择<strong>添加 | 添加文件</strong>。选择要显示的图像文件,然后单击<strong>打开</strong>。</p>
</li>
<li>
<p>右键单击您刚刚添加的图像文件,然后单击<strong>构建操作 | 内容</strong>。</p>
</li>
<li>
<p>最后,在模拟器上编译并运行应用程序。您添加到项目中的图像应显示在屏幕上,如下面的图像所示:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_07.jpg"></p>
<h2 id="如何工作-2">如何工作...</h2>
<p><code>UIImageView</code>类基本上是一个用于显示图像的自定义视图。在项目中添加图像时,其<strong>构建操作</strong>必须在<strong>解决方案</strong>面板中设置为<strong>内容</strong>,否则图像将不会复制到应用程序包中。</p>
<p>当显示图像时,<code>ContentMode</code>属性非常重要。它设置<code>UIView</code>(在这种情况下为<code>UIImageView</code>)对象显示图像的方式。我们将其设置为<code>UIViewContentMode.ScaleAspectFit</code>,这样它将被调整大小以适应<code>UIImageView</code>的区域,同时保持宽高比不变。如果<code>ContentMode</code>字段保留为默认的<code>Scale To Fill</code>值,输出将类似于以下图像:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_02_08.jpg"></p>
<p>要设置<code>UIImageView</code>应显示的图像,我们使用<code>UIImage</code>对象设置其<code>Image</code>属性:</p>
<pre><code class="language-swift">this.imageDisplay.Image = UIImage.FromFile("Toroni.jpg");
</code></pre>
<p><code>ContentMode</code>属性接受一个名为<code>UIViewContentMode</code>的枚举类型。提供的值如下:</p>
<ul>
<li>
<p><strong>填充以适应</strong>:这是基本<strong>UIView</strong>对象的默认值。它将内容缩放到适合视图的大小,必要时更改宽高比。</p>
</li>
<li>
<p><strong>缩放以适应视图</strong>:将内容缩放到适合视图的大小,同时保持其宽高比。视图内容剩余区域变为透明。</p>
</li>
<li>
<p><strong>填充以适应视图</strong>:将内容缩放到填充视图的大小,同时保持其宽高比。内容的一些部分可能被省略。</p>
</li>
<li>
<p><strong>重绘</strong>:当视图的边界发生变化时,其内容不会被重绘。此值会导致内容被重绘。在 CPU 周期方面,绘制内容是一项昂贵的操作,因此在使用此值之前请三思,特别是对于大量内容。</p>
</li>
<li>
<p><strong>中心</strong>:将内容放置在视图的中心,保持其宽高比。</p>
</li>
<li>
<p><strong>顶部、底部、左侧、右侧、左上角、右上角、左下角</strong>和<strong>右下角</strong>:根据相应的值在视图中对齐内容。</p>
</li>
</ul>
<h2 id="还有更多-6">还有更多...</h2>
<p><code>UIImage</code>类是表示图像信息的对象。它支持的文件格式列在以下表中:</p>
<table>
<thead>
<tr>
<th>文件格式</th>
<th>文件扩展名</th>
</tr>
</thead>
<tbody>
<tr>
<td>可移植网络图形 (PNG)</td>
<td>.png</td>
</tr>
<tr>
<td>联合图像专家小组 (JPEG)</td>
<td>.jpg, .jpeg</td>
</tr>
<tr>
<td>标记图像文件格式 (TIFF)</td>
<td>.tiff, .tif</td>
</tr>
<tr>
<td>图像交换格式</td>
<td>.gif</td>
</tr>
<tr>
<td>Windows 位图格式</td>
<td>.bmp</td>
</tr>
<tr>
<td>Windows 图标格式</td>
<td>.ico</td>
</tr>
<tr>
<td>Windows 光标</td>
<td>.cur</td>
</tr>
<tr>
<td>XWindow 位图</td>
<td>.xbm</td>
</tr>
</tbody>
</table>
<h3 id="注意-15">注意</h3>
<p><code>UIImageView</code> 类不支持动画 GIF 图像文件。当动画 GIF 设置为 <code>UIImageView</code> 的 <code>Image</code> 属性时,只会显示其第一帧。</p>
<h3 id="使用不同屏幕尺寸的图像">使用不同屏幕尺寸的图像</h3>
<p>为背景创建图像为开发者提供了制作丰富和优雅的用户界面的能力。创建视图背景的首选图像文件格式是 <code>PNG</code>。但是,自从 iPhone 4 发布以来,屏幕分辨率提高了。为了在应用程序中支持两种屏幕分辨率,iOS SDK 提供了一个简单的解决方案。只需将图像保存为高分辨率,并在扩展名之前添加一个 <code>@2x</code> 后缀到文件名,例如,名为 <code>Default.png</code> 的文件的高分辨率版本将被命名为 <code>Default@2x.png</code>。另外,使用这两个文件不需要额外的代码。</p>
<p>只需使用 <code>UIImage.FromBundle(string)</code> 静态方法,传递不带扩展名的文件名,iOS 会根据应用程序运行的设备加载适当的文件。</p>
<pre><code class="language-swift">this.imageDisplay = UIImage.FromBundle("Default");
</code></pre>
<h3 id="注意-16">注意</h3>
<p>上述内容仅适用于 PNG 图像文件。</p>
<h2 id="参见-8">参见</h2>
<p>在本章:</p>
<ul>
<li><em>添加和自定义视图</em></li>
</ul>
<p>在本书中:</p>
<p>第七章,多媒体资源:</p>
<ul>
<li><em>加载图像</em></li>
</ul>
<h1 id="显示和编辑文本">显示和编辑文本</h1>
<p>在这个菜谱中,我们将学习如何显示具有编辑功能的简单文本块。</p>
<h2 id="准备工作-10">准备工作</h2>
<p>在这个任务中,我们将讨论 <code>UITextView</code> 的用法以及如何使用它显示可编辑文本。在 MonoDevelop 中创建一个新的 <strong>iPhone 单视图应用程序</strong> 项目,并将其命名为 <code>TextViewApp</code>。</p>
<h2 id="如何操作-7">如何操作...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开 <code>TextViewAppViewController.xib</code>。</p>
</li>
<li>
<p>在其视图的顶部附近添加一个 UIButton,在其下方添加一个 UITextView。将这两个对象连接到它们的出口。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在 MonoDevelop 中,在 <code>TextViewAppViewController</code> 类中输入以下 <code>ViewDidLoad</code> 方法:</p>
<pre><code class="language-swift">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
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>在文本视图中轻触某处,键盘就会出现。输入一些文本,然后轻触 <strong>完成</strong> 按钮以隐藏键盘。</p>
</li>
</ol>
<h2 id="它是如何工作的-9">它是如何工作的...</h2>
<p><code>UITextView</code> 类提供了一个显示可编辑文本块的对象。为了响应用户文本视图中的事件,我们实现了一个继承自 <code>UITextViewDelegate</code> 的类,该类将充当文本视图的代理:</p>
<pre><code class="language-swift">private class MyTextViewDelegate : UITextViewDelegate{
public MyTextViewDelegate (TextViewAppViewController parentController){
this.parentController = parentController;
}
private TextViewAppViewController parentController;
</code></pre>
<p>我们声明了一个接受 <code>TextViewAppViewController</code> 对象的构造函数,这样我们就可以有一个控制器实例来访问我们的控件。</p>
<p>然后,我们重写了 <code>UITextViewDelegate</code> 类的三个方法:</p>
<pre><code class="language-swift">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!");
}
</code></pre>
<p>这些方法是在触发相应事件时将被调用的处理程序。当点击文本时,<code>EditingStarted()</code> 方法被调用。我们在其中启用 <strong>完成</strong> 按钮。当我们向文本视图中输入一些文本时,<code>Changed()</code> 方法被调用,我们可以在 MonoDevelop 的 <strong>应用程序输出</strong> 面板中看到 <code>Console.WriteLine()</code> 方法的输出。最后,当我们点击 <strong>完成</strong> 按钮时,键盘隐藏,并调用允许我们禁用按钮的 <code>EditingEnded()</code> 方法。</p>
<p>在 <code>ViewDidLoad</code> 方法中,我们为按钮的 <code>TouchUpInside</code> 事件分配一个处理程序:</p>
<pre><code class="language-swift">this.buttonFinished.TouchUpInside += delegate {
this.myTextView.ResignFirstResponder ();
};
</code></pre>
<p>我们在它里面调用文本视图的 <code>ResignFirstResponder()</code> 方法,这样当按钮被点击时,文本视图将失去焦点,导致键盘隐藏。然后,我们将我们创建的委托的新实例分配给文本视图的 <code>Delegate</code> 属性,传递 <code>TextViewAppViewController</code> 对象的实例:</p>
<pre><code class="language-swift">this.myTextView.Delegate = new MyTextViewDelegate (this);
</code></pre>
<h2 id="还有更多-7">还有更多...</h2>
<p>Objective-C 中的委托与 C# 中的委托有所不同。尽管在两个世界中它们最常用的用途都是提供对某种形式的事件通知机制的访问,但在 Objective-C 中,这种机制要复杂一些。C# 委托类似于 C 或 C++ 编程语言中的函数指针。它是一个包含对特定签名方法引用的对象。另一方面,Objective-C 委托是一种符合特定协议的对象。它基本上是一个封装了一个或多个方法(以及/或其它成员)作为事件处理器的对象。</p>
<h3 id="注意-17">注意</h3>
<p>Objective-C 协议类似于 C# 中的接口。</p>
<p>代理对象的概念一开始可能看起来有些复杂,但并不难理解。关于事件通知机制,MonoTouch 通过为大多数对象提供事件来简化了 .NET 开发者的工作。</p>
<h3 id="注意-18">注意</h3>
<p><code>UITextView</code> 类适合显示不带格式的简单文本块。对于显示格式化文本,请使用 <code>UIWebView</code> 类。</p>
<h2 id="相关内容-2">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>使用键盘</em></li>
</ul>
<p>在本书中:</p>
<p>第五章, 显示数据:</p>
<p><em>显示和格式化文本</em></p>
<h1 id="使用键盘">使用键盘</h1>
<p>在这个配方中,我们将讨论虚拟键盘使用的一些重要方面。</p>
<h2 id="准备工作-11">准备工作</h2>
<p>在前两个任务中,我们讨论了可用的文本输入类型。在这个任务中,我们将讨论我们可以做的一些事情,甚至必须做的事情,以有效地使用键盘。在 MonoDevelop 中创建一个新的 <strong>iPhone 单视图应用程序</strong> 项目,并将其命名为 <code>KeyboardApp</code>。</p>
<h2 id="如何做-3">如何做...</h2>
<p>按照以下步骤创建项目:</p>
<ol>
<li>
<p>在 Interface Builder 中打开 <code>KeyboardAppViewController.xib</code> 文件。</p>
</li>
<li>
<p>在视图的下半部分添加一个 <code>UITextField</code> 对象,并将其连接到一个出口。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>回到 MonoDevelop,在 <code>KeyboardAppViewController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">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 ();
} ;
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击文本字段,并观察它向上移动以避免被键盘遮挡。</p>
</li>
<li>
<p>点击键盘上的<strong>完成</strong>按钮,并观察当键盘隐藏时文本字段返回到其原始位置。</p>
</li>
</ol>
<h2 id="它是如何工作的-10">它是如何工作的...</h2>
<p>iOS 中有各种类型的键盘。由于屏幕尺寸有限,不能一次性显示所有键,因此根据我们需要用户提供的文本输入设置适当的键盘类型是一种良好的做法。在这个项目中,我们将键盘设置为<strong>电子邮件地址</strong>类型。我们还通过将其设置为<strong>完成</strong>来定制了<strong>返回</strong>键的类型。</p>
<pre><code class="language-swift">this.emailField.KeyboardType = UIKeyboardType.EmailAddress;
this.emailField.ReturnKeyType = UIReturnKeyType.Done;
</code></pre>
<p>当键盘显示时,开发者的责任是确保它不会遮挡必要的 UI 元素。在这种情况下,因为我们为用户提供了一些文本输入的能力,我们必须确保文本字段是可见的,以便用户可以看到正在输入的内容。为此,我们在默认的通知中心添加了两个观察者。</p>
<pre><code class="language-swift">// Add observers for the keyboard
this.kbdWillShow = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillShowNotification, delegate(NSNotification ntf) {
</code></pre>
<p><code>NSNotificationCenter</code>为各种通知提供了一个通知机制。</p>
<p>我们通过<code>NSNotificationCenter.DefaultCenter</code>静态属性访问运行时的默认通知中心。使用<code>AddObserver()</code>方法添加了一个观察者,它接受两个参数。第一个参数是一个<code>NSString</code>值,它通知通知中心要观察哪种类型的通知。<code>UIKeyboard</code>类包含预定义的静态属性,具有我们需要的键盘通知类型。<code>UIKeyboard.WillShowNotification</code>被传递,表示观察者将在键盘准备好出现时进行观察并通知。第二个参数是<code>Action<NSNotification></code>类型,表示当通知发生时将被执行的处理程序。在<code>anonymous()</code>方法内部,我们调用<code>UIKeyboard.FrameEndNotification(NSNotification)</code>方法,该方法返回键盘的边界:</p>
<pre><code class="language-swift">//Get the keyboard's bounds
RectangleF kbdBounds = UIKeyboard.FrameEndNotification (ntf);
</code></pre>
<p>然后,我们将文本字段的框架存储在一个变量中,并减少其<code>Y</code>位置值,以便文本字段向上移动:</p>
<pre><code class="language-swift">// Get the text field's frame
RectangleF textFrame = this.emailField.Frame;
// Change the y position of the text field frame
textFrame.Y -= kbdBounds.Height;
</code></pre>
<p>当新的框架设置为<code>emailField</code>时,它将移动到新的位置:</p>
<pre><code class="language-swift">this.emailField.Frame = textFrame;
</code></pre>
<p>第二个观察者是为了将文本字段移回其原始位置。它与第一个观察者几乎相同,除了两个区别。传递了<code>UIKeyboard.DidHideNotification</code>字符串,指示观察者在键盘关闭后触发处理程序,并将文本字段框架的<code>Y</code>值增加,使文本字段返回到其原始位置。</p>
<h2 id="还有更多-8">还有更多...</h2>
<p>类中<code>NSObject</code>类型的两个字段包含有关我们添加的观察者的信息。为了移除我们在此处添加的两个观察者,请添加以下代码:</p>
<pre><code class="language-swift">NSNotificationCenter.DefaultCenter.RemoveObserver (this.kbdWillShow);
NSNotificationCenter.DefaultCenter.RemoveObserver (this.kbdDidHide);
</code></pre>
<h3 id="注意-19">注意</h3>
<p>在开发使用键盘并支持多种界面方向的应用程序时,必须小心。例如,如果键盘以纵向模式出现,而用户切换到横向模式,则键盘的边界和文本字段的框架都将不同,必须相应地进行调整。</p>
<h2 id="参见-9">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>显示和编辑文本</em></li>
</ul>
<p>在本书中:</p>
<p>第九章,与设备硬件交互:</p>
<ul>
<li>
<p><em>旋转设备</em></p>
</li>
<li>
<p><em>调整用户界面</em></p>
</li>
</ul>
<h1 id="显示进度">显示进度</h1>
<p>在本食谱中,我们将讨论如何显示已知长度的进度。</p>
<h2 id="准备中-1">准备中</h2>
<p>在这个任务中,我们将讨论 <code>UIProgressView</code> 控件。此控件提供的功能类似于 .NET 中的 <strong>ProgressBar</strong>。在 MonoDevelop 中创建一个 <strong>iPhone 单视图应用程序</strong> 项目,并将其命名为 <code>ProgressApp</code>。</p>
<h2 id="如何做-4">如何做...</h2>
<p>使用 <code>UIProgressView</code> 类的步骤如下。请注意,在本食谱中,我们将以编程方式添加所有控件,而不使用 Interface Builder:</p>
<ol>
<li>
<p>在 <code>ProgressAppViewController</code> 类文件中添加以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using System.Drawing;
using System.Threading;
</code></pre>
</li>
<li>
<p>添加以下字段:</p>
<pre><code class="language-swift">UILabel labelStatus;
UIButton buttonStartProgress;
UIProgressView progressView;
float incrementBy = 0f;
</code></pre>
</li>
<li>
<p>在 <code>ViewDidLoad</code> 覆盖中输入以下代码:</p>
<pre><code class="language-swift">// 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);
</code></pre>
</li>
<li>
<p>在类中添加以下方法:</p>
<pre><code class="language-swift">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;
}
} );
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮并观察进度条填充。</p>
</li>
</ol>
<h2 id="它是如何工作的-11">它是如何工作的...</h2>
<p><code>UIProgressView</code> 的当前值由其 <code>Progress</code> 属性表示。其可接受值范围始终是从 <code>0</code> 到 <code>1</code>。因此,在初始化时,我们将它设置为 <code>0</code> 以确保条形不会填充任何内容:</p>
<pre><code class="language-swift">this.progressView.Progress = 0f;
</code></pre>
<p>由于 <code>UIProgressView</code> 有一个特定的范围,我们需要根据我们需要处理的项的数量分配我们想要增加的值,在这种情况下是 <code>10:</code>。</p>
<pre><code class="language-swift">this.incrementBy = 1f / 10f;
</code></pre>
<p>在按钮的 <code>TouchUpInside</code> 处理程序中,我们禁用按钮并通过异步调用一个方法来开始我们的进度:</p>
<pre><code class="language-swift">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);
};
</code></pre>
<p>在 <code>StartProgress()</code> 方法中,我们启动一个循环,该循环将处理需要完成的工作。由于工作在异步方法中执行,当我们想要更改控件时,必须在主 UI 线程上通过调用 <code>UIApplicationDelegate</code> 的 <code>BeginInvokeOnMainThread()</code> 方法来完成,该方法接受 <code>NSAction</code> 类型的参数。<code>NSAction</code> 可以接受匿名方法:</p>
<pre><code class="language-swift">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;
}
});
</code></pre>
<h2 id="更多-1">更多...</h2>
<p><code>UIProgressView</code> 支持除了默认样式之外的一种样式。将它的 <code>Style</code> 属性设置为 <code>UIProgressViewStyle.Bar</code>,这样条形就会像在接收新电子邮件时 Mail 应用程序中的条形一样显示。</p>
<h2 id="参见-10">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>使用按钮接收用户输入</em></li>
</ul>
<h1 id="显示屏幕外的内容">显示屏幕外的内容</h1>
<p>在本食谱中,我们将学习如何显示超出屏幕边界的内 容。</p>
<h2 id="准备中-2">准备中</h2>
<p>在这个任务中,我们将讨论 <code>UIScrollView</code> 控件。创建一个 <strong>iPhone 单视图应用程序</strong> 项目,并将其命名为 <code>ScrollApp</code>。</p>
<h2 id="如何做-5">如何做...</h2>
<p>创建项目的步骤如下:</p>
<ol>
<li>
<p>在 Interface Builder 中打开 <code>ScrollAppViewController.xib</code> 文件。</p>
</li>
<li>
<p>在其视图中添加一个 <code>UIScrollView</code> 对象,并将其连接到一个出口。然后保存文档。</p>
</li>
<li>
<p>回到 MonoDevelop,在 <code>ScrollAppViewController</code> 类中添加以下代码:</p>
<pre><code class="language-swift">// 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);
}
</code></pre>
</li>
<li>
<p>最后,将图像添加到项目中,并将其 <strong>Build Action</strong> 设置为 <strong>Content</strong>。一个比模拟器屏幕更大的图像,大小为 <strong>320x480</strong> 像素,是首选的。</p>
</li>
<li>
<p>在模拟器上编译并运行应用程序。在图像上点击并拖动以显示不同的部分。通过按下 <em>Alt</em> <em>+</em> 鼠标点击,可以模拟捏合缩放功能。</p>
</li>
</ol>
<h2 id="它是如何工作的-12">它是如何工作的...</h2>
<p><code>UIScrollView</code> 能够管理超出屏幕大小的内容。滚动视图将显示的内容大小必须在它的 <code>ContentSize</code> 属性中设置:</p>
<pre><code class="language-swift">this.scrollContent.ContentSize = this.imgView.Image.Size;
</code></pre>
<p><code>ContentOffset</code> 属性定义了内容在滚动视图边界内的位置:</p>
<pre><code class="language-swift">this.scrollContent.ContentOffset = new PointF (200f, 50f);
</code></pre>
<p>这意味着图像的 (x=200, y=50) 点将在 <code>UIScrollView</code> 的原点 (x=0, y=0) 处显示。为了提供内容的缩放功能,我们首先设置 <code>MinimumZoomScale</code> 和 <code>MaximumZoomScale</code> 属性,这些属性设置了内容的缩放比例的最小值和最大值。值为 <code>2</code> 表示内容将是原来大小的两倍,而值为 <code>0.5</code> 表示内容将是原来大小的一半。</p>
<pre><code class="language-swift">this.scrollContent.MinimumZoomScale = 0.5f;
this.scrollContent.MaximumZoomScale = 2f;
</code></pre>
<p>对于实际的缩放操作,我们需要设置 <code>ViewForZoomingInScrollView</code> 属性,它接受一个 <code>UIScrollViewGetZoomView</code> 类型的代理并返回一个 <code>UIView</code>。在这里,我们返回创建的图像视图,但也可以使用另一个更高分辨率的图像视图来提供在缩放时更好的图像质量。在分配代理后,设置初始缩放比例:</p>
<pre><code class="language-swift">this.scrollContent.ViewForZoomingInScrollView = delegate(UIScrollView scroll) {
return this.imgView;
};this.scrollContent.ZoomScale = 1f;
</code></pre>
<p>最后,设置滚动视图的指示器样式:</p>
<pre><code class="language-swift">this.scrollContent.IndicatorStyle = UIScrollViewIndicatorStyle.White;
</code></pre>
<p>指示器是当滚动或缩放时出现的两条线:一条位于滚动视图的垂直右侧,另一条位于水平底部,它们告知用户内容的位置。与滚动条类似。</p>
<h2 id="还有更多-9">还有更多...</h2>
<p>为了给用户提供更令人愉悦的滚动和缩放效果,<code>UIScrollView</code> 暴露了 <code>Bounce</code> 属性。默认情况下,它被设置为 <code>true</code>,但我们可以选择将其设置为 <code>false</code> 来禁用它。当内容在水平或垂直方向上达到边界时,弹跳内容会立即给用户反馈,告知他们已经达到了边界。此外,可以单独设置 <code>AlwaysBounceHorizontal</code> 和 <code>AlwaysBounceVertical</code> 属性。设置其中一个或两个这些属性将使滚动视图在相应的方向上始终弹跳内容,即使内容的大小等于或小于滚动视图的边界。因此,实际上并不需要滚动。</p>
<h3 id="uiscrollview-事件"><code>UIScrollView</code> 事件</h3>
<p><code>UIScrollView</code> 类公开了一些非常有用的事件:</p>
<ul>
<li>
<p><code>Scrolled:</code> 当内容正在滚动时发生此事件</p>
</li>
<li>
<p><code>DecelerationStarted:</code> 当用户开始滚动内容时发生此事件</p>
</li>
<li>
<p><code>DecelerationEnded:</code> 当用户完成滚动并且内容停止移动时发生此事件</p>
</li>
</ul>
<h3 id="注意-20">注意</h3>
<p>如果已将处理程序分配给<code>Scrolled</code>事件,并且设置了<code>ContentOffset</code>属性,则将触发事件。</p>
<h2 id="参见-11">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示图像</em></p>
</li>
<li>
<p><em>显示和编辑文本</em></p>
</li>
<li>
<p><em>在分页的内容中导航</em></p>
</li>
</ul>
<h1 id="在分页的内容中导航">在分页的内容中导航</h1>
<p>在这个菜谱中,我们将学习如何使用<code>UIPageControl</code>类来实现页面导航。</p>
<h2 id="准备工作-12">准备工作</h2>
<p><code>UIPageControl</code>为 iOS 应用程序中的多个页面或屏幕提供了一个简单的视觉表示,以点表示。与当前页面相对应的点会被突出显示。它通常与<code>UIScrollView</code>结合使用。在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>项目,并将其命名为<code>PageNavApp</code>。在项目中添加三个图像文件,并将它们的<strong>构建操作</strong>设置为<strong>内容</strong>。</p>
<h2 id="如何做到">如何做到...</h2>
<p>创建此项目的步骤如下:</p>
<ol>
<li>
<p>在 Interface Builder 中打开<code>PageNavAppViewController.xib</code>文件。</p>
</li>
<li>
<p>在视图底部添加一个<code>UIPageControl</code>,在其上方添加<code>UIScrollView</code>。调整滚动视图的大小以占用视图剩余的所有空间,并保存文档。</p>
</li>
<li>
<p>回到 MonoDevelop 中,在<code>PageNavAppViewController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在类中添加以下方法:</p>
<pre><code class="language-swift">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;
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。在滚动视图中滚动以更改页面。同样,在页面控制上轻触或滚动也可以更改页面。</p>
</li>
</ol>
<h2 id="它是如何工作的-13">它是如何工作的...</h2>
<p>我们需要做的第一件事是将<code>UIScrollView.PagingEnabled</code>属性设置为<code>true</code>,如下所示:</p>
<pre><code class="language-swift">this.scrollView.PagingEnabled = true;
</code></pre>
<p>此属性指示滚动视图以滚动视图边界的倍数进行滚动,从而提供分页功能。之后,准备将在不同页面中显示的图像视图。在这里,我们注意调整每个图像视图的框架,以便它们彼此相邻:</p>
<pre><code class="language-swift">this.page1 = new UIImageView (pageFrame);
//…
pageFrame.X += this.scrollView.Frame.Width;
//…
pageFrame.X += this.scrollView.Frame.Width;
</code></pre>
<p>我们为两个事件附加了处理程序。第一个是<code>UIScrollView.DecelerationEnded</code>事件,当用户滚动滚动视图时,它将调整页面控制器的当前页面。当前页面由滚动视图的<code>ContentOffset</code>属性确定:</p>
<pre><code class="language-swift">float x = this.scrollView.ContentOffset.X;
if (x == x1) {
this.pageControl.CurrentPage = 0;
// etc.
</code></pre>
<p>我们附加处理器的第二个事件是<code>UIPageControl.ValueChanged</code>事件。在这个处理程序中,我们确保当用户在页面控制上轻触或拖动时,内容会滚动。滚动是在将<code>ContentOffset</code>属性设置为所需图像视图的<code>Frame.X</code>属性时执行的,使用<code>UIScrollView.SetContentOffset(PointF, bool)</code>方法:</p>
<pre><code class="language-swift">case 0:
contentOffset.X = this.page1.Frame.X;
this.scrollView.SetContentOffset (contentOffset, true);
break;
// etc.
</code></pre>
<p><code>SetContentOffset()</code>方法的第二个参数指示滚动视图在滚动时进行动画。</p>
<h2 id="更多-2">更多...</h2>
<p>在这个菜谱中,使用了不同的<code>UIImageView</code>对象。可以根据我们想要显示的内容类型使用任何类型的<code>UIView</code>对象。</p>
<h3 id="正确使用-uipagecontrol">正确使用 UIPageControl</h3>
<p>用户期望在点击或拖动页面控件时滚动到其他页面。仅用于显示页面索引而不作为完全活动的控件是不好的做法。</p>
<h2 id="参见-12">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示图像</em></p>
</li>
<li>
<p><em>显示比屏幕更大的内容</em></p>
</li>
</ul>
<h1 id="显示工具栏">显示工具栏</h1>
<p>在这个菜谱中,我们将学习如何在应用程序中添加和使用工具栏。</p>
<h2 id="准备工作-13">准备工作</h2>
<p><code>UIToolbar</code> 类提供了一个包含各种按钮的工具栏。它是位于视图底部的栏。在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>项目,并将其命名为 <code>ToolbarApp</code>。</p>
<h2 id="如何做-6">如何做...</h2>
<p>创建此项目的步骤如下:</p>
<ol>
<li>
<p>在 Interface Builder 中打开 <code>ToolbarAppViewController.xib</code> 文件,并在其视图底部添加一个 <code>UIToolbar</code> 对象。</p>
</li>
<li>
<p>选择默认包含的按钮,并在 <strong>Attributes Inspector</strong> 面板中将它的 <strong>Identifier</strong> 设置为 <strong>Save</strong>。</p>
</li>
<li>
<p>向工具栏添加一个 <strong>Flexible Space Bar Button Item</strong> 对象。</p>
</li>
<li>
<p>在工具栏上添加另一个按钮,位于上一个对象的右侧,并将其 <strong>Identifier</strong> 设置为 <strong>Reply</strong>。</p>
</li>
<li>
<p>在视图中添加一个 <code>UILabel</code> 对象,并将所有控件(除了灵活空间项)连接到 outlets。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>回到 MonoDevelop,在 <code>ToolBarAppViewController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">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!";
} ;
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。点击工具栏的两个按钮,并查看状态字符串在标签上显示。</p>
</li>
</ol>
<h2 id="它是如何工作的-14">它是如何工作的...</h2>
<p>工具栏包含类型为 <code>UIBarButtonItem</code> 的项。这些项是特殊类型的按钮和间隔。<code>UIBarButtonItem</code> 可以有自定义类型,或者任何在 Interface Builder 的 <strong>Identifier</strong> 属性中列出的预定义类型。当项是按钮并且它提供的行为包含在这些标识符中时,建议使用它们。这些标识符中的每一个基本上为按钮提供了一种特定的图标,根据其预期用途,用户对它们相当熟悉,因为它们被大多数 iOS 应用程序所使用。注意,我们添加到工具栏中的 <strong>Flexible Space Bar Button Item</strong> 也是一个 <strong>UIBarButtonItem</strong>,具有特定的标识符。它的目的是在需要时保持两个按钮之间的距离,例如,当设备在横屏方向旋转并且工具栏调整大小以适应新宽度时。</p>
<h3 id="注意-21">注意</h3>
<p>仅旋转设备不会使 <strong>UIToolbar</strong> 调整大小。这种行为将在 第三章 中讨论,用户界面:视图控制器</p>
<p>这种类型的按钮在 Interface Builder 中显示,但在运行时并不显示。</p>
<p>在代码中,我们为按钮的 <code>Clicked</code> 事件添加了处理程序,其目的是相当熟悉的:</p>
<pre><code class="language-swift">this.barSave.Clicked += delegate {
this.labelStatus.Text = "Button Save tapped!";
};
</code></pre>
<p>当用户点击按钮时,它会被触发。</p>
<h2 id="更多内容-4">更多内容...</h2>
<p><code>UIBarButtonItem</code> 类有一个 <code>Style</code> 属性,它决定了按钮的样式。它只能在按钮项的标识符设置为 <code>Custom</code> 时使用。</p>
<h3 id="以编程方式设置-uitoolbar-的项">以编程方式设置 UIToolbar 的项</h3>
<p>要将工具栏项设置为 <code>UIToolbar</code>,请使用其 <code>SetItems()</code> 方法的重载之一。在 <code>UIToolbar</code> 中设置两个工具栏项的示例如下:</p>
<pre><code class="language-swift">UIBarButtonItem barSave = new UIBarButtonItem(UIBarButtonSystemItem.Save);
UIBarButtonItem barReply = new UIBarButtonItem(UIBarButtonSystemItem.Reply);
this.toolBar.SetItems(new UIBarButtonItem[] { barSave, barReply }, true);
</code></pre>
<h2 id="相关内容-3">相关内容</h2>
<p>在本书中:</p>
<p>第三章,用户界面:视图控制器:</p>
<ul>
<li><em>在不同视图控制器之间导航</em></li>
</ul>
<h1 id="创建自定义视图">创建自定义视图</h1>
<p>在本食谱中,我们将学习如何覆盖 <code>UIView</code> 类及其派生类,以创建自定义视图。</p>
<h2 id="准备工作-14">准备工作</h2>
<p>到目前为止,我们已经讨论了创建 iOS 应用程序时可以使用的许多视图。然而,将会有很多情况需要我们实现自己的自定义视图。在本任务中,我们将看到如何创建自定义视图并使用它。</p>
<h3 id="注意-22">注意</h3>
<p>创建自定义视图在我们需要捕捉触摸或实现其他自定义行为,例如绘图时非常有用。</p>
<p>在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>项目,并将其命名为 <code>CustomViewApp</code>。</p>
<h2 id="如何实现">如何实现...</h2>
<p>完成此项目的步骤如下:</p>
<ol>
<li>
<p>在项目中添加一个新的 C# 类文件,并将其命名为 <code>MyView</code>。</p>
</li>
<li>
<p>使用以下代码实现:</p>
<pre><code class="language-swift">using System;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using System.Drawing;
namespace CustomViewApp{
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);
}
}
}
</code></pre>
</li>
<li>
<p>在 Interface Builder 中打开 <code>CustomViewAppViewController.xib</code> 文件,并在主视图中添加一个 <code>UIView</code> 对象。</p>
</li>
<li>
<p>将其连接到出口,并在<strong>身份检查器</strong>中设置其<strong>类</strong>字段为 <code>MyView</code>。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在模拟器上编译并运行应用程序。在视图中轻触并拖动,观察触摸坐标在屏幕底部的标签中显示。</p>
</li>
</ol>
<h2 id="它是如何工作的-15">它是如何工作的...</h2>
<p>创建自定义视图时要注意的第一件事是从 <code>UIView</code> 类派生它们,并用 <code>Register</code> 属性装饰它们:</p>
<pre><code class="language-swift">
public class MyView : UIView
</code></pre>
<p><code>Register</code> 属性基本上将我们的类暴露给 Objective-C 世界。请注意,我们传递给它的参数名称必须与我们在<strong>身份检查器</strong>中的<strong>类</strong>字段中输入的名称相同。创建以下构造函数以覆盖基类的 <code>UIView(IntPtr)</code> 非常重要。</p>
<pre><code class="language-swift">public MyView (IntPtr handle) : base(handle) {}
</code></pre>
<p>当视图通过原生代码初始化时,始终调用此构造函数。如果我们不覆盖它,当运行时尝试在发出内存警告时重新创建它时,对象初始化时将发生异常。本例中使用的另一个构造函数仅作为指导,说明如果视图是程序初始化的,可能会使用什么:</p>
<pre><code class="language-swift">public MyView (RectangleF frame) : base(frame) {}
</code></pre>
<p>这两个构造函数都调用了 <code>Initialize()</code> 方法,该方法执行我们需要的初始化,例如创建将使用的标签,设置背景颜色等。</p>
<p>然后,重写了 <code>TouchesMoved</code> 方法。这是当用户在视图中拖动手指时执行的方法。在方法内部,我们从方法的 <code>NSSet</code> 参数中检索 <code>UITouch</code> 对象:</p>
<pre><code class="language-swift">UITouch touch = (UITouch)touches.AnyObject;
</code></pre>
<h3 id="注意-23">注意</h3>
<p><code>NSSet</code> 对象是一个无特定顺序的数据集合。它类似于数组。它的 <code>AnyObject</code> 参数从集合中返回一个对象。</p>
<p><code>UITouch</code> 对象包含有关用户触摸的信息。我们从其中检索触摸的当前位置:</p>
<pre><code class="language-swift">PointF touchLocation = touch.LocationInView (this);
</code></pre>
<p>它的 <code>LocationInView</code> 方法接受一个类型为 <code>UIView</code> 的参数,该参数声明了位置将在哪个视图的坐标系中计算。在这种情况下,我们感兴趣的是 <code>MyView</code> 的坐标。</p>
<h2 id="还有更多-10">还有更多...</h2>
<p>如果我们想要初始化我们通过编程创建的自定义视图,我们会输入以下代码:</p>
<pre><code class="language-swift">MyView myView = new MyView(new RectangleF(0f, 0f, 320f, 480f));
</code></pre>
<h2 id="参见-13">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>添加和自定义视图</em></li>
</ul>
<p>在本书中:</p>
<p>第三章,用户界面:视图控制器:</p>
<ul>
<li><em>视图控制器和视图</em></li>
</ul>
<h1 id="第三章用户界面视图控制器">第三章。用户界面:视图控制器</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>使用视图控制器加载视图</p>
</li>
<li>
<p>在不同的视图控制器之间导航</p>
</li>
<li>
<p>在标签页中提供控制器</p>
</li>
<li>
<p>创建表格控制器</p>
</li>
<li>
<p>模态视图控制器</p>
</li>
<li>
<p>创建自定义视图控制器</p>
</li>
<li>
<p>高效使用视图控制器</p>
</li>
<li>
<p>组合不同的视图控制器</p>
</li>
<li>
<p>iPad 视图控制器</p>
</li>
<li>
<p>为不同设备创建用户界面</p>
</li>
</ul>
<h1 id="简介-2">简介</h1>
<p>到目前为止,我们已经讨论了视图及其使用方法。在大多数现实世界应用场景中,仅使用视图是不够的。Apple 提供了另一个基类,即 <code>UIViewController</code>,它负责管理视图。视图控制器可以响应设备通知,例如当设备旋转时,或者可以提供不同的方式来显示和关闭多个视图,甚至其他视图控制器。</p>
<p>我们还将了解如何使用最常用的视图控制器来创建管理多个视图的应用程序。</p>
<p>这些视图控制器是:</p>
<ul>
<li>
<p><code>UIViewController:</code> 这是所有视图控制器的基类</p>
</li>
<li>
<p><code>UINavigationController:</code> 这是一个提供多种在不同视图控制器之间导航方式的视图控制器</p>
</li>
<li>
<p><code>UITabBarController:</code> 这是一个以标签界面显示多个视图控制器的视图控制器</p>
</li>
<li>
<p><code>UITableViewController:</code> 这是一个用于以列表形式显示数据的视图控制器</p>
</li>
<li>
<p><strong>针对 iPad 的视图控制器:</strong> 这些是仅适用于 iPad 设备的视图控制器</p>
</li>
</ul>
<p>此外,我们还将讨论组合不同的控制器,如何创建自定义控制器并使用它们,以及我们将创建一个可以在 iPhone 和 iPad 上部署的应用程序。</p>
<h1 id="使用视图控制器加载视图">使用视图控制器加载视图</h1>
<p>在本食谱中,我们将学习如何使用 <code>UIViewController</code> 类来管理视图。</p>
<h2 id="准备工作-15">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 <code>ViewControllerApp</code>。</p>
<h2 id="如何操作-8">如何操作...</h2>
<ol>
<li>
<p>向项目中添加一个新文件。</p>
</li>
<li>
<p>右键点击 <strong>Solution</strong> 面板中的项目,并选择 <strong>Add | New File</strong>。</p>
</li>
<li>
<p>在出现的对话框中,从 <strong>MonoTouch</strong> 部分选择 <strong>iPhone View</strong> 并带有 <strong>Controller</strong>,将其命名为 <code>MainViewController</code>,然后点击 <strong>New</strong> 按钮。MonoDevelop 将创建一个新的 <code>XIB</code> 文件,并自动打开 <code>MainViewController.cs</code> 源文件。此文件包含一个覆盖 <code>UIViewController</code> 的类,我们可以在其中实现与我们的视图控制器相关的任何代码。</p>
</li>
<li>
<p>在 Interface Builder 中打开 <code>MainViewController.xib</code> 文件。</p>
</li>
<li>
<p>在视图中添加一个 <code>UILabel</code>。</p>
</li>
<li>
<p>在 <code>MainViewController</code> 类中创建并连接一个出口,并将其命名为 <code>myLabel</code>。</p>
</li>
<li>
<p>在标签中输入文本 <code>View in controller!</code>。</p>
</li>
<li>
<p>保存 <code>XIB</code> 文档。</p>
</li>
<li>
<p>在 MonoDevelop 中返回,并在 <code>FinishedLaunching()</code> 方法中输入以下代码:</p>
<pre><code class="language-swift">MainViewController mainController = new MainViewController ();
window.RootViewController = mainController;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
</ol>
<h2 id="它是如何工作的-16">它是如何工作的...</h2>
<p>当我们在项目中添加一个新的<strong>iPhone View with Controller</strong>文件时,在这种情况下是<code>MainViewController</code>,MonoDevelop 基本上创建并添加了三个文件:</p>
<ol>
<li>
<p><code>MainViewController.xib</code>:这是包含控制器的 XIB 文件。</p>
</li>
<li>
<p><code>MainViewController.cs</code>:这是实现我们控制器类的 C#源文件。</p>
</li>
<li>
<p><code>MainViewController.designer.cs</code>:这是自动生成的源文件,反映了我们在 Interface Builder 中对控制器所做的更改。</p>
</li>
</ol>
<p>注意,我们不需要为视图添加一个出口,因为这是由 MonoDevelop 处理的。我们通过其类初始化控制器:</p>
<pre><code class="language-swift">MainViewController mainController = new MainViewController ();
</code></pre>
<p>然后,我们通过控制器中的<code>View</code>显示其视图,将其设置为应用程序窗口的<code>RootViewController</code>:</p>
<pre><code class="language-swift">window.RootViewController = mainController;
</code></pre>
<h2 id="更多内容-5">更多内容...</h2>
<p>我们刚刚创建的项目仅展示了我们如何添加一个带有视图的控制器。注意,我们在<code>MainViewController</code>类内部创建了标签的出口,该类在<code>XIB</code>文件中充当文件的所有者对象。为了为<code>MainViewController</code>提供一些功能,在<code>MainViewController.cs</code>文件中的<code>MainViewController</code>类中添加以下方法:</p>
<pre><code class="language-swift">public override void ViewDidLoad (){
base.ViewDidLoad();
this.myLabel.Text = "View loaded!";
}
</code></pre>
<p>此方法覆盖了<code>UIViewController.ViewDidLoad()</code>方法,该方法在控制器加载其视图时执行。</p>
<h3 id="要覆盖的uiviewcontroller方法">要覆盖的<code>UIViewController</code>方法</h3>
<p><code>UIViewController</code>类包含的方法是我们覆盖以使用其功能的方法。其中一些方法包括:</p>
<ul>
<li>
<p><code>ViewDidUnload()</code>:当视图被卸载时调用</p>
</li>
<li>
<p><code>ViewWillAppear()</code>:当视图即将在屏幕上显示时调用</p>
</li>
<li>
<p><code>ViewDidAppear()</code>:当视图已显示时调用</p>
</li>
<li>
<p><code>ViewWillDisappear()</code>:当视图即将消失时调用,例如,当另一个控制器即将显示时<code>ViewDidDisappear()</code>:当视图消失时调用</p>
</li>
</ul>
<h2 id="参见-14">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>在不同的视图控制器之间导航</em></li>
</ul>
<p>在本书中:</p>
<p>第一章,开发工具:</p>
<ul>
<li>
<p><em>使用 MonoDevelop 创建 iPhone 项目</em></p>
</li>
<li>
<p><em>通过出口访问 UI</em></p>
</li>
</ul>
<h1 id="在不同的视图控制器之间导航">在不同的视图控制器之间导航</h1>
<p>在本食谱中,我们将学习如何使用<code>UINavigationController</code>类在多个视图控制器之间导航。</p>
<h2 id="准备工作-16">准备工作</h2>
<p><code>UINavigationController</code>是一个提供具有多个视图控制器的分层导航功能的控制器。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为<code>NavigationControllerApp</code>。</p>
<h2 id="如何做到这一点-2">如何做到这一点...</h2>
<ol>
<li>
<p>在项目中添加三个新的<strong>iPhone View with Controller</strong>文件,并分别命名为<code>RootViewController, ViewController1</code>和<code>ViewController2</code>。</p>
</li>
<li>
<p>在<code>AppDelegate</code>类中添加以下字段:</p>
<pre><code class="language-swift">UINavigationController navController;
</code></pre>
</li>
<li>
<p>在同一类中,在<code>FinishedLaunching</code>方法中,在<code>window.MakeKeyAndVisible()</code>行之上添加以下代码:</p>
<pre><code class="language-swift">RootViewController rootController = new RootViewController();
this.navController = new UINavigationController(rootController);
window.RootViewController = this.navController;
</code></pre>
</li>
<li>
<p>在 Interface Builder 中打开<code>RootViewController.xib</code>文件,并添加两个按钮及其相应的出口。分别设置它们的标题为<code>第一个视图</code>和<code>第二个视图</code>。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>打开<code>ViewController1.xib</code>和<code>ViewController2.xib</code>,并在每个视图中添加一个标题为<code>返回根视图</code>的按钮。不要忘记连接按钮与出口并保存文档。</p>
</li>
<li>
<p>在<code>RootViewController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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);
};
}
</code></pre>
</li>
<li>
<p>在<code>ViewController1</code>和<code>ViewController2</code>类中输入以下内容:</p>
<pre><code class="language-swift">public override void ViewDidLoad (){
this.buttonPop.TouchUpInside += delegate {
this.NavigationController.PopToRootViewController (true);
};
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。点击每个按钮以查看和导航到可用的视图。</p>
</li>
</ol>
<h2 id="它是如何工作的-17">它是如何工作的...</h2>
<p><code>UINavigationController</code>保留了一个控制器堆栈。<code>UIViewController</code>类有一个名为<code>NavigationController</code>的属性。在正常情况下,这个属性返回 null。但是,如果控制器被推入导航控制器的堆栈中,它将返回正在推入的导航控制器的实例。因此,这样在任何控制器层次结构中的任何一点,都可以提供对导航控制器的访问。要显示一个控制器,我们调用<code>UINavigationController.PushViewController(UIViewController, bool)</code>方法:</p>
<pre><code class="language-swift">this.NavigationController.PushViewController (cont1, true);
</code></pre>
<p>注意,<code>RootViewController</code>是导航堆栈中最顶层或根控制器。导航控制器必须至少有一个将充当其根控制器的视图控制器。我们在创建<code>UINavigationController</code>类的实例时设置它:</p>
<pre><code class="language-swift">this.navController = new UINavigationController(rootController);
</code></pre>
<p>要返回根控制器,我们在当前控制器中调用<code>PopToRootViewController(bool)</code>方法:</p>
<pre><code class="language-swift">this.NavigationController.PopToRootViewController (true);
</code></pre>
<p>两个方法中的<code>bool</code>参数用于在控制器之间进行带有动画的转换。</p>
<h2 id="还有更多-11">还有更多...</h2>
<p>在这个简单的示例中,我们通过按钮提供了返回根控制器的导航。注意顶部栏中有一个箭头形状的按钮。那个栏被称为<strong>导航栏</strong>,其类型为<code>UINavigationBar</code>。箭头形状的按钮被称为<strong>返回</strong>按钮,其类型为<code>UIBarButtonItem</code>。当返回按钮存在时,它总是导航到导航堆栈中的上一个控制器。</p>
<h3 id="管理导航栏按钮">管理导航栏按钮</h3>
<p>要更改、添加和隐藏导航栏的按钮,我们可以使用当前显示的视图控制器的<code>NavigationItem</code>属性的以下方法:</p>
<ul>
<li>
<p><code>SetLeftBarButtonItem:</code> 它在导航栏的左侧添加一个自定义按钮,替换默认的<strong>返回</strong>按钮</p>
</li>
<li>
<p><code>SetRightBarButtonItem:</code> 它在导航栏的右侧添加一个自定义按钮</p>
</li>
<li>
<p><code>SetHidesBackButton:</code> 它设置默认<strong>返回</strong>按钮的可见性</p>
</li>
</ul>
<p>要移除或隐藏导航栏上的自定义左侧或右侧按钮,请调用适当的方法,传递<code>null</code>而不是<code>UIBarButtonItem</code>对象。</p>
<h2 id="参见-15">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>模态视图控制器</em></p>
</li>
<li>
<p><em>高效使用视图控制器</em></p>
</li>
<li>
<p><em>结合不同的视图控制器</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第十一章,图形和动画:</p>
<ul>
<li><em>使用动画推送视图控制器</em></li>
</ul>
<h1 id="在标签中提供控制器">在标签中提供控制器</h1>
<p>在这个示例中,我们将学习如何在标签界面中显示多个视图控制器。</p>
<h2 id="准备工作-17">准备工作</h2>
<p><code>UITabBarController</code> 提供了一种在相同层次结构级别上显示不同视图控制器的方法,这些控制器被划分为类似标签的界面。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 <code>TabControllerApp</code>。</p>
<h2 id="如何做-7">如何做...</h2>
<p>向项目中添加两个 <strong>iPhone View with Controller</strong> 文件。将它们命名为 <code>MainController</code> 和 <code>SettingsController</code>。</p>
<ol>
<li>
<p>在 Interface Builder 中打开两个控制器,并设置它们视图的不同背景颜色,然后保存文档。</p>
</li>
<li>
<p>在 <code>AppDelegate</code> 类中添加以下字段:</p>
<pre><code class="language-swift">UITabBarController tabController;
</code></pre>
</li>
<li>
<p>在 <code>FinishedLaunching()</code> 方法中输入以下代码,在 <code>window.MakeKeyAndVisible()</code> 行之上:</p>
<pre><code class="language-swift">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);
} ;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击屏幕底部的每个标签,并查看它们各自显示的视图。控制台输出显示在 MonoDevelop 的 <strong>应用程序输出</strong> 窗格中。以下截图显示了模拟器的屏幕,其中 <strong>设置</strong> 标签被选中:</p>
</li>
</ol>
<p></p>
<h2 id="它是如何工作的-18">它是如何工作的...</h2>
<p><code>UITabBarController</code> 显示它管理的每个控制器的一个标签页。该标签页的类型为 <code>UITabBarItem</code>,它可以接受文本和图像。<code>UITabBarController</code> 类包含有关其包含的控制器的信息。我们可以通过 <code>ViewControllerSelected</code> 事件确定用户选择了哪个控制器:</p>
<pre><code class="language-swift">this.tabBarController.ViewControllerSelected += new EventHandler<UITabBarSelectionEventArgs> (delegate(object sender, UITabBarSelectionEventArgs e) {
</code></pre>
<p><code>UITabBarSelectionEventArgs</code> 对象在其 <code>ViewController</code> 属性中持有所选控制器的实例。通过访问 <code>UIViewController.TabBarItem</code> 属性,我们可以确定哪个控制器被选中:</p>
<pre><code class="language-swift">Console.WriteLine ("Controller {0} selected.", e.ViewController.TabBarItem.Title);
</code></pre>
<p>在这个示例中,我们输出其 <code>Title</code> 属性。</p>
<h3 id="注意-24">注意</h3>
<p>就像 <code>UIViewController</code> 类的 <code>NavigationController</code> 属性,其中它返回它所属的 <code>UINavigationController</code> 实例一样,<code>TabBarItem</code> 属性只有在控制器是 <code>UITabBarController</code> 的部分时才会持有实例。在其他情况下,它将返回 <code>null</code>。</p>
<p>当我们初始化标签控制器时,我们通过 <code>SetViewControllers</code> 方法设置它将包含的控制器,传递一个视图控制器对象的数组:</p>
<pre><code class="language-swift">this.tabController.SetViewControllers(new UIViewController[] {
mainController,
settingsController
});
</code></pre>
<h2 id="还有更多-12">还有更多...</h2>
<p>控制器可以接受我们想要的任意数量的控制器,但如果添加六个或更多,则四个将带有标签显示,而第五个预定义的<strong>更多</strong>标签将代表所有剩余的控制器。这是为了保持界面易于用户访问,通过保持标签到适合人类手指的特定大小。当我们在一个标签栏控制器界面中添加超过六个控制器时,默认情况下,对象在<strong>更多</strong>标签的顶部提供一个<strong>编辑</strong>按钮,允许用户重新排列控制器的顺序。如果我们想排除某些控制器从这个功能中,我们必须从 <code>CustomizableViewControllers</code> 数组中移除它。</p>
<h3 id="有用的-uitabbarcontroller-属性">有用的 UITabBarController 属性</h3>
<p><code>UITabBarController</code> 类的一些其他有用属性如下:</p>
<ul>
<li>
<p><code>ViewControllers:</code> 返回一个包含所有由标签控制器持有的控制器的数组</p>
</li>
<li>
<p><code>SelectedIndex:</code> 返回选中标签的零基索引</p>
</li>
<li>
<p><code>SelectedViewController:</code> 返回当前选中的控制器</p>
</li>
</ul>
<h3 id="关于标签栏界面的重要说明">关于标签栏界面的重要说明</h3>
<p>虽然我们可以在 <code>UITabBarController</code> 中添加任何类型的控制器,但我们不能在另一个控制器中添加 <code>UITabBarController</code>,例如 <code>UINavigationController</code>。然而,我们可以在 <code>UITabBarController</code> 中添加 <code>UINavigationController</code>。这是因为标签栏界面是为了实现不同的控制器作为不同的应用程序模式,而不是层次屏幕。</p>
<h2 id="相关内容-4">相关内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>高效使用视图控制器</em></p>
</li>
<li>
<p><em>组合不同的视图控制器</em></p>
</li>
</ul>
<h1 id="创建表格控制器">创建表格控制器</h1>
<p>在本食谱中,我们将学习如何创建并将 <code>UITableViewController</code> 添加到项目中。</p>
<h2 id="准备工作-18">准备工作</h2>
<p>使用 <code>UITableViewController</code> 来显示 <code>UITableView</code>。<code>UITableView</code> 提供了一个以列表形式显示数据的界面。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 <code>TableControllerApp</code>。</p>
<h2 id="如何操作-9">如何操作...</h2>
<ol>
<li>
<p>将项目添加一个<strong>iPhone 视图控制器</strong>,并将其命名为 <code>TableController</code>。</p>
</li>
<li>
<p>在 <code>AppDelegate</code> 类的 <code>FinishedLaunching</code> 方法中添加以下代码:</p>
<pre><code class="language-swift">TableController tableController = new TableController();
window.RootViewController = tableController;
</code></pre>
</li>
<li>
<p>将 <code>TableController</code> 类的继承从 <code>UIViewController</code> 改为 <code>UITableViewController:</code></p>
<pre><code class="language-swift">public partial class TableController : UITableViewController
</code></pre>
</li>
<li>
<p>在 Interface Builder 中打开 <code>TableController.xib</code>,选择并按退格键删除其视图。</p>
</li>
<li>
<p>将 <code>UITableView</code> 拖放到其位置。</p>
</li>
<li>
<p>右键单击 <code>UITableView</code> 以显示输出口面板。</p>
</li>
<li>
<p>按照以下截图所示,从<strong>新引用输出口</strong>拖动到<strong>文件所有者</strong>对象:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_03_02.jpg"></p>
</li>
<li>
<p>当你释放按钮时,从出现的<strong>文件所有者</strong>对象的小面板中选择视图。这连接了我们刚刚添加的 <code>UITableView</code> 到<strong>文件所有者</strong>对象的 <code>view</code> 输出口。</p>
</li>
<li>
<p>保存文档。</p>
</li>
</ol>
<h2 id="它是如何工作的-19">它是如何工作的...</h2>
<p>当我们在 Interface Builder 文档中添加一个 <code>UITableView</code> 时,其视图会显示一些预定义的数据。这些数据仅在设计时出现,而不是在运行时。</p>
<p><code>UITableViewController</code> 包含一个 <code>UITableView</code> 类型的视图。这个视图负责显示数据,并且可以通过多种方式自定义。</p>
<h2 id="还有更多-13">还有更多...</h2>
<p>除了 <code>View</code> 属性外,我们还可以通过 <code>TableView</code> 属性访问 <code>UITableViewController</code> 的视图。这两个属性返回相同的对象。</p>
<h3 id="uitableviewcontroller-特定属性"><code>UITableViewController</code> 特定属性</h3>
<p><code>UITableViewController</code> 有一个额外的属性:<code>ClearsSelectionOnViewWillAppear</code>。当它设置为 <code>true</code> 时,控制器将在视图出现时自动清除所选行。</p>
<p>如何使用 <code>UITableView</code> 填充数据将在 第五章 中详细讨论,显示数据。</p>
<h2 id="参见-16">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>模态视图控制器</em></li>
</ul>
<p>在这本书中:</p>
<p>第五章,显示数据:</p>
<ul>
<li><em>在表格中显示数据</em></li>
</ul>
<h1 id="模态视图控制器">模态视图控制器</h1>
<p>在这个菜谱中,我们将讨论如何以模态方式显示视图控制器。</p>
<h2 id="准备工作-19">准备工作</h2>
<p><strong>模态视图控制器</strong> 是任何显示在其他视图或控制器之上的控制器。这个概念类似于将 <strong>WinForm</strong> 作为对话框显示,它控制界面并阻止访问应用程序的其他窗口,除非它被关闭。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 <code>ModalControllerApp</code>。</p>
<h2 id="如何做到这一点-3">如何做到这一点...</h2>
<ol>
<li>
<p>将两个带有控制器的视图添加到项目中,并分别命名为 <code>MainController</code> 和 <code>ModalController</code>。</p>
</li>
<li>
<p>在 Interface Builder 中打开 <code>MainController.xib</code> 文件,并在其视图中添加一个标题为 <code>Present</code> 的按钮。</p>
</li>
<li>
<p>创建并连接按钮的适当出口。保存文档并打开 <code>ModalController.xib</code> 文件。</p>
</li>
<li>
<p>在其视图中添加一个标题为 <code>Dismiss</code> 的按钮,并为它创建适当的出口。将其视图的背景颜色设置为非白色。</p>
</li>
<li>
<p>保存文档并在 <code>MainController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">public override void ViewDidLoad (){
this.buttonPresent.TouchUpInside += delegate {
ModalController modal = new ModalController ();
this.PresentModalViewController (modal, true);
};
}
</code></pre>
</li>
<li>
<p>类似地,覆盖 <code>ModalController</code> 类中的 <code>ViewDidLoad()</code> 方法,并在其中输入以下代码:</p>
<pre><code class="language-swift">this.buttonDismiss.TouchUpInside += delegate {
this.DismissModalViewControllerAnimated (true);
};
</code></pre>
</li>
<li>
<p>最后,在 <code>FinishedLaunching()</code> 方法中添加代码以显示主控制器:</p>
<pre><code class="language-swift">MainController mainController = new MainController ();
window.RootViewController = mainController;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>Present</strong> 按钮并观察模态控制器在主控制器之上显示。</p>
</li>
<li>
<p>点击 <strong>Dismiss</strong> 按钮以隐藏它。</p>
</li>
</ol>
<h2 id="它是如何工作的-20">它是如何工作的...</h2>
<p>每个控制器对象都有两个处理模态显示和关闭控制器的方法。在我们的示例中,我们调用 <code>PresentModalViewController (UIViewController, bool)</code> 方法来显示控制器:</p>
<pre><code class="language-swift">this.buttonPresent.TouchUpInside += delegate {
ModalController modal = new ModalController ();
this.PresentModalViewController (modal, true);
};
</code></pre>
<p>其第一个参数表示我们想要以模态方式显示的控制器,第二个参数确定我们是否想要动画化显示。要关闭控制器,我们调用其 <code>DismissModalViewControllerAnimated(bool)</code> 方法:</p>
<pre><code class="language-swift">this.DismissModalViewControllerAnimated (true);
</code></pre>
<p>它只接受一个参数,用于切换消失动画。</p>
<h2 id="更多内容-6">更多内容...</h2>
<p>我们可以使用控制器的 <code>ModalTransitionStyle</code> 属性定义模态视图控制器呈现的动画类型。在呈现模态控制器之前输入以下代码行:</p>
<pre><code class="language-swift">modal.ModalTransitionStyle = UIModalTransitionStyle.FlipHorizontal;
</code></pre>
<p>主控制器将翻转以显示模态控制器,给人一种它附着在其后面的印象。</p>
<h3 id="访问模态控制器">访问模态控制器</h3>
<p>每个以模态方式呈现另一个控制器的控制器都通过 <code>ModalController</code> 属性提供对其“子”控制器的访问。如果您需要访问此属性,请确保在调用 <code>DismissModalViewControllerAnimated()</code> 方法之前进行操作。</p>
<h4 id="有多少模态控制器">有多少模态控制器?</h4>
<p>理论上,我们可以呈现无限数量的模态控制器。当然,对此有两个限制:</p>
<ol>
<li>
<p><strong>内存不是无限的</strong>:视图控制器会消耗内存,因此我们呈现的视图控制器越多,性能越差。</p>
</li>
<li>
<p><strong>糟糕的用户体验</strong>:以模态方式呈现许多控制器会让用户感到不适。</p>
</li>
</ol>
<h2 id="参见-17">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>在不同视图控制器之间导航</em></p>
</li>
<li>
<p><em>在标签页中提供控制器</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第十一章,图形和动画:</p>
<ul>
<li><em>使用动画推送视图控制器</em></li>
</ul>
<h1 id="创建自定义视图控制器">创建自定义视图控制器</h1>
<p>在本食谱中,我们将学习如何创建 <code>UIViewController</code> 的子类,并使用它来从 <code>XIB</code> 文件中派生视图控制器。</p>
<h2 id="准备工作-20">准备工作</h2>
<p>在本任务中,我们将看到如何创建一个自定义视图控制器,它将充当基控制器,为其继承者提供共同的功能。我们将添加到我们的基控制器以与继承类共享的功能是,在 MonoDevelop 的 <strong>应用程序输出</strong> 面板上输出当前的触摸位置。在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 <code>CustomControllerApp</code>。</p>
<h2 id="如何实现-1">如何实现...</h2>
<ol>
<li>
<p>在项目中添加一个新的空 C# 类,并将其命名为 BaseController。</p>
</li>
<li>
<p>在 BaseController.cs 文件中输入以下代码:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>现在,将一个 <strong>带有控制器的 iPhone 视图</strong> 文件添加到项目中,并将其命名为 <code>DerivedController</code>。</p>
</li>
<li>
<p>在其类定义中将继承自 <code>UIViewController</code> 的类更改为 <code>BaseController</code>:<code>public partial class DerivedController : BaseController</code>。</p>
</li>
<li>
<p>最后,将派生控制器的视图添加到主窗口:</p>
<pre><code class="language-swift">DerivedController derivedController = new DerivedController();
window. RootViewController = derivedController;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>在白色表面上点击并拖动鼠标指针,观察 MonoDevelop 的 <strong>应用程序输出</strong> 面板显示指针在模拟器屏幕上的当前位置。</p>
</li>
</ol>
<h2 id="它是如何工作的-21">它是如何工作的...</h2>
<p>我们在这里所做的是创建一个可以用于多个 MonoTouch 项目的基控制器类。我们添加到这个控制器中的功能是响应用户触摸。任何继承它的控制器都将继承相同的功能。我们添加到创建<code>BaseController</code>类的代码相当简单。我们实现的构造函数仅仅是 MonoDevelop 在项目中添加新视图控制器时在类实现中创建的构造函数的副本。这里只有一处细微的修改:</p>
<pre><code class="language-swift">public BaseController (string nibName, NSBundle bundle) : base(nibName, bundle){}
</code></pre>
<p>这是当通过派生对象的<code>DerivedController()</code>构造函数使用<code>new</code>关键字初始化<code>DerivedController</code>类时将被调用的基构造函数。</p>
<pre><code class="language-swift">derivedController = new DerivedController();
</code></pre>
<h2 id="还有更多-14">还有更多...</h2>
<p>派生控制器也可以添加到另一个<code>XIB</code>文件中,并通过出口直接在代码中使用。</p>
<h3 id="从-xib-继承视图控制器">从 XIB 继承视图控制器</h3>
<p>如果我们想要创建一个从包含在<code>XIB</code>文件中的控制器派生的基控制器,过程是类似的。</p>
<h2 id="参见-18">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>使用控制器加载视图</em></p>
</li>
<li>
<p><em>高效使用视图控制器</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第二章,用户界面:视图:</p>
<ul>
<li><em>添加和自定义视图</em></li>
</ul>
<h1 id="高效使用视图控制器">高效使用视图控制器</h1>
<p>在这个食谱中,我们将学习关于高效使用视图控制器的基本指南。</p>
<h2 id="准备工作-21">准备工作</h2>
<p>打开我们在本章前面“在标签中提供控制器”食谱中创建的项目<code>TabControllerApp</code>。</p>
<h2 id="如何做到-1">如何做到...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开<code>MainController.xib</code>文件,并添加一个<code>UIButton</code>和一个<code>UILabel</code>。通过出口将它们连接起来。</p>
</li>
<li>
<p>在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>在<strong>主</strong>标签页上轻触按钮以显示我们列表的内容。</p>
</li>
<li>
<p>切换到<strong>设置</strong>标签。</p>
</li>
<li>
<p>在模拟器的菜单栏中点击<strong>硬件 | 模拟内存警告</strong>。</p>
</li>
<li>
<p>在 MainDevelop 的<strong>应用程序输出</strong>中查看输出,并切换回<strong>主</strong>标签。</p>
</li>
</ol>
<h2 id="它是如何工作的-22">它是如何工作的...</h2>
<p>此项目不提供任何有用的功能。其主要目的是展示如何正确使用视图控制器。</p>
<p>当 iOS 需要更多内存来执行各种操作时,它会发出内存警告。当发生内存警告时,所有由控制器处理且未使用的 UI 对象都会从内存中清除,以释放更多内存。</p>
<p>模拟器提供了一个方法,让开发者可以通过从菜单栏中选择我们之前选择的<strong>硬件 | 模拟内存警告</strong>操作来重新创建这样的场景。</p>
<p>由于我们处于<strong>设置</strong>标签,<code>MainController</code>的内容已被从内存中清除。在<code>DidReceiveMemoryWarning</code>方法中,我们清理任何非 UI 对象,否则这些对象将保留在内存中:</p>
<pre><code class="language-swift">this.cacheList.Clear();
</code></pre>
<p>接下来,在<code>ViewDidUnload</code>方法中,我们只需要释放任何由出口保留的 UI 对象。请注意,这就是我们从这些对象可能持有的事件中取消连接处理程序的地方:</p>
<pre><code class="language-swift">this.lblOutput = null;
this.btnShowData.TouchUpInside -= ButtonShowData_TouchUpInside;
this.btnShowData = null;
</code></pre>
<p>当我们再次选择 <strong>主</strong> 标签时,<code>ViewDidLoad</code> 方法将被再次调用,在控制器视图及其包含的所有视图和输出加载完毕之后。</p>
<h2 id="更多内容-7">更多内容...</h2>
<p>当发生内存警告时,与 UI 无直接关系的对象实例将保留在内存中。在极少数情况下,如果没有足够的内存来完成特定任务,操作系统可能会终止我们的应用程序,如果它占用了大部分可用内存。为了防止这种情况,我们需要小心清理所有不需要的对象和资源,为 iOS 释放更多内存。</p>
<h3 id="注意-25">注意</h3>
<p>不要在 <code>ViewDidUnload</code> 方法中访问控制器的视图:</p>
<pre><code class="language-swift">public override ViewDidUnload()
{
base.ViewDidUnload();
this.View = null; // Never do this.
}
</code></pre>
<p>这是因为即使我们请求视图控制器的 <code>View</code> 属性的返回值,也会导致视图重新加载,这在大多数情况下意味着不会释放内存。</p>
<h2 id="相关内容-5">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>在标签页中提供控制器</em></li>
</ul>
<p>在本书中:</p>
<p>第一章,开发工具:</p>
<ul>
<li><em>界面构建器</em></li>
</ul>
<p>第四章,</p>
<ul>
<li>
<p><em>创建文件</em></p>
</li>
<li>
<p><em>创建 SQLite 数据库</em></p>
</li>
</ul>
<h1 id="结合不同的视图控制器">结合不同的视图控制器</h1>
<p>在这个菜谱中,我们将学习如何在 <code>UITabBarController</code> 中显示 <code>UINavigationController</code>。</p>
<h2 id="准备工作-22">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的 iPhone 空项目,并将其命名为 <code>CombinedControllerApp</code>。</p>
<h2 id="如何做到-2">如何做到...</h2>
<p>创建此项目的步骤如下:</p>
<ol>
<li>
<p>将三个 iPhone 视图控制器文件添加到项目中,并分别命名为 <code>MainController, SettingsController</code> 和 <code>AfterMainController</code>。</p>
</li>
<li>
<p>在 Interface Builder 中 <code>MainController</code> 视图中添加一个 <code>UIButton</code>,并保存文档。</p>
</li>
<li>
<p>在 <code>MainController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">public override void ViewDidLoad (){
base.ViewDidLoad ();
this.Title = "Main";
this.buttonPush.TouchUpInside += delegate {
this.NavigationController.PushViewController(new AfterMainController(), true);
};
}
</code></pre>
</li>
<li>
<p>在 <code>AppDelegate</code> 类中添加以下字段:</p>
<pre><code class="language-swift">UINavigationController navController;
UITabBarController tabController;
</code></pre>
</li>
<li>
<p>在 <code>AppDelegate</code> 类的 <code>FinishedLaunching</code> 方法中添加以下代码:</p>
<pre><code class="language-swift">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;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。在 <code>MainController</code> 中轻触按钮,将 <code>AfterMainController</code> 推送到导航堆栈中,然后切换到 <strong>主</strong> 和 <strong>设置</strong> 标签。</p>
</li>
</ol>
<h2 id="工作原理">工作原理...</h2>
<p>完整的解决方案可以在 <code>CombinedControllerApp</code> 文件夹中找到。我们通过这个项目实现了提供三个不同屏幕的用户界面,这对用户来说不会造成困惑。</p>
<p>标签栏包含两个系统定义的项目,每个项目代表一个不同的视图控制器。我们在标签栏控制器中实现了第一个项目,使用导航控制器。这样,我们可以提供更多与特定部分的应用程序相关的屏幕(<strong>主</strong> 加 <strong>AfterMain</strong>),同时让应用程序的另一部分在任何时候都可以直接访问 <strong>(设置)</strong>。</p>
<h2 id="更多内容-8">更多内容...</h2>
<p>本项目结合了三个不同的控制器(一个 <code>UITabBarController</code>、一个 <code>UINavigationController</code> 和一个 <code>UIViewController</code>)的方式是完全可接受的。我们甚至可以用另一个导航控制器替换第二个标签项,为应用程序的另一个部分提供更多的屏幕,或者甚至添加另一个标签项。</p>
<p>然而,正如本章中 <em>在标签中提供控制器</em> 的配方所述,如果我们在一个 <code>UINavigationController</code> 内添加一个 <code>UITabBarController</code>,这是不可接受的。如果我们想在导航控制器内提供类似标签的行为,我们应该使用它的 <code>UIToolbar</code> 来实现。</p>
<h2 id="参见-19">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>在不同视图控制器之间导航</em></p>
</li>
<li>
<p><em>在标签中提供控制器</em></p>
</li>
<li>
<p><em>为不同设备创建用户界面</em></p>
</li>
</ul>
<h1 id="ipad-视图控制器">iPad 视图控制器</h1>
<p>在本配方中,我们将讨论仅适用于 iPad 的控制器。</p>
<h2 id="准备工作-23">准备工作</h2>
<p>创建一个新的 iPad 空项目,并将其命名为 <code>iPadControllerApp</code>。</p>
<h2 id="如何实现-2">如何实现...</h2>
<ol>
<li>
<p>将两个带有控制器的 iPad 视图添加到项目中,并分别命名为 <code>FirstController</code> 和 <code>SecondController</code>。为它们的背景视图设置不同的颜色。在 <code>SecondController</code> 中,在其视图顶部添加一个 <code>UIToolbar</code>,并将其连接到一个出口。</p>
</li>
<li>
<p>在 <code>AppDelegate</code> 类中添加以下字段:</p>
<pre><code class="language-swift">UISplitViewController splitController;
FirstController firstController;
SecondController secondController;
</code></pre>
<h3 id="注意-26">注意</h3>
<p><code>UISplitViewController</code> 类仅适用于 iPad。</p>
</li>
<li>
<p>在 <code>FinishedLaunching</code> 方法中添加以下代码:</p>
<pre><code class="language-swift">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;
</code></pre>
</li>
<li>
<p>在 <code>AppDelegate</code> 中添加以下嵌套类:</p>
<pre><code class="language-swift">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, true);
}
}
</code></pre>
</li>
<li>
<p>在 <code>SecondController</code> 类中添加一个属性,它返回我们在 <em>步骤 1</em> 中创建的工具栏出口:</p>
<pre><code class="language-swift">public UIToolbar SecToolbar{
get { return this.secToolbar; }
}
</code></pre>
</li>
<li>
<p>最后,在模拟器中编译并运行应用程序。点击工具栏中的按钮,使 <code>FirstController</code> 出现。结果应该类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_03_03.jpg"></p>
<h2 id="它是如何工作的-23">它是如何工作的...</h2>
<p>完整解决方案可以在 <code>iPadControllerApp</code> 文件夹中找到。有两个特定于 iPad 的控制器:<code>UISplitViewController</code> 和 <code>UIPopoverController</code>。它们都被使用在这里,尽管 <code>UIPopoverController</code> 没有直接使用。</p>
<p><code>UISplitViewController</code> 有助于充分利用 iPad 的更大屏幕。它提供了一种在相同屏幕区域内同时显示两个不同视图的方法。它是通过在纵向全屏显示一个控制器,而在弹出视图中显示另一个较小的控制器来实现的。<strong>弹出视图</strong>基本上是一个视图,它显示在其他控制器(及其视图)的顶部,就像一个模态视图控制器一样。</p>
<p>为了让用户能够访问我们项目中的两个控制器,我们实现了一个继承自 <code>UISplitViewControllerDelegate</code> 的类,并在 <code>FinishedLaunching()</code> 方法中将它分配给我们的分割控制器。我们创建的 <code>Delegate</code> 对象覆盖了两个方法。在第一个方法中,我们将一个按钮分配给工具栏:</p>
<pre><code class="language-swift">public override void WillHideViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem barButtonItem, UIPopoverController pc){
barButtonItem.Title = "First";
this.parentController.SecToolbar.SetItems (new UIBarButtonItem[] { barButtonItem }, true);
}
</code></pre>
<p>当<code>UISplitViewController</code>从横屏变为竖屏,并且其较小的控制器即将被隐藏时,<code>WillHideViewController()</code>方法会被执行。因此,为了显示它,我们在全屏控制器的工具栏上提供了一个按钮。当我们点击该按钮时,另一个控制器将以弹出窗口的形式出现。当方向从竖屏变为横屏时,较小的控制器出现在较大的控制器旁边,无需弹出窗口。因此,我们不再需要在工具栏上按钮,因此我们重写<code>WillShowViewController</code>以从工具栏中移除按钮。我们通过分配一个空的<code>UIBarButtonItem[]</code>数组来完成此操作:</p>
<pre><code class="language-swift">public override void WillShowViewController (UISplitViewController svc, UIViewController aViewController, UIBarButtonItem button){
this.parentController.SecToolbar.SetItems (new UIBarButtonItem, true);
}
</code></pre>
<h2 id="更多内容-9">更多内容...</h2>
<p>当设备旋转时,界面不会自动响应。为了指示视图控制器旋转其视图,我们在分割视图控制器的两个控制器中重写<code>ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation)</code>方法:</p>
<pre><code class="language-swift">public bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation){
return true;
}
</code></pre>
<h3 id="ipad-特定控制器使用">iPad 特定控制器使用</h3>
<p>尽管所有其他控制器都可用于 iPhone 和 iPad,但这两个控制器不能在 iPhone 上使用。在这种情况下将发生异常。</p>
<h2 id="参考内容">参考内容</h2>
<p>在本章中:</p>
<ul>
<li><em>为不同设备创建用户界面</em></li>
</ul>
<p>在本书中:</p>
<p>第九章, 与设备硬件交互:</p>
<ul>
<li><em>旋转设备</em></li>
</ul>
<h1 id="为不同设备创建用户界面">为不同设备创建用户界面</h1>
<p>在这个菜谱中,我们将学习如何创建一个同时支持 iPhone 和 iPad 的应用程序。</p>
<h2 id="准备工作-24">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的通用空项目,并将其命名为<code>UniversalApp</code>。</p>
<h2 id="如何操作-10">如何操作...</h2>
<ol>
<li>
<p>向项目中添加一个新的<strong>iPhone 视图控制器</strong>,并将其命名为<code>MainController</code>。</p>
</li>
<li>
<p>在 Interface Builder 中打开它,并在视图中为其添加一个标签和一个出口。</p>
</li>
<li>
<p>在标签中输入文本<code>在 iPhone 上运行!</code></p>
</li>
<li>
<p>将视图的背景颜色改为白色以外的颜色。同样对标签进行操作,并保存文档。</p>
</li>
<li>
<p>在<code>MainViewController</code>类中添加以下代码:</p>
<pre><code class="language-swift">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!";
}
}
</code></pre>
</li>
<li>
<p>在<code>AppDelegate</code>类的<code>FinishedLaunching()</code>中添加以下代码:</p>
<pre><code class="language-swift">MainController mainController = new MainController();
window.RootViewController = mainController;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>读取标签的消息,表明它正在 iPhone 上运行。在 MonoDevelop 中终止执行,并在菜单栏上点击<strong>运行 | 运行方式 | iPad 模拟器 x.x</strong>(其中<strong>x.x</strong>是系统上安装的相应 iOS 版本,这里<strong>5.0</strong>)。<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_03_04.jpg"></p>
</li>
<li>
<p>读取表明应用程序正在 iPad 上运行的消息!</p>
</li>
</ol>
<h2 id="它是如何工作的-24">它是如何工作的...</h2>
<p>当我们在 MonoDevelop 中创建通用项目时,基本区别在于应用程序设置文件<code>(Info.plist)</code>,其中声明应用程序支持 iPhone 和 iPad。</p>
<p>事实上,我们添加了一个 <strong>带有控制器的 iPhone 视图</strong> 并不会阻止我们为两种设备使用相同的控制器。记住,所有控制器都适用于所有设备,除了之前菜谱中讨论的那些。</p>
<p>在 <code>ViewDidLoad</code> 方法内部,我们通过检查 <code>UIDevice.CurrentDevice</code> 静态属性的 <code>UserInterfaceIdiom</code> 属性来确定应用程序正在运行在哪种设备上,并为视图提供一个大小与 iPad 屏幕尺寸 {768, 1024} 相匹配的框架。</p>
<pre><code class="language-swift">if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
</code></pre>
<h2 id="更多内容-10">更多内容...</h2>
<p>这将确保根据应用程序运行在哪种设备上,对项目中包含的视图进行尺寸调整。但是,这并不能保证所有控件都将被正确地调整大小和定位。为了避免用户界面杂乱,我们必须确保调整我们控件和视图的 <code>Autosizing</code> 属性,以便它们可以在不同的屏幕上正确地调整大小和定位。</p>
<h2 id="参见-20">参见</h2>
<p>本章内容:</p>
<ul>
<li>
<p><em>高效使用视图控制器</em></p>
</li>
<li>
<p><em>iPad 视图控制器</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第一章,开发工具:</p>
<ul>
<li>
<p><em>使用 MonoDevelop 创建 iPhone 项目</em></p>
</li>
<li>
<p><em>界面构建器</em></p>
</li>
</ul>
<h1 id="第四章数据管理">第四章:数据管理</h1>
<p>在本章中,我们将涵盖以下主题:</p>
<ul>
<li>
<p>创建文件</p>
</li>
<li>
<p>创建 SQLite 数据库</p>
</li>
<li>
<p>插入和更新数据</p>
</li>
<li>
<p>查询 SQLite 数据库</p>
</li>
<li>
<p>使用现有的 SQLite 数据库</p>
</li>
<li>
<p>使用序列化存储数据</p>
</li>
<li>
<p>使用 XML 存储数据</p>
</li>
<li>
<p>使用 LINQ to XML 管理 XML 数据</p>
</li>
</ul>
<h1 id="简介-3">简介</h1>
<p>几乎每个应用程序都需要在文件系统中具有永久数据存储。在本章中,我们将讨论不同的数据存储方式。我们将了解如何在 iPhone 应用程序中创建 SQLite 数据库并使用它来管理数据。此外,我们还将学习如何在项目中使用现有的数据库。</p>
<p>SQLite (<code>www.sqlite.org</code> ) 是一个自包含的事务型数据库系统。每个数据库都保存在一个独立的文件中,没有数据库服务器。在 iOS 中,SQLite 支持是原生的。</p>
<p>接下来,我们将看到如何序列化和将对象保存到文件系统中,以及如何使用 LINQ to XML 与 XML 文件一起使用。</p>
<h1 id="创建文件">创建文件</h1>
<p>在这个配方中,我们将学习如何在 iOS 设备的文件系统中创建文件。</p>
<h2 id="准备工作-25">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>项目,命名为<code>FileCreationApp</code>。打开<code>FileCreationAppViewController.xib</code>文件,并在其视图中添加一个<code>UILabel</code>和一个<code>UIButton</code>。</p>
<h2 id="如何操作-11">如何操作...</h2>
<p>在<code>FileCreationAppViewController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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 ();
}
}
};
</code></pre>
<h2 id="工作原理-1">工作原理...</h2>
<p>从这段代码中可以看出,我们可以像在桌面应用程序中一样使用<code>System.IO</code>命名空间中的标准类。我们首先做的事情是为我们将要保存的文件设置一个路径。我们在以下行中这样做:</p>
<pre><code class="language-swift">string filePath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "MyFile.txt");
</code></pre>
<p>在 iOS 中,我们无法访问整个文件系统,甚至在应用程序包内部也不行。如果我们尝试在无法访问的文件夹中写入,将会发生异常。因此,我们使用静态的<code>Environment.GetFolderPath(SpecialFolder)</code>方法来检索<code>Personal</code>特殊文件夹,这对应于我们的应用程序的<code>Documents</code>文件夹。注意<code>Path.Combine(string, string)</code>的使用,它将两个字符串组合并返回一个路径。之后,我们创建<code>StreamWriter</code>类的新实例,并使用其<code>WriteLine(string)</code>方法在文件中写入一些文本。</p>
<pre><code class="language-swift">using (StreamWriter sw = new StreamWriter (filePath)){
sw.WriteLine ("Some text in file!");
}
</code></pre>
<p>要从文件中检索文本,我们创建<code>StreamReader</code>类的新实例,并使用其<code>ReadLine</code>方法读取文本:</p>
<pre><code class="language-swift">using (StreamReader sr = new StreamReader (filePath)){
this.labelMessage.Text = sr.ReadToEnd ();
}
</code></pre>
<h2 id="更多内容-11">更多内容...</h2>
<p>如果我们想要写入或读取二进制数据,我们可以使用<code>FileStream</code>类。</p>
<h3 id="文档文件夹">文档文件夹</h3>
<p>应用程序的<code>Documents</code>文件夹仅与该应用程序相关。如果从设备中卸载应用程序,其内容也会被删除。我们在这个文件夹中既有读又有写权限。</p>
<h2 id="参见-21">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>使用序列化存储数据</em></li>
</ul>
<h1 id="创建-sqlite-数据库">创建 SQLite 数据库</h1>
<p>在这个配方中,我们将学习如何创建 SQLite 数据库文件。</p>
<h2 id="准备工作-26">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>,命名为<code>CreateSQLiteApp</code>。在其视图中添加一个<code>UILabel</code>和一个<code>UIButton</code>。</p>
<h2 id="如何操作-12">如何操作...</h2>
<ol>
<li>
<p>将项目引用添加到<code>Mono.Data.Sqlite</code>程序集,并在命名空间上添加相应的<code>using</code>指令:</p>
<pre><code class="language-swift">using Mono.Data.Sqlite;
</code></pre>
</li>
<li>
<p>在<code>CreateSQLiteAppViewController</code>类中输入以下方法:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>然后在<code>ViewDidLoad</code>方法中添加以下代码:</p>
<pre><code class="language-swift">string sqlitePath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "MyDB.db3");
this.btnCreate.TouchUpInside += delegate {
this.CreateSQLiteDatabase (sqlitePath);
};
</code></pre>
</li>
</ol>
<h3 id="它是如何工作的-25">它是如何工作的...</h3>
<p>iOS 为 SQLite 数据库提供了原生支持。</p>
<ol>
<li>
<p>我们可以使用 Mono 的<code>Mono.Data.Sqlite</code>命名空间来访问 SQLite 数据库,如下所示:</p>
<pre><code class="language-swift">using Mono.Data.Sqlite;
</code></pre>
</li>
<li>
<p>在<code>CreateSQLiteDatabase</code>方法内部,我们首先检查文件是否已存在:</p>
<pre><code class="language-swift">if (!File.Exists (databaseFile))
</code></pre>
</li>
<li>
<p>然后,我们可以继续创建数据库。我们首先使用<code>SqliteConnection.CreateFile(string)</code>静态方法创建文件,如下所示:</p>
<pre><code class="language-swift">SqliteConnection.CreateFile (databaseFile);
</code></pre>
</li>
<li>
<p>我们通过初始化一个<code>SqliteConnection</code>对象并调用其<code>Open()</code>方法来连接新创建的文件。SQLite 数据库的连接字符串为<code>Data Source =</code>,后跟数据库的文件名:</p>
<pre><code class="language-swift">using (SqliteConnection sqlCon = new SqliteConnection (String.Format ("Data Source = {0};", databaseFile)))
sqlCon.Open();
</code></pre>
</li>
<li>
<p>在数据库中创建一个表时,会初始化一个<code>SqliteCommand</code>对象。我们将一个标准的 SQL 字符串传递给其<code>CommandText</code>属性,并调用<code>ExecuteNonQuery()</code>方法来执行 SQL:</p>
<pre><code class="language-swift">sqlCom.CommandText = "CREATE TABLE Customers (ID INTEGER PRIMARY KEY, FirstName VARCHAR(20), LastName VARCHAR(20))";
sqlCom.ExecuteNonQuery ();
</code></pre>
</li>
</ol>
<h4 id="更多内容-12">更多内容...</h4>
<p>注意 try-catch 块的使用。它用于在数据库创建过程中出现错误时向用户显示消息。</p>
<h5 id="sql-表创建">SQL 表创建</h5>
<p>在这个任务中,我们为我们的数据库创建了一个简单的表,名为<code>Customers</code>。它包含三个字段。<code>FirstName</code>和<code>LastName</code>字段类型为<code>VARCHAR(20)</code>,而<code>ID</code>字段类型为<code>INTEGER</code>,同时也是表的<code>PRIMARY KEY</code>。</p>
<p>除了使用 SQL 命令创建表之外,我们还可以使用各种商业或免费的 GUI 工具创建 SQLite 数据库。在互联网上简单搜索就会得到各种结果。</p>
<h4 id="参见-22">参见</h4>
<p>在本章中:</p>
<ul>
<li>
<p><em>查询 SQLite 数据库</em></p>
</li>
<li>
<p><em>插入和更新数据</em></p>
</li>
<li>
<p><em>使用现有的数据库</em></p>
</li>
</ul>
<h1 id="插入和更新数据">插入和更新数据</h1>
<p>在这个菜谱中,我们将学习如何将数据写入数据库。</p>
<h2 id="准备工作-27">准备工作</h2>
<p>对于这个任务,我们将扩展我们在上一个任务中创建的<code>CreateSQLiteApp</code>项目。</p>
<h2 id="如何操作-13">如何操作...</h2>
<ol>
<li>
<p>在视图中添加两个额外的按钮。</p>
</li>
<li>
<p>在<code>CreateSQLiteAppViewController</code>类内部,创建两个方法,这些方法将使用上一个任务中的代码连接到数据库文件。这里的区别在于<code>SqliteCommand</code>对象的使用:</p>
<pre><code class="language-swift">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 ();
}
</code></pre>
</li>
</ol>
<h3 id="它是如何工作的-26">它是如何工作的...</h3>
<p>要在 SQLite 表中插入和更新数据,我们分别使用常见的<code>INSERT</code>和<code>UPDATE</code>语句。代码中高亮的部分表示了 SQLite 参数的使用。这两个语句都在<code>sqlCom.ExecuteNonQuery()</code>行上对数据库进行执行。<code>ExecuteNonQuery</code>的返回值类型为<code>int</code>,表示受影响的表中的行数。所以,如果我们像以下示例中那样调用该方法,我们会得到输出<code>1</code>,表示影响了一行:</p>
<pre><code class="language-swift">Console.WriteLine(sqlCom.ExecuteNonQuery());
</code></pre>
<h3 id="更多内容-13">更多内容...</h3>
<p>由于我们已经使用了上一个任务中的项目,其中我们提供了创建数据库文件的代码,因此我们应该在我们的每个执行数据操作的方法的开始处添加以下代码:</p>
<pre><code class="language-swift">if (!File.Exists (databaseFile)) {
this.lblMessage.Text = "Database file does not exist. Tap the appropriate button to create it.";
return;
}
</code></pre>
<p>这是为了确保当用户在没有任何数据库文件的情况下点击 <strong>插入</strong> 或 <strong>更新</strong> 按钮时,不会出现异常。</p>
<h4 id="sqlite-性能">SQLite 性能</h4>
<p>虽然 SQLite 提供了出色的性能和可移植性,但它在很大程度上依赖于其宿主文件系统,无论它存储在哪个平台上。如果您想执行多个并发 <code>INSERT</code> 或 <code>UPDATE</code> 语句,请考虑使用 <code>SqliteTransaction</code>。除了性能上的好处外,通过将多个语句批处理在一起,事务提供了一种在出现问题时回滚操作的方法。</p>
<h3 id="参见-23">参见</h3>
<p>在本章中:</p>
<ul>
<li>
<p><em>创建文件</em></p>
</li>
<li>
<p><em>创建 SQLite 数据库</em></p>
</li>
<li>
<p><em>查询 SQLite 数据库</em></p>
</li>
</ul>
<h1 id="查询-sqlite-数据库">查询 SQLite 数据库</h1>
<p>在本配方中,我们将学习如何从 SQLite 数据库中检索数据。</p>
<h2 id="准备工作-28">准备工作</h2>
<p>再次使用 <code>CreateSQLiteApp</code> 项目。完成此任务后,该项目将是一个完整的 SQLite 管理应用程序。</p>
<h2 id="如何操作-14">如何操作...</h2>
<ol>
<li>
<p>在视图中添加另一个按钮。</p>
</li>
<li>
<p>在 <code>CreateSQLiteAppViewController</code> 类内部,添加一个处理查询的方法。执行查询的部分如下:</p>
<pre><code class="language-swift">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"]));
}
}
}
}
</code></pre>
</li>
</ol>
<h3 id="它是如何工作的-27">它是如何工作的...</h3>
<p>要在 SQLite 数据库上执行查询,我们创建一个简单的 <code>SELECT</code> 语句并将其分配给 <code>SqliteCommand</code> 对象的 <code>CommandText</code> 属性:</p>
<pre><code class="language-swift">sqlCom.CommandText = "SELECT * FROM Customers WHERE LastName = @lastName";
</code></pre>
<p><code>SELECT</code> 关键字后面的星号 (*) 表示我们想要检索表中的所有字段。执行 SQL 查询的最简单方法是通过使用 <code>SqliteDataReader</code> 类:</p>
<pre><code class="language-swift">using (SqliteDataReader dbReader = sqlCom.ExecuteReader ())
</code></pre>
<p><code>SqliteCommand.ExecuteReader()</code> 方法在表上执行查询并创建一个 <code>SqliteDataReader</code> 对象。现在我们有了对象,我们首先检查表是否包含任何行:</p>
<pre><code class="language-swift">if (dbReader.HasRows)
</code></pre>
<p>如果上述返回 <code>true</code>,我们开始通过传递 <code>SqliteDataReader.Read()</code> 方法在 <code>while</code> 循环中逐行前进:</p>
<pre><code class="language-swift">while (dbReader.Read ())
</code></pre>
<p>我们现在可以通过传递每个字段的名称作为 <code>SqliteDataReader</code> 实例的索引来检索每个字段的值:</p>
<pre><code class="language-swift">this.lblMessage.Text += String.Format ("ID: {0}\n", Convert.ToString (dbReader["ID"]));
//...
</code></pre>
<p>由于索引的 <code>dbReader</code> 变量返回的是 <code>System.Object</code> 类型的对象,我们使用 <code>Convert.ToString(object)</code> 静态方法将其转换为字符串。</p>
<h3 id="更多内容-14">更多内容...</h3>
<p>SQLite 数据库不是线程安全的。如果我们想在执行查询的同时,另一个线程可能正在执行 <code>INSERT</code> 或 <code>UPDATE</code> 语句,那么提供某种同步机制会更好。</p>
<h4 id="查询性能">查询性能</h4>
<p>虽然运行 iOS 平台的设备在性能上优于一些较老的桌面计算机,但它们的资源仍然有限。在查询大型数据库中的数据时,请考虑使用 SQL <code>WHERE</code> 语句缩小结果,以获取特定时间所需的数据。此外,如果不需要排序,请避免使用 <code>ORDER BY</code> 语句。</p>
<h3 id="参见-24">参见</h3>
<p>在本章中:</p>
<ul>
<li>
<p><em>创建 SQLite 数据库</em></p>
</li>
<li>
<p><em>插入和更新数据</em></p>
</li>
<li>
<p><em>使用现有的 SQLite 数据库</em></p>
</li>
</ul>
<h1 id="使用现有的-sqlite-数据库">使用现有的 SQLite 数据库</h1>
<p>在这个菜谱中,我们将讨论如何将现有的 SQLite 数据库文件包含到我们的项目中。</p>
<h2 id="准备工作-29">准备工作</h2>
<p>使用某种前端创建数据库总是更容易。在这个任务中,我们将了解如何将现有的 SQLite 数据库与 iPhone 项目集成。创建一个新的 <strong>iPhone 单视图应用程序</strong> 项目,并将其命名为 <code>SqliteIntegrationApp</code>。</p>
<h2 id="如何做到这一点-4">如何做到这一点...</h2>
<ol>
<li>
<p>在 Interface Builder 中的视图上添加一个 <code>UIButton</code> 和一个 <code>UILabel</code>。将它们与适当的出口连接起来。确保标签足够高,可以显示六行,并在 <strong>检查器</strong> 选项卡中设置其 <strong># Lines</strong> 字段。</p>
</li>
<li>
<p>在 MonoDevelop 中,在 <strong>解决方案</strong> 面板中右键单击项目,然后点击 <strong>添加文件...</strong>。</p>
</li>
<li>
<p>在显示的对话框中,导航到数据库文件所在的路径并选择它。文件将被添加到项目中。</p>
</li>
<li>
<p>右键单击它,然后选择 <strong>构建操作 | 内容</strong>。</p>
</li>
<li>
<p>在 <code>SqliteIntegrationAppViewController</code> 类中输入以下方法:</p>
<pre><code class="language-swift">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)
{ //... }
</code></pre>
</li>
<li>
<p><code>QueryData</code> 方法的功能与之前任务中执行查询的方法相同。主要区别在于,在这个项目中,它将作为按钮的 TouchUpInside 事件处理程序。</p>
</li>
<li>
<p>添加一个类型为 <code>string</code> 的字段,用于存储数据库路径:</p>
<pre><code class="language-swift">private string database;
</code></pre>
</li>
<li>
<p>最后,在 <code>ViewDidLoad</code> 方法中:</p>
<pre><code class="language-swift">this.database = this.CopyDatabase ();
this.buttonQuery.TouchUpInside += this.QueryData;
</code></pre>
</li>
<li>
<p>编译并运行应用程序。点击按钮,并观察数据库内容在标签中显示。</p>
</li>
</ol>
<h2 id="工作原理-2">工作原理...</h2>
<p>我们在这里所做的是将之前任务中讨论的各种实践相结合。当我们想在项目中添加各种文件时,我们设置它们的 <strong>构建操作</strong> 为 <strong>内容</strong> 非常重要。任何标记为 <strong>内容</strong> 的文件都会在应用程序包中按原样复制。在这种情况下,文件是一个 SQLite 数据库,因此我们需要在运行时对它有写入权限。我们需要将其复制到应用程序的 <strong>Documents</strong> 文件夹中。这就是 <code>CopyDatabase()</code> 方法在这一点上所做的事情:</p>
<pre><code class="language-swift">if (!File.Exists (databasePath)){
File.Copy (databaseFile, databasePath);
}
</code></pre>
<p>检查文件是否已经存在非常重要,这样它就不会在应用程序下次执行时被覆盖。</p>
<h2 id="还有更多-15">还有更多...</h2>
<p>如果遇到 <code>SqliteException</code>,首先检查是否由于某种原因数据库文件没有被复制到正确的文件夹。</p>
<h2 id="参见-25">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>创建文件</em></p>
</li>
<li>
<p><em>创建 SQLite 数据库</em></p>
</li>
</ul>
<h1 id="使用序列化存储数据">使用序列化存储数据</h1>
<p>在这个菜谱中,我们将讨论使用 .NET 序列化来存储 C# 对象。</p>
<h2 id="准备工作-30">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的 <strong>iPhone 单视图应用程序</strong> 项目。将其命名为 <code>SerializationApp</code>。</p>
<h2 id="如何做到这一点-5">如何做到这一点...</h2>
<ol>
<li>
<p>在视图上添加两个按钮和一个标签,在 Interface Builder 中。在 <code>SerializationAppViewController.cs</code> 文件中添加以下使用指令:</p>
<pre><code class="language-swift">using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
</code></pre>
</li>
<li>
<p>在 <code>AppDelegate</code> 类中添加以下方法:</p>
<pre><code class="language-swift">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.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!";
}
}
</code></pre>
</li>
</ol>
<p>完整代码可以在 <code>SerializationApp</code> 项目中找到。</p>
<h3 id="工作原理-3">工作原理...</h3>
<p>使用 MonoTouch 进行序列化与桌面 C#应用程序中的操作相同。在突出显示的代码中指示的<code>CustomerData</code>类是一个简单的对象,它包含一个整数和两个字符串属性。</p>
<ol>
<li>
<p>要序列化对象,我们首先初始化一个<code>MemoryStream</code>:</p>
<pre><code class="language-swift">using (MemoryStream ms = new MemoryStream ())
</code></pre>
</li>
<li>
<p>然后我们使用<code>BinaryFormatter</code>类来序列化对象并将其存储到流中:</p>
<pre><code class="language-swift">BinaryFormatter bf = new BinaryFormatter ();
bf.Serialize (ms, custData);
</code></pre>
</li>
<li>
<p>序列化后,我们将流的位置重置为其开始处,并将数据从流中读取到字节数组中:</p>
<pre><code class="language-swift">ms.Seek (0, SeekOrigin.Begin);
this.objBuffer = new byte;
ms.Read (this.objBuffer, 0, this.objBuffer.Length);
</code></pre>
</li>
<li>
<p>要从缓冲区反序列化对象,过程类似,但顺序相反。在<code>MemoryStream</code>初始化后,我们将流的位置重置为其开始处,并使用<code>BinaryFormatter</code>的<code>Deserialize</code>方法。它返回一个<code>System.Object</code>类型的对象,因此我们需要将其转换为我们的对象类型:</p>
<pre><code class="language-swift">ms.Seek (0, SeekOrigin.Begin);
BinaryFormatter bf = new BinaryFormatter ();
custData = (CustomerData)bf.Deserialize (ms);
</code></pre>
</li>
</ol>
<h4 id="还有更多-16">还有更多...</h4>
<p>在使用 MonoTouch 序列化对象时,有一件重要的事情要记住,那就是用<code>PreserveAttribute</code>装饰将要序列化的对象。此属性指示链接器避免删除未使用的成员,保持对象完整。在这个任务中声明的<code>CustomerData</code>类如下:</p>
<pre><code class="language-swift">
public class CustomerData
</code></pre>
<h5 id="可序列化对象">可序列化对象</h5>
<p>这是一个简单的示例,用于展示在 iOS 应用程序中使用二进制序列化的用法。可以通过继承<code>ISerializable</code> C#接口来定制用于序列化的对象。</p>
<p>当创建用于序列化的对象时,不要忘记用<code>SerializableAttribute</code>标记它。如果尝试序列化未标记此属性的对象,将会发生异常。</p>
<h4 id="相关内容-6">相关内容</h4>
<p>在本章中:</p>
<ul>
<li><em>使用 XML 存储数据</em></li>
</ul>
<p>在这本书中:</p>
<p>第一章,开发工具:</p>
<ul>
<li><em>编译</em></li>
</ul>
<h2 id="使用-xml-存储数据">使用 XML 存储数据</h2>
<p>在这个菜谱中,我们将讨论如何使用 XML 序列化存储数据。</p>
<h3 id="准备工作-31">准备工作</h3>
<p>在 MonoDevelop 中创建一个新的<strong>iPhone 单视图应用程序</strong>项目,并将其命名为<code>XMLDataApp</code>。</p>
<h3 id="如何操作-15">如何操作...</h3>
<ol>
<li>
<p>在视图中添加一个<code>UIButton</code>和一个<code>UILabel</code>。将上一个任务中的<code>CustomerData</code>类添加到项目中。在<code>XMLDataAppViewController.cs</code>文件中添加以下<code>using</code>指令:</p>
<pre><code class="language-swift">using System.Xml.Serialization;
using System.Xml;
using System.Text;
</code></pre>
</li>
<li>
<p>在<code>XMLDataAppViewController</code>类中添加以下方法:</p>
<pre><code class="language-swift">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 ();
}
</code></pre>
</li>
</ol>
<p>完整的解决方案可以在<code>XMLDataApp</code>项目中找到。</p>
<h3 id="工作原理-4">工作原理...</h3>
<p>在这个例子中,我们使用<code>XmlSerializer</code>类将对象序列化为 XML。<code>StringBuilder</code>对象将保存 XML 数据:</p>
<pre><code class="language-swift">this.sb = new StringBuilder ();
XmlSerializer xmlSer = new XmlSerializer (typeof(CustomerData));
</code></pre>
<p>要初始化<code>XmlSerializer</code>,我们使用它的<code>XmlSerializer(System.Type)</code>构造函数,将我们想要序列化的对象的类型作为参数传递:<code>(typeof(CustomerData))</code>。然后我们调用它的<code>Serialize(XmlWriter, object)</code>方法,该方法将执行实际的序列化并将输出 XML 存储到<code>StringBuilder</code>中,通过<code>XmlWriter</code>类,如下所示:</p>
<pre><code class="language-swift">xmlSer.Serialize (XmlWriter.Create (sb), custData);
</code></pre>
<p>当你编译并运行应用程序时,你将在标签中看到输出 XML。</p>
<h3 id="还有更多-17">还有更多...</h3>
<p>使用 XML 存储数据有两个主要优势:</p>
<ol>
<li>
<p>输出是纯文本,与二进制序列化不同,这使得它易于阅读。</p>
</li>
<li>
<p>数据可以传输或接收来自不同类型的应用程序、网站等,这些应用程序和网站不一定是用 C#编写的。</p>
</li>
</ol>
<h4 id="反序列化">反序列化</h4>
<p>要从 XML 反序列化我们的对象,我们将使用以下行:</p>
<pre><code class="language-swift">custData = (CustomerData)xmlSer.Deserialize (new StringReader (sb.ToString ()));
</code></pre>
<h3 id="相关内容-7">相关内容</h3>
<p>在本章中:</p>
<ul>
<li>
<p><em>使用序列化存储数据</em></p>
</li>
<li>
<p><em>使用 LINQ to XML 管理 XML 数据</em></p>
</li>
</ul>
<h2 id="使用-linq-to-xml-管理-xml-数据">使用 LINQ to XML 管理 XML 数据</h2>
<p>在这个配方中,我们将学习如何使用<strong>语言集成查询(LINQ)</strong>来管理 XML 数据。</p>
<h3 id="准备工作-32">准备工作</h3>
<p>在这个任务中,我们将使用上一个任务中的项目<code>XMLDataApp</code>。在 MonoDevelop 中打开它。</p>
<h3 id="如何操作-16">如何操作...</h3>
<ol>
<li>
<p>在视图中添加另一个<code>UIButton</code>。在<code>XMLDataAppViewController.cs</code>文件中添加对<code>System.Xml.Linq</code>程序集的引用和以下<code>using</code>指令:</p>
<pre><code class="language-swift">using System.Linq.Xml;
</code></pre>
</li>
<li>
<p>在<code>XMLDataAppViewController</code>类中输入以下方法:</p>
<pre><code class="language-swift">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);
}
}
}
</code></pre>
</li>
</ol>
<h4 id="它是如何工作的-28">它是如何工作的...</h4>
<p>LINQ 在查询 XML 数据方面非常灵活且直观。</p>
<ol>
<li>
<p>我们首先从我们的 XML 数据创建一个<code>XDocument</code>:</p>
<pre><code class="language-swift">XDocument xDoc = XDocument.Parse (this.sb.ToString ());
</code></pre>
</li>
<li>
<p>然后,我们对其<code>Descendants()</code>方法创建一个查询,该方法返回一个<code>IEnumerable<XElement></code>对象。每个<code>XElement</code>对象对应一个 XML 元素。</p>
</li>
<li>
<p>使用返回的每个<code>XElement</code>的信息,我们在<code>select</code>语句中构建我们的<code>CustomerData</code>对象:</p>
<pre><code class="language-swift">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
};
</code></pre>
</li>
<li>
<p>要检索创建的对象,我们在查询上调用扩展方法<code>FirstOrDefault()</code>:</p>
<pre><code class="language-swift">CustomerData custData = query.FirstOrDefault();
</code></pre>
</li>
</ol>
<h5 id="更多内容-15">更多内容...</h5>
<p>在这个例子中,XML 只包含一个类型为<code>CustomerData</code>的对象。如果有更多,我们可以通过提供一个<code>where</code>语句来缩小结果:</p>
<pre><code class="language-swift">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
};
</code></pre>
<h6 id="linq-中的匿名类型">LINQ 中的匿名类型</h6>
<p>在 MonoTouch 中,我们还可以在查询中使用 C#的强大功能——匿名类型,例如,<code>select new { ID = … }</code></p>
<h5 id="相关内容-8">相关内容</h5>
<p>在本章中:</p>
<ul>
<li>
<p><em>使用序列化存储数据</em></p>
</li>
<li>
<p><em>存储数据</em></p>
</li>
</ul>
<h1 id="第五章显示数据">第五章:显示数据</h1>
<p>在本章中,我们将涵盖以下主题:</p>
<ul>
<li>
<p>提供列表</p>
</li>
<li>
<p>在表格中显示数据</p>
</li>
<li>
<p>自定义行</p>
</li>
<li>
<p>编辑表格:删除行</p>
</li>
<li>
<p>编辑表格:插入行</p>
</li>
<li>
<p>表索引</p>
</li>
<li>
<p>搜索数据</p>
</li>
<li>
<p>创建一个简单的网页浏览器</p>
</li>
<li>
<p>显示本地内容</p>
</li>
<li>
<p>显示格式化文本</p>
</li>
<li>
<p>显示文档</p>
</li>
</ul>
<h1 id="简介-4">简介</h1>
<p>在上一章中,我们讨论了 iOS 应用程序中数据管理的一些可用选项。在本章中,我们将讨论向用户显示数据的各种方法。</p>
<p>具体来说,我们将看到如何使用以下控件:</p>
<ul>
<li>
<p><code>UIPickerView:</code> 这是一个提供类似列表框功能的控件。</p>
</li>
<li>
<p><code>UITableView:</code> 这是一个非常可定制的视图,用于显示数据。iOS 应用程序中最常用的控件之一。</p>
</li>
<li>
<p><code>UISearchBar</code> <strong>和</strong> <code>UISearchDisplayController:</code> 这些是一组控件,提供了一种易于使用的界面来搜索数据。</p>
</li>
<li>
<p><code>UIWebView:</code> 这将网页浏览器功能带给应用程序。</p>
</li>
<li>
<p><code>QLPreviewController:</code> 这可以显示各种文档格式。</p>
</li>
</ul>
<p>此外,我们将学习如何在表格中提供索引,以便用户可以轻松访问大量数据。我们还将讨论一些显示格式化文本的可用方法,甚至包括<strong>便携式文档格式(PDF)</strong>和其他文档。</p>
<p>从本章开始,所有代码示例都将使用默认视图控制器<code>MainController</code>,除非另有说明。</p>
<h1 id="提供列表">提供列表</h1>
<p>在本食谱中,我们将学习如何使用<code>UIPickerView</code>类。</p>
<h2 id="准备工作-33">准备工作</h2>
<p><code>UIPickerView</code>类为我们提供了一个与列表框功能相似的控件,专门设计用于人类手指触摸屏幕。它与普通列表框的主要区别在于,每一列可以有自己的行数。要开始,创建一个新的 iPhone 项目,并将其命名为<code>PickerViewApp</code>。</p>
<h2 id="如何做到这一点-6">如何做到这一点...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开<code>MainController.xib</code>文件。</p>
</li>
<li>
<p>在主视图中添加一个<code>UILabel</code>和一个<code>UIPickerView</code>。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在 MonoDevelop 中,创建一个继承自<code>UIPickerViewModel:</code>的<code>MainController</code>类中的嵌套类:</p>
<pre><code class="language-swift">private class PickerModelDelegate : UIPickerViewModel
</code></pre>
</li>
<li>
<p>在嵌套类中添加以下构造函数和字段:</p>
<pre><code class="language-swift">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;
this.distanceSelected = this.distanceList;
this.unitSelected = this.unitList;
}
private MainController parentController;
private List<string> transportList;
private List<string> distanceList;
private List<string> unitList;
string transportSelected;
string distanceSelected;
string unitSelected;
</code></pre>
</li>
<li>
<p>您现在需要覆盖<code>UIPickerViewModel</code>类中的四个方法:</p>
<ul>
<li>
<p><code>int GetComponentCount (UIPickerView picker)</code></p>
</li>
<li>
<p><code>int GetRowsInComponent (UIPickerView picker, int component)</code></p>
</li>
<li>
<p><code>string GetTitle (UIPickerView picker, int row, int component)</code></p>
</li>
<li>
<p><code>void Selected (UIPickerView picker, int row, int component)</code></p>
</li>
</ul>
</li>
<li>
<p>最后,在控制器中的<code>ViewDidLoad</code>方法内将创建的模型对象设置为 picker 视图的<code>Model</code>属性:</p>
<pre><code class="language-swift">this.picker.Model = new PickerModelDelegate (this);
</code></pre>
</li>
</ol>
<p>完整的代码可以在<code>PickerViewApp</code>项目中找到。</p>
<h2 id="它是如何工作的-29">它是如何工作的...</h2>
<p><code>UIPickerViewModel</code> 类在 <code>Objective-C</code> 中不存在。MonoTouch 提供了这个类,作为原生协议 <code>UIPickerViewDataSource</code> 和 <code>UIPickerViewDelegate</code> 的包装器,并包含这两个类的所有方法,以便我们重写。这非常有帮助,因为我们只需要实现和分配一个类,而不是两个类来为我们的选择视图。这两个协议同时作为类在 MonoTouch 中可用。</p>
<p>在构造函数中,我们初始化将包含要显示在选择器中的数据的列表。我们需要重写的四个类负责显示数据:</p>
<ol>
<li>
<p><code>int GetComponentCount (UIPickerView picker):</code> 这个方法返回我们想要选择视图显示的列数。</p>
</li>
<li>
<p><code>int GetRowsInComponent (UIPickerView picker, int component):</code> 这个方法返回每个组件将显示的行数。</p>
</li>
<li>
<p><code>string GetTitle (UIPickerView picker, int row, int component):</code> 这个方法返回每一行的文本。</p>
</li>
<li>
<p><code>void Selected (UIPickerView picker, int row, int component):</code> 这个方法返回当用户从选择视图的任何组件/行组合中选择项目时要采取的操作。</p>
</li>
</ol>
<p>我们使用在构造函数中分配的列表来显示数据。例如,<code>GetTitle</code> 方法实现如下:</p>
<pre><code class="language-swift">switch (component){
case 0:
return this.transportList;
case 1:
return this.distanceList;
default:
return this.unitList;
}
</code></pre>
<p>当我们运行应用程序并从选择器中选择任何内容时,结果将类似于以下截图:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_05_01.jpg"></p>
<h2 id="更多内容-16">更多内容...</h2>
<p>我们可以通过调用方法 <code>Select (int, int, bool)</code> 来程序化选择选择视图的初始选择。前两个参数分别表示行和组件索引,而 <code>bool</code> 参数切换选择动画。使用此方法时需要注意的唯一一点是,我们必须在分配选择器的 <code>Model</code> 属性之后调用它。否则将发生异常。</p>
<h3 id="更多关于-uipickerview-定制的信息">更多关于 UIPickerView 定制的信息</h3>
<p>除了提供的选项外,我们还可以设置每个组件的宽度。为此,我们重写 <code>GetComponentWidth (UIPickerView, int)</code> 方法,它返回一个表示每个组件宽度的浮点数。</p>
<p>我们还可以通过重写 <code>GetView(UIPickerView, int, int, UIView)</code> 方法来设置自定义视图作为选择视图中的项目,而不是纯文本。这可以通过返回我们想要在 <code>UIPickerView</code> 控制中的每个位置显示的视图来实现。</p>
<h3 id="日期和时间选择">日期和时间选择</h3>
<p>有一个名为 <code>UIDatePicker</code> 的控件,它与 <code>UIPickerView</code> 类似,专门用于显示和选择日期和时间值。请注意,尽管它的用户界面与选择视图相同,但它并不继承 <code>UIPickerView</code> 类。它只是使用它的一个实例作为子视图。</p>
<h2 id="相关内容-9">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>在表格中显示数据</em></li>
</ul>
<h1 id="在表格中显示数据">在表格中显示数据</h1>
<p>在本食谱中,我们将学习如何使用 <code>UITableView</code> 类来显示数据。</p>
<h2 id="准备工作-34">准备工作</h2>
<p><code>UITableView</code> 类以及 <code>UITableViewCell</code> 对象提供了一种在屏幕上以多行但单列的形式显示数据的接口。要开始,请在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>TableViewApp.</code></p>
<h2 id="如何做到这一点-7">如何做到这一点...</h2>
<ol>
<li>
<p>将带有控制器的视图添加到项目中,并将其命名为 <code>TableController</code>。</p>
</li>
<li>
<p>将 <code>TableController</code> 类的继承从 <code>UIViewController</code> 更改为 <code>UITableViewController:</code></p>
<pre><code class="language-swift">public partial class TableController : UITableViewController
</code></pre>
</li>
<li>
<p>在 Interface 中打开 <code>TableController.xib</code> 文件。</p>
</li>
<li>
<p>删除文档的 <code>UIView</code>,并在其位置添加一个 <code>UITableView</code>。</p>
</li>
<li>
<p>将 <code>TableController</code> 的输出端口 <code>view</code> 连接到添加的表格视图。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在 MonoDevelop 中,在 <code>TableController</code> 类内部创建以下嵌套类:</p>
<pre><code class="language-swift">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;
return cell;
}
}
</code></pre>
</li>
<li>
<p>重写控制器的 <code>ViewDidLoad</code> 方法,并添加以下代码行:</p>
<pre><code class="language-swift">this.TableView.Source = new TableSource ();
</code></pre>
</li>
</ol>
<p>完整的代码可以在 <code>TableViewApp</code> 项目中找到。</p>
<h2 id="它是如何工作的-30">它是如何工作的...</h2>
<p>我们创建的嵌套类充当 <code>UITableView</code> 的数据源。它继承自 MonoTouch 的 <code>UITableViewSource</code> 类:</p>
<pre><code class="language-swift">private class TableSource : UITableViewSource
</code></pre>
<h3 id="注意-27">注意</h3>
<p>与之前讨论的示例中的 <code>UIPickerView</code> 类似,<code>UITableViewSource</code> 类在 <code>Objective-C</code> 中不存在。它仅仅是 MonoTouch 提供的围绕 <code>UITableViewDelegate</code> 和 <code>UITableViewSource</code> 协议的包装对象。</p>
<p>在其构造函数中,我们初始化两个变量。一个字符串将作为单元格的标识符,以及一个用于数据源的通用 <code>Dictionary</code>。</p>
<pre><code class="language-swift">this.cellID = "cellIdentifier";
this.tableData = new Dictionary<int, string> () {
{0, "Music"},
{1, "Videos"},
{2, "Images"}
};
</code></pre>
<p>要使 <code>TableSource</code> 类工作,我们需要重写两个方法。第一个方法名为 <code>RowsInSection</code>,它返回表格应显示的行数。在这里,我们返回数据源对象中的项目数量:</p>
<pre><code class="language-swift">return this.tableData.Count;
</code></pre>
<p>第二个方法 <code>GetCell</code> 返回将在表格中显示的 <code>UITableViewCell</code> 对象。</p>
<h3 id="注意-28">注意</h3>
<p><code>UITableViewCell</code> 类代表一行,并在 <code>UITableView</code> 中管理其内容。</p>
<p>为了更高效,表格视图在需要时创建其单元格对象。因此,我们需要通过其 <code>DequeueReusableCell</code> 方法从表格中获取之前使用的 <code>UITableViewCell</code>:</p>
<pre><code class="language-swift">UITableViewCell cell = tableView.DequeueReusableCell (this.cellID);
</code></pre>
<p>如果不存在特定单元格标识符的单元格,该方法将返回 <code>null</code>。因此,我们创建将要使用的单元格:</p>
<pre><code class="language-swift">cell = new UITableViewCell (UITableViewCellStyle.Default, this.cellID);
</code></pre>
<p>然后,我们分配特定单元格将显示的文本并返回它:</p>
<pre><code class="language-swift">cell.TextLabel.Text = this.tableData;
return cell;
</code></pre>
<p>默认情况下,<code>UITableViewCell</code> 类包含两个标签,可以用来显示文本。主标签可以通过 <code>TextLabel</code> 属性访问,次要标签可以通过 <code>DetailTextLabel</code> 属性访问。请注意,当使用具有 <code>Default</code> 风格的单元格时,<code>DetailTextLabel</code> 属性不能使用,并将返回 <code>null</code>。</p>
<h2 id="还有更多-18">还有更多...</h2>
<p>为了在用户选择特定行时提供功能,我们需要覆盖作为<code>UITableViewSource</code>的类的<code>RowSelected</code>属性。默认情况下,当用户点击一行时,单元格会以蓝色突出显示以表示选中。要取消选中行,我们使用<code>UITableView.DeselectRow(NSIndexPath, bool)</code>方法:</p>
<pre><code class="language-swift">public override void RowSelected (UITableView tableView, NSIndexPath indexPath){
tableView.DeselectRow (indexPath, true);
}
</code></pre>
<h3 id="uitableview样式"><code>UITableView</code>样式</h3>
<p><code>UITableView</code>可以以两种不同的样式创建。默认样式是<code>Plain</code>。另一种可以使用的样式是<code>Grouped</code>样式。这种样式在许多 iOS 原生应用中都被使用,例如<code>Settings</code>应用。</p>
<p>此外,<code>UITableView</code>支持显示分为不同部分的数据。如果我们想使用不同的部分,我们必须在<code>RowsInSection</code>覆盖中明确返回每个部分将有的行数。</p>
<h2 id="参见-26">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>提供列表</em></p>
</li>
<li>
<p><em>自定义行</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第三章,用户界面:视图控制器:</p>
<ul>
<li><em>创建</em>一个<em>表格控制器</em></li>
</ul>
<h1 id="自定义行"><em>自定义行</em></h1>
<p>在这个菜谱中,我们将介绍一些可用于自定义表格单元格内容显示的不同选项。</p>
<h2 id="准备工作-35">准备工作</h2>
<p>以与上一个菜谱中创建项目相同的方式在 MonoDevelop 中创建一个新的项目。将其命名为<code>CustomRowsApp.</code></p>
<h2 id="如何做-8">如何做...</h2>
<ol>
<li>
<p>将上一个任务中的项目中的<code>TableSource</code>类复制并粘贴到<code>TableController</code>类内部。</p>
</li>
<li>
<p>在<code>GetCell</code>覆盖中执行以下更改:</p>
<pre><code class="language-swift">int rowIndex = indexPath.Row;
string cellID = this.tableData;
UITableViewCell cell = tableView.DequeueReusableCell (cellID);
if (null == cell){
cell = new UITableViewCell (this.cellStyles, cellID);
}
cell.TextLabel.Text = this.tableData;
if (rowIndex > 0){
cell.DetailTextLabel.Text = String.Format ("Details for {0}", cellID);
}
return cell;
</code></pre>
</li>
<li>
<p>删除<code>cellID</code>字段并添加一个新的:</p>
<pre><code class="language-swift">private Dictionary<int, UITableViewCellStyle> cellStyles;
</code></pre>
</li>
<li>
<p>在构造函数中初始化,如下所示:</p>
<pre><code class="language-swift">this.cellStyles = new Dictionary<int, UITableViewCellStyle>() {
{0, UITableViewCellStyle.Default},
{1, UITableViewCellStyle.Subtitle},
{2, UITableViewCellStyle.Value1},
{3, UITableViewCellStyle.Value2}
};
</code></pre>
</li>
<li>
<p>在数据源对象中添加另一个<code>KeyValuePair</code>:</p>
<pre><code class="language-swift">{3, "Recordings"}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。输出应该类似于以下内容,如以下截图所示:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_05_02.jpg"></p>
<h2 id="它是如何工作的-31">它是如何工作的...</h2>
<p>一个表格单元格可以有四种不同的单元格样式,这些样式由<code>UITableViewCellStyle</code>枚举表示。其值如下:</p>
<ol>
<li>
<p><code>默认:</code> 这是默认单元格样式。只能使用<code>TextLabel</code>属性来显示文本。</p>
</li>
<li>
<p><code>副标题:</code> 这是一种提供<code>DetailTextLabel</code>作为<code>TextLabel</code>副标题的样式。</p>
</li>
<li>
<p><code>Value1:</code> 这是显示<code>TextLabel</code>和<code>DetailTextLabel</code>文本大小相同、颜色不同且对齐到单元格两侧的样式。</p>
</li>
<li>
<p><code>Value2:</code> 这是显示<code>TextLabel</code>文本比<code>DetailTextLabel</code>文本小的样式。这种样式在原生的<code>Contacts</code>应用中的联系人详情屏幕中使用。</p>
</li>
</ol>
<p>为了方便使用所有可用的样式,我们已经在<code>Dictionary:</code>中添加了<code>UITableViewCellStyle</code>枚举的所有值:</p>
<pre><code class="language-swift">private Dictionary<int, UITableViewCellStyle> cellStyles;
</code></pre>
<p>现在我们使用了不同的单元格样式,因此需要为每个字符串提供一个单元格标识符。为了避免在类中声明另一个列表或更多字段,我们出于这个原因使用数据源:</p>
<pre><code class="language-swift">int rowIndex = indexPath.Row;
string cellID = this.tableData;
UITableViewCell cell = tableView.DequeueReusableCell (cellID);
</code></pre>
<p>要为每个单元格创建具有特定样式的单元格,我们根据当前行从 <code>cellStyles</code> 字段中提取 <code>UITableViewCellStyle</code> 值:</p>
<pre><code class="language-swift">cell = new UITableViewCell (this.cellStyles, cellID);
</code></pre>
<p>要为每个单元格设置 <code>DetailTextLabel</code> 文本,我们只需确保我们不是在具有 <code>Default</code> 样式的单元格上设置它,如本例中的第一个:</p>
<pre><code class="language-swift">if (rowIndex > 0){
cell.DetailTextLabel.Text = String.Format ("Details for {0}", cellID);
}
</code></pre>
<h2 id="还有更多-19">还有更多...</h2>
<p>可以在 <code>UITableViewCell</code> 中进行进一步的自定义。单元格包含的所有视图,包括 <code>TextLabel</code> 和 <code>DetailTextLabel</code>,都是单元格视图的子视图,该视图通过其 <code>ContentView</code> 属性公开。我们可以创建自定义视图并将其作为子视图添加到其中。</p>
<h3 id="uitableviewcell-类的其他有用属性"><code>UITableViewCell</code> 类的其他有用属性</h3>
<p>除了在默认标签中添加文本外,<code>UITableViewCell</code> 还包含一些其他属性,我们可以设置它们的值,以在单元格中添加更多默认项:</p>
<ul>
<li>
<p><code>ImageView:</code> 这个属性接受一个 <code>UIImageView</code>。我们可以用它来在单元格的左侧显示一个图像。</p>
</li>
<li>
<p><code>AccessoryView:</code> 这个属性接受任何 <code>UIView</code> 实例。它的默认位置是单元格的右侧,在单元格的 <code>Accessory</code> 位置,位于单元格的右侧。</p>
</li>
<li>
<p><code>Accessory:</code> 这个属性接受 <code>UITableViewCellAccessory</code> 类型的值。它为单元格的附件提供预定义视图,例如 <code>DetailDisclosureButton</code> 或 <code>Checkmark</code>。</p>
</li>
</ul>
<h2 id="参见-27">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>在表格中显示数据</em></p>
</li>
<li>
<p><em>在表格中编辑数据</em></p>
</li>
</ul>
<h1 id="编辑表格删除行">编辑表格:删除行</h1>
<p>在这个菜谱中,我们将讨论如何从 <code>UITableView</code> 中删除行,并提供适当的用户反馈。</p>
<h2 id="准备工作-36">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>EditingTableDataApp</code>。</p>
<h2 id="如何操作-17">如何操作...</h2>
<ol>
<li>
<p>将视图控制器添加到项目中,并将其转换为本章中 <em>在表格中显示数据</em> 部分所述的 <code>UITableViewController</code>,并将其命名为 <code>TableController</code>。</p>
</li>
<li>
<p>在 <code>AppDelegate</code> 类中添加一个 <code>UINavigationController</code>。初始化它,将 <code>TableController</code> 设置为其根控制器:</p>
<pre><code class="language-swift">TableController tableController;
UINavigationController navController;
//…
this.tableController = new TableController();
this.navController = new UINavigationController(this.tableController);
window.RootViewController = this.navController;
</code></pre>
</li>
<li>
<p>在 MonoDevelop 中,在 <code>TableController</code> 类中添加三个字段:<code>List<string> tableData</code>、<code>UIBarButtonItem buttonEdit</code> 和 <code>UIBarButtonItem buttonDone</code>。重写类的 <code>ViewDidLoad</code> 方法,如下所示:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>为表格创建适当的表格视图源,它在构造函数中接受 <code>tableData</code> 泛型 <code>List</code> 作为参数。创建处理方法 <code>ButtonEdit_Clicked</code>,并在其中输入以下代码:</p>
<pre><code class="language-swift">this.TableView.SetEditing (true, true);
this.NavigationItem.SetRightBarButtonItem (this.buttonDone, true);
</code></pre>
</li>
<li>
<p>创建处理方法 <code>ButtonDone_Clicked</code>,并在其中输入以下代码:</p>
<pre><code class="language-swift">this.TableView.SetEditing (false, true);
this.NavigationItem.SetRightBarButtonItem (this.buttonEdit, true);
</code></pre>
</li>
<li>
<p>最后,重写表格源代码中的 <code>CommitEditingStyle</code> 方法:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
</ol>
<h3 id="它是如何工作的-32">它是如何工作的...</h3>
<p>在这里我们首先使用导航栏添加按钮来处理表格的编辑模式。当视图加载时,我们使用 <code>SetRightBarButtonItem</code> 方法设置编辑按钮:</p>
<pre><code class="language-swift">this.NavigationItem.SetRightBarButtonItem (this.buttonEdit, false);
</code></pre>
<p>在 <code>ButtonEdit_Clicked</code> 方法内部,我们将表格设置为编辑模式。然后,我们更改导航栏中的按钮,以便用户可以退出编辑模式:</p>
<pre><code class="language-swift">this.TableView.SetEditing (true, true);
this.NavigationItem.SetRightBarButtonItem (this.buttonDone, true);
</code></pre>
<p><code>SetEditing</code> 方法用于启用或禁用表格的编辑模式。当表格处于编辑模式时,每个单元格左侧会出现一个带有减号 (-) 符号的圆形红色图标。当用户点击该图标时,单元格中会出现一个确认的红色 <strong>删除</strong> 按钮。要实际删除用户通过点击 <strong>删除</strong> 按钮确认删除的行,我们必须在表格源中实现 <code>CommitEditingStyle</code> 方法:</p>
<pre><code class="language-swift">if (editingStyle == UITableViewCellEditingStyle.Delete){
this.tableData.RemoveAt (rowIndex);
tableView.DeleteRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Left);
}
</code></pre>
<p>我们需要做的第一件事是检查该方法是否是由于用户点击 <code>删除</code> 按钮而调用的。这是通过检查 <code>editingStyle</code> 参数来完成的,如代码中所突出显示的。然后,我们使用 <code>DeleteRows</code> 方法从数据源和表格中删除行的数据。</p>
<h3 id="更多内容-17">更多内容...</h3>
<p>表视图为用户提供了另一种更直接的方式来删除行。这可以通过在想要删除的单元格上滑动手指来实现。在这种情况下,只会显示 <strong>删除</strong> 按钮。我们仍然需要在表格源中实现 <code>CommitEditingStyle</code> 方法,以实际从表格中删除行。</p>
<p>在这个菜谱中使用导航控制器并不意味着它是实现删除行功能的唯一方法。然而,它是一种在现实世界的应用场景中将被广泛使用的视图控制器组合。</p>
<h4 id="行删除动画">行删除动画</h4>
<p>在 <code>DeleteRows</code> 方法中使用的 <code>UITableViewRowAnimation</code> 枚举表示将要被删除的行的动画类型。它包含各种值(Left,<code>Right</code>,Middle,Fade,Top,Bottom 和 <code>None</code>),用于动画行。请注意,为了实现最佳效果,应根据行在数据源中的位置使用动画类型。例如,如果将要删除的行是表格中的最后一行,最好使用 <code>UITableViewRowAnimation.Bottom</code>,这样应该删除的行会向下移动。如果将要删除的行是数据源中的第一行,最好使用 <code>UITableViewRowAnimation.Top</code>,这样应该删除的行会向上移动。其余选项更适合中间行,即在第一行和最后一行之间。</p>
<h3 id="相关内容-10">相关内容</h3>
<p>在本章中:</p>
<ul>
<li>
<p><em>在表格中显示数据</em></p>
</li>
<li>
<p><em>编辑表格:插入行</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第三章,用户界面:视图控制器:</p>
<ul>
<li><em>在不同视图控制器之间导航</em></li>
</ul>
<h1 id="编辑表格插入行">编辑表格:插入行</h1>
<p>在这个菜谱中,我们将学习如何为用户在 <code>UITableView</code> 中提供插入行的能力。</p>
<h2 id="准备工作-37">准备工作</h2>
<p>对于这个任务,我们将使用上一个任务中的项目 <code>EditingTableDataApp</code>。在 MonoDevelop 中打开它。</p>
<h2 id="如何操作-18">如何操作...</h2>
<ol>
<li>
<p>在 <code>TableController</code> 类中添加另一个 <code>UIBarButtonItem</code> 字段,并在 <code>ViewDidLoad</code> 方法中初始化它:</p>
<pre><code class="language-swift">this.buttonAdd = new UIBarButtonItem (UIBarButtonSystemItem.Add, this.ButtonAdd_Clicked);
</code></pre>
</li>
<li>
<p>添加处理方法 <code>ButtonAdd_Clicked:</code></p>
<pre><code class="language-swift">private void ButtonAdd_Clicked (object sender, EventArgs e){
this.tableData.Add ("Recordings");
this.TableView.ReloadData ();
}
</code></pre>
</li>
<li>
<p>在 <code>ButtonEdit_Clicked</code> 方法中添加以下行:</p>
<pre><code class="language-swift">this.NavigationItem.SetLeftBarButtonItem (this.buttonAdd, true);
</code></pre>
</li>
<li>
<p>还需要在<code>ButtonDone_Clicked</code>方法中添加以下行:</p>
<pre><code class="language-swift">this.NavigationItem.SetLeftBarButtonItem (null, true);
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>编辑</strong>按钮,你会看到添加按钮出现在导航栏的左侧。点击它,你会看到新行被添加到表格中。</p>
</li>
</ol>
<h2 id="它是如何工作的-33">它是如何工作的...</h2>
<p>这里使用了一个系统默认的<strong>添加</strong>按钮。当用户点击按钮时,表格中会添加一行。按钮作为导航栏中的左侧按钮添加到编辑按钮的<code>Clicked</code>处理程序中。要移除它,我们调用相同的函数,将<code>UIBarButtonItem</code>参数传递为<code>null</code>值,在<code>ButtonDone_Clicked</code>方法中:</p>
<pre><code class="language-swift">this.NavigationItem.SetLeftBarButtonItem (this.buttonAdd, true);
this.NavigationItem.SetLeftBarButtonItem (null, true);
</code></pre>
<p>这样,当用户禁用编辑模式时,<code>添加</code>按钮将消失。接下来,我们只需将数据添加到数据源中,并强制表格重新加载,具体操作如下:</p>
<pre><code class="language-swift">this.tableData.Add ("Recordings");
this.TableView.ReloadData ();
</code></pre>
<h2 id="还有更多-20">还有更多...</h2>
<p>这是在表格中插入行最简单的方法。但这并不是最有效的方法。调用<code>UITableView.ReloadData</code>方法会导致<code>UITableView</code>重新加载所有内容,如果表格包含大量行,这将降低性能。为了避免这种情况,您可以将此示例中的<code>ReloadData</code>调用替换为以下行:</p>
<pre><code class="language-swift">this.TableView.InsertRows (new NSIndexPath[] { NSIndexPath.FromRowSection(this.tableData.Count - 1, 0) }, UITableViewRowAnimation.Right);
</code></pre>
<p><code>InsertRows</code>方法只会重新加载表格内容中所需的部分,在本例中是数据源中的最后一个项目。注意,使用此方法,我们还可以指定单元格将被插入表格的哪个部分。</p>
<h3 id="行重新排序">行重新排序</h3>
<p><code>UITableView</code>类的另一个有用功能是行重新排序。为了演示这一点,在表源中添加以下方法重写:</p>
<pre><code class="language-swift">public override bool CanMoveRow (UITableView tableView, NSIndexPath indexPath){
return true;
}
public override void MoveRow (UITableView tableView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath){
string itemToMove = this.tableData;
this.tableData.Remove (itemToMove);
this.tableData.Insert (destinationIndexPath.Row, itemToMove);
}
</code></pre>
<p>在<code>CanMoveRow</code>方法中返回<code>true</code>可以启用所有单元格的重新排序。这通过在每个单元格右侧显示的抓手外观图标来表示。当用户触摸并拖动图标时,单元格可以被移动到另一个位置。实际的重新排序发生在<code>MoveRow</code>方法中。需要做的只是使用<code>sourceIndexPath</code>和<code>destinationIndexPath</code>参数,在数据源中移除并重新插入项目到期望的索引。</p>
<h2 id="参见-28">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>在表格中显示数据</em></p>
</li>
<li>
<p><em>编辑表格:删除行</em></p>
</li>
</ul>
<h1 id="表索引">表索引</h1>
<p>在本教程中,我们将学习如何在表格中提供一个索引,使用户能够更快地浏览<code>UITableView</code>中的行。</p>
<h2 id="准备工作-38">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>TableIndexApp</code>。添加一个<code>UITableViewController</code>,如本章前面的任务所示,并实现<code>TableSource</code>类。</p>
<h2 id="如何操作-19">如何操作...</h2>
<p>在表源类中,重写并实现以下方法:</p>
<pre><code class="language-swift">public override int NumberOfSections (UITableView tableView){
return this.tableData.Count;
}
public override string TitleForHeader (UITableView tableView, int section){
return Convert.ToString (this.tableData);
}
public override string[] SectionIndexTitles (UITableView tableView){
return this.tableData.Select (s => Convert.ToString (s)).Distinct ().ToArray ();
}
</code></pre>
<h2 id="它是如何工作的-34">它是如何工作的...</h2>
<p>在此任务中创建的表源包含许多不同的部分。为了简单起见,每个部分包含一行。<code>NumberOfSections</code>方法返回表格将显示的总部分数。</p>
<p>要为每个部分设置标题,我们必须重写<code>TitleForHeader</code>方法:</p>
<pre><code class="language-swift">public override string TitleForHeader (UITableView tableView, int section){
return Convert.ToString (this.tableData);
}
</code></pre>
<p>此实现返回数据源中每个字符串的第一个字母。为了提供索引,我们重写了<code>SectionIndexTitles</code>方法:</p>
<pre><code class="language-swift">public override string[] SectionIndexTitles (UITableView tableView){
return this.tableData.Select (s => Convert.ToString (s)).Distinct ().ToArray ();
}
</code></pre>
<p>这里,它返回数据源中每个项目的第一个字母。此项目的结果将类似于以下:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_05_03.jpg"></p>
<p>当用户触摸索引上的任何位置时,表格视图将自动滚动到该特定部分。</p>
<h2 id="更多内容-18">更多内容...</h2>
<p>应在具有<code>Plain</code>样式的表格上应用索引。在设置了<code>Grouped</code>样式的表格上应用索引是不推荐的,因为索引将不易区分。</p>
<p>一个具有表格索引的本地 iOS 应用程序的好例子可以在本地的<code>Contacts</code>应用程序中找到。</p>
<h2 id="参见-29">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>在表格中显示数据</em></p>
</li>
<li>
<p><em>搜索数据</em></p>
</li>
</ul>
<h1 id="搜索数据">搜索数据</h1>
<p>在本教程中,我们将学习如何为表格中的内容提供搜索功能。</p>
<h2 id="准备工作-39">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>SearchTableApp</code>。添加一个<code>UITableViewController</code>,并将其命名为<code>TableController</code>。</p>
<h2 id="如何实现-3">如何实现...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开<code>TableController.xib</code>文件。</p>
</li>
<li>
<p>在<code>UITableView</code>中添加<strong>搜索栏</strong>和<strong>搜索显示控制器</strong>。请注意,在此操作之后,将自动创建并连接一些输出。我们需要大多数它们,所以我们保留它们原样并保存文档。</p>
</li>
<li>
<p>回到 MonoDevelop,实现一个将作为搜索显示控制器委托对象的类:</p>
<pre><code class="language-swift">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;
}
}
</code></pre>
</li>
<li>
<p>重写<code>ViewDidLoad</code>方法,并在其中分配源和委托对象:</p>
<pre><code class="language-swift">this.TableView.Source = new TableSource (this);
this.SearchDisplayController.SearchResultsSource = new TableSource(this);
this.SearchDisplayController.Delegate = new SearchDelegate(this);
</code></pre>
</li>
<li>
<p>你可以在<code>SearchTableApp</code>项目中找到完整的代码。结果将是表格上方的常见 iOS 搜索栏,类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_05_04.jpg"></p>
<h2 id="工作原理-5">工作原理...</h2>
<p><code>UISearchDisplayController</code>类提供了一个方便的搜索数据的方法。它包含一个<code>UISearchBar</code>,用于接受用户的输入,以及一个<code>UITableView</code>,用于显示结果。在我们在视图控制器中添加搜索控制器后,我们可以通过该控制器的<code>SearchDisplayController</code>属性访问它。为了触发结果表格,我们必须实现<code>UISearchDisplayDelegate</code>并重写其<code>ShouldReloadForSearchString</code>,它返回一个布尔值:</p>
<pre><code class="language-swift">private class SearchDelegate : UISearchDisplayDelegate
</code></pre>
<p>在<code>ShouldReloadForSearchString</code>方法重写中,我们根据其<code>forSearchString</code>参数搜索数据源,将过滤后的结果保存在一个新的数据源中:</p>
<pre><code class="language-swift">this.parentController.filterDataList = this.parentController.tableData
.Where (s => s.ToLower ().Contains (forSearchString.ToLower ()))
.ToList ();
</code></pre>
<p>然后,我们按字母顺序对结果进行排序并返回<code>true</code>,这样搜索控制器的表格就会重新加载数据:</p>
<pre><code class="language-swift">this.parentController.filterDataList.Sort (delegate( string firstStr, string secondStr) {
return firstStr.CompareTo (secondStr);
});
return true;
</code></pre>
<p>搜索控制器的表格视图也需要一个源对象。在这个例子中,我们将其设置为为我们表格创建的相同对象:</p>
<pre><code class="language-swift">this.TableView.Source = new TableSource (this);
this.SearchDisplayController.SearchResultsSource = new TableSource(this);
</code></pre>
<p>由于我们使用的是相同对象的实例,我们需要对其进行一些修改以根据调用它的表格显示数据。例如,<code>RowsInSection</code>方法看起来如下:</p>
<pre><code class="language-swift">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;
}
}
</code></pre>
<p>这样,我们就根据表格调用该方法返回行数。同样,我们还需要在<code>GetCell</code>方法内部设置每个单元格的文本标签:</p>
<pre><code class="language-swift">if (tableView.Equals (this.parentController.TableView)){
cell.TextLabel.Text = this.parentController.tableData;
} else{
cell.TextLabel.Text = this.parentController.filterDataList;
}
</code></pre>
<h2 id="还有更多-21">还有更多...</h2>
<p>当用户点击搜索栏时,键盘出现,设置搜索控制器为活动状态。要使其不活动,我们可以挂钩到搜索栏的<code>SearchButtonClicked</code>事件。当用户点击键盘上的<strong>搜索</strong>按钮时,此事件将被触发:</p>
<pre><code class="language-swift">this.SearchDisplayController.SearchBar.SearchButtonClicked += delegate {
this.SearchDisplayController.SetActive(false, true);
};
</code></pre>
<p><code>SetActive</code>方法是我们用来启用或禁用搜索控制器的。</p>
<h3 id="为其他控制器提供搜索功能">为其他控制器提供搜索功能。</h3>
<p>虽然这个例子在<code>UITableViewController</code>中使用了一个<code>UISearchDisplayController</code>,但这并不意味着它是唯一的使用方式。我们可以使用任何我们想要的<code>UIViewController</code>类型的搜索控制器。在这种情况下,我们唯一需要做的额外事情是将搜索控制器的<code>SearchContentsController</code>属性设置为它所属的视图控制器。当我们在<code>UITableViewController</code>中添加<code>UISearchDisplayController</code>时,Interface Builder 会自动处理这一点,但不是在其他控制器中。</p>
<h2 id="参见-30">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>在表格中显示数据</em></p>
</li>
<li>
<p><em>表格索引</em></p>
</li>
</ul>
<h1 id="创建简单的网页浏览器">创建简单的网页浏览器</h1>
<p>在这个菜谱中,我们将讨论使用<code>UIWebView</code>类显示在线内容。</p>
<h2 id="准备工作-40">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>WebBrowserApp</code>。</p>
<h2 id="如何实现-4">如何实现...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开<code>MainController.xib</code>文件,并在主视图中添加一个<code>UIWebView</code>对象。</p>
</li>
<li>
<p>创建并连接一个名为<code>webView</code>的出口。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在<code>MainController</code>类中重写<code>ViewDidAppear</code>方法:</p>
<pre><code class="language-swift">public override void ViewDidAppear (bool animated){
NSUrl url = new NSUrl ("http://software.tavlikos.com");
NSUrlRequest urlRequest = new NSUrlRequest (url);
this.webView.LoadRequest (urlRequest);
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。观察屏幕上网站加载的情况!</p>
</li>
</ol>
<h2 id="它是如何工作的-35">它是如何工作的...</h2>
<p><code>UIWebView</code>类是 iOS SDK 的网页浏览器控件。要加载网页内容,我们只需调用其<code>LoadRequest</code>方法,该方法接受一个类型为<code>NSUrlRequest</code>的参数。<code>NSUrlRequest</code>对象包含我们想要它加载的 URL:</p>
<pre><code class="language-swift">NSUrl url = new NSUrl ("http://software.tavlikos.com");
</code></pre>
<h2 id="还有更多-22">还有更多...</h2>
<p><code>UIWebView</code>类包含一些非常有用的事件:</p>
<ul>
<li>
<p><code>LoadStarted:</code> 当控件开始加载内容时触发</p>
</li>
<li>
<p><code>LoadFinished:</code> 当内容成功加载完成时触发</p>
</li>
<li>
<p><code>LoadError:</code> 当内容加载失败时触发</p>
</li>
</ul>
<h3 id="内容缩放">内容缩放</h3>
<p><code>UIWebView</code>类的另一个重要功能是内容的自动缩放。可以通过将其<code>ScalePageToFit</code>属性设置为<code>true</code>来激活它。</p>
<h2 id="参见-31">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示本地内容</em></p>
</li>
<li>
<p><em>显示格式化文本</em></p>
</li>
<li>
<p><em>显示文档</em></p>
</li>
</ul>
<h1 id="显示本地内容">显示本地内容</h1>
<p>在这个菜谱中,我们将讨论显示本地 HTML 文件。</p>
<h2 id="准备工作-41">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>LocalContentApp</code>。</p>
<h2 id="如何实现-5">如何实现...</h2>
<ol>
<li>
<p>在<code>MainController</code>的主视图中添加一个<code>UIWebView</code>,并保存文档。</p>
</li>
<li>
<p>在项目中添加一个新的文件夹,并将其命名为<code>html_content</code>。</p>
</li>
<li>
<p>通过 MonoDevelop 将您的内容添加到该文件夹中。别忘了将每个文件的 <strong>构建操作</strong> 设置为 <strong>内容</strong>。</p>
</li>
<li>
<p>在 <code>MainController</code> 类中重写 <code>ViewDidAppear</code> 方法,并输入以下代码:</p>
<pre><code class="language-swift">NSUrl fileUrl = NSUrl.FromFilename ( "./html_content/T-Shirts.html");
NSUrlRequest urlRequest = new NSUrlRequest (fileUrl);
this.webView.ScalesPageToFit = false;
this.webView.LoadRequest (urlRequest);
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>查看屏幕上显示的 HTML 内容。</p>
</li>
<li>
<p>放大以查看更大的内容,就像您在线内容中做的那样。</p>
</li>
</ol>
<h2 id="工作原理-6">工作原理...</h2>
<p>显示本地内容的过程与显示在线内容相同。<code>NSUrl</code> 类有一个静态方法,根据文件路径创建一个实例:</p>
<pre><code class="language-swift">NSUrl fileUrl = NSUrl.FromFilename ("./html_content/T-Shirts.html");
</code></pre>
<h2 id="还有更多-23">还有更多...</h2>
<p><code>UIWebView</code> 是一个非常强大的控件。它可以用来显示 iOS 上 Safari 浏览器可以显示的一切。这包括 HTML、纯文本、图片和 PDF 文档。</p>
<h3 id="在内容中导航">在内容中导航</h3>
<p>您还可以使用其 <code>GoBack()</code> 和 <code>GoForward()</code> 方法在 <code>UIWebView</code> 的历史记录中导航。</p>
<h3 id="uiwebview-支持的文件">UIWebView 支持的文件</h3>
<p><code>UIWebView</code> 控件也可以用来显示其他类型的文件。这些文件类型包括:</p>
<ul>
<li>
<p><strong>Excel (.xls)</strong></p>
</li>
<li>
<p><strong>Keynote (.key.zip)</strong></p>
</li>
<li>
<p><strong>数字文件 (.numbers.zip)</strong></p>
</li>
<li>
<p><strong>Pages (.pages.zip)</strong></p>
</li>
<li>
<p><strong>PDF (.pdf)</strong></p>
</li>
<li>
<p><strong>PowerPoint (.ppt)</strong></p>
</li>
<li>
<p><strong>Word (.doc)</strong></p>
</li>
<li>
<p><strong>富文本格式</strong> (.rtf)</p>
</li>
<li>
<p><strong>富文本格式目录 (.rtfd.zip)</strong></p>
</li>
<li>
<p><strong>Keynote '09 (.key)</strong></p>
</li>
<li>
<p><strong>Numbers '09 (.numbers)</strong></p>
</li>
<li>
<p><strong>Pages '09 (.pages)</strong></p>
</li>
</ul>
<h2 id="参见-32">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>创建一个简单的网页浏览器</em></p>
</li>
<li>
<p><em>显示文档</em></p>
</li>
</ul>
<h1 id="显示格式化文本">显示格式化文本</h1>
<p>在本章中,我们将学习如何使用 <code>UIWebView</code> 类来显示格式化文本。</p>
<h2 id="准备工作-42">准备工作</h2>
<p>在这个任务中,我们将处理之前讨论过的 <code>LocalContentApp</code> 项目。在 MonoDevelop 中打开它。</p>
<h2 id="如何操作-20">如何操作...</h2>
<ol>
<li>
<p>在 <code>ViewDidAppear</code> 方法中注释掉之前的代码,并添加以下代码:</p>
<pre><code class="language-swift">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);
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>观察 HTML 字符串的显示。</p>
</li>
</ol>
<h2 id="工作原理-7">工作原理...</h2>
<p>如同在 第二章 中讨论的,<em>用户界面:视图</em>,<code>UITextView</code> 可以用来显示大块文本并进行编辑,但它不能显示格式化文本。<code>UIWebView</code> 可以通过将我们的 HTML 格式化文本作为参数传递给 <code>LoadHtmlString</code> 方法来实现这一点:</p>
<pre><code class="language-swift">this.webView.LoadHtmlString (htmlString, null);
</code></pre>
<p>第二个参数的类型是 <code>NSUrl</code>。由于我们在代码中创建了 HTML 字符串,并且没有对其他文件的引用,所以我们不需要它,因此我们只需传递 <code>null</code>。</p>
<h2 id="还有更多-24">还有更多...</h2>
<p>如果我们想在 HTML 字符串内部引用外部文件,我们应该将 <code>LoadHtmlString</code> 的 <code>NSUrl</code> 参数设置为包含文件的路径,从而设置 HTML 的基本目录。例如,考虑以下 HTML 字符串,它引用了应用程序包中 <code>html_content</code> 文件夹内的文件:</p>
<pre><code class="language-swift">string htmlString = "<img style=\"width: 215px;\" src=\"tshirts_s.jpg\">";
</code></pre>
<p>如果我们要将其传递给 <code>LoadHtmlString</code> 来显示图片,我们还应该设置 <code>baseUrl</code> 参数:</p>
<pre><code class="language-swift">this.webView.LoadHtmlString (htmlString, new NSUrl ( "./html_content", true));
</code></pre>
<p><code>NSUrl</code> 构造函数的 <code>bool</code> 参数表示第一个参数的 URL 字符串是目录的路径,应该像目录一样处理。</p>
<h3 id="备注">备注</h3>
<p>虽然 <code>UIWebView</code> 可以显示各种内容,但不能用于编辑。</p>
<h3 id="允许特定的链接">允许特定的链接</h3>
<p><code>UIWebView</code> 还提供了对用户点击的链接如何处理的控制。为此,我们可以将其 <code>ShouldStartLoad</code> 属性分配一个处理程序。它接受 <code>UIWebLoaderControl</code> 类型的代理。</p>
<h2 id="参见-33">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>创建一个简单的网页浏览器</em></p>
</li>
<li>
<p><em>显示本地内容</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第二章, 用户界面:视图:</p>
<ul>
<li><em>显示和编辑文本</em></li>
</ul>
<h1 id="显示文档">显示文档</h1>
<p>在本配方中,我们将讨论如何使用 <code>QLPreviewController</code> 类轻松显示不同格式的各种文档。</p>
<h2 id="准备工作-43">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>DocumentPreviewApp</code>。添加一个带有控制器的视图,并将其命名为 <code>MainController</code>。</p>
<h2 id="如何做到这一点-8">如何做到这一点...</h2>
<ol>
<li>
<p>在 Interface Builder 中打开 <code>MainController.xib</code> 文件并添加一个 <code>UIButton</code>。</p>
</li>
<li>
<p>保存文档。</p>
</li>
<li>
<p>在项目中添加一个名为 <code>docs</code> 的文件夹,并将一些文档文件放入其中。项目 <code>DocumentPreviewApp</code> 包含三种不同的文档:一个 <code>PDF</code>、一个 <code>DOCX</code> 和一个 <code>XLSX</code>。</p>
</li>
<li>
<p>在 <code>MainController.cs</code> 文件中输入以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using MonoTouch.QuickLook;
</code></pre>
</li>
<li>
<p>在 <code>MainController</code> 类内部创建以下嵌套类:</p>
<pre><code class="language-swift">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;
}
}
</code></pre>
</li>
<li>
<p>在 <code>MainController</code> 的 <code>ViewDidLoad</code> 重写中输入以下代码:</p>
<pre><code class="language-swift">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);
};
</code></pre>
</li>
</ol>
<h2 id="它是如何工作的-36">它是如何工作的...</h2>
<p><code>QLPreviewController</code> 类提供了一种非常方便的方式,可以一次性显示多种文档格式。它是一个可以通过将其推入导航控制器堆栈或以模态方式显示的控制器。</p>
<p>为了定义我们想要显示的文档,我们必须创建一个 <code>QLPreviewControllerDataSource</code> 类并将其分配给其 <code>DataSource</code> 属性:</p>
<pre><code class="language-swift">private class PreviewDataSource : QLPreviewControllerDataSource
</code></pre>
<p><code>QLPreviewControllerDataSource</code> 包含两个我们需要重写的方法:<code>PreviewItemCount</code>,它返回控制器需要显示的项目数量,以及 <code>GetPreviewItem</code>,它返回实际的项目。此项目是 <code>QLPreviewItem</code> 类型,我们必须实现一个继承它的方法:</p>
<pre><code class="language-swift">private class PreviewItem : QLPreviewItem
</code></pre>
<p>在此类中,我们必须重写两个属性,这两个属性都代表要预览的项目信息。这些是 <code>ItemTitle</code> 和 <code>ItemUrl</code>。</p>
<p>当控制器调用 <code>PreviewItemCount</code> 方法并且返回一个大于 <code>1</code> 的数字时,它会添加一个带有两个箭头按钮的 <code>UIToolbar</code>,允许用户在文档之间导航。当调用 <code>GetPreviewItem</code> 方法时,它会将当前标题设置为 <code>ItemTitle</code> 属性,并根据 <code>ItemUrl</code> 属性加载文档。如果在此应用程序中点击按钮,结果将类似于以下内容:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_05_05.jpg"></p>
<p>此截图显示了导航到最后一个文档(<code>XLSX</code> 类型的文件)后的 <code>QLPreviewController</code>。</p>
<h2 id="更多-3">更多...</h2>
<p>控制器在其导航栏上包含一个系统默认的<strong>完成</strong>按钮。如果按下该按钮,控制器将自动关闭。我们可以通过其<code>WillDismiss</code>和/或<code>DidDismiss</code>事件来提供额外的行为。</p>
<h2 id="参见-34">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示本地内容</em></p>
</li>
<li>
<p><em>显示格式化文本</em></p>
</li>
</ul>
<h1 id="第六章网络服务">第六章:网络服务</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>消费网络服务</p>
</li>
<li>
<p>调用网络服务</p>
</li>
<li>
<p>消费 WCF 服务</p>
</li>
<li>
<p>读取 JSON 数据</p>
</li>
</ul>
<h1 id="简介-5">简介</h1>
<p>向用户提供在线信息是移动开发的关键部分。在本章中,我们将讨论开发与网络服务通信以提供信息的应用程序。我们将看到如何基于 SOAP 消费和调用网络服务,还将讨论如何使用 WCF 网络服务以及如何从网络服务器解析流行的 JSON 数据格式。</p>
<p>本章中的所有示例都使用与 Mono 框架一起提供的 xsp 轻量级网络服务器,因此无需在线上运行一个实时网络服务来使用提供的代码。</p>
<h1 id="消费网络服务">消费网络服务</h1>
<p>在本配方中,我们将学习如何在 MonoTouch 项目中使用 SOAP 网络服务。</p>
<h2 id="准备工作-44">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>WebServiceApp</code>。本章的代码包含一个名为<code>MTWebService</code>的网络服务项目。这就是将要使用的网络服务。</p>
<h2 id="如何操作-21">如何操作...</h2>
<ol>
<li>
<p>要使用<code>MTWebService</code>网络服务,我们需要一个网络服务器。Mono 框架提供了用于测试目的的 xsp 轻量级网络服务器。</p>
</li>
<li>
<p>打开一个终端,并输入以下命令以进入网络服务的目录,将<code><code_directory></code>替换为下载的代码所在的路径:</p>
<pre><code class="language-swift">cd <code_directory>/CH06_code/MTWebService/MTWebService
</code></pre>
</li>
<li>
<p>通过在提示符中输入<code>xsp4</code>来运行 xsp 网络服务器。你将看到类似以下输出的内容:</p>
<pre><code class="language-swift">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.
</code></pre>
</li>
<li>
<p>现在,我们需要在项目中添加对网络服务的引用。在<strong>解决方案</strong>面板中右键单击项目,然后选择<strong>添加 | 添加 Web 引用</strong>。</p>
</li>
<li>
<p>在将显示的对话框中,添加以下截图提供的信息:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_06_01.jpg"></p>
</li>
<li>
<p>在将引用添加到<strong>MTTestWebService</strong>网络服务后,在<code>MainController</code>的视图中添加一个按钮和一个标签。覆盖<code>MainController</code>类的<code>ViewDidLoad</code>方法,并在其中输入以下代码:</p>
<pre><code class="language-swift">this.buttonFetchData.TouchUpInside += delegate {
using (MTTestWebService webService = new MTTestWebService()){
this.lblMessage.Text = webService.GetMessage ("Hello Web Service!");
}
} ;
</code></pre>
</li>
<li>
<p>最后,为我们的网络服务命名空间提供一个<code>using</code>指令:</p>
<pre><code class="language-swift">using WebServiceApp.mtWebService;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮以调用网络服务,并注意标签中的输出消息。</p>
</li>
</ol>
<h2 id="工作原理-8">工作原理...</h2>
<p>MonoTouch 可以像.NET 桌面应用程序一样消费网络服务。<code>xsp</code>轻量级网络服务器在安装 Mono 框架时默认安装,这是 MonoTouch 安装的要求。在终端中运行不带任何参数的<code>xsp4</code>命令时,它默认将其基本目录设置为当前目录,并开始监听<code>8080</code>端口。如果网络服务器已启动,可以通过在浏览器中输入<code>http://localhost:8080/MTTestWebService.asmx</code>来查看网络服务描述:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_06_02.jpg"></p>
<p>需要在网络服务引用对话框的 <strong>Web 服务 URL</strong> 字段中输入的链接,可以通过点击服务描述页面上的 <strong>服务描述</strong> 链接,然后点击位于网络服务 WSDL 描述上方的 <strong>下载</strong> 链接来找到。</p>
<p>然后,我们将 <strong>框架</strong> 值设置为 <strong>.NET 2.0 Web 服务</strong>,并提供一个 <strong>引用</strong> 名称,该名称将反映网络引用的命名空间。为了在我们的代码中使用网络服务,我们实例化它,然后只需调用我们感兴趣的方法:</p>
<pre><code class="language-swift">this.lblMessage.Text = webService.GetMessage ("Hello Web Service!");
</code></pre>
<h2 id="更多内容-19">更多内容...</h2>
<p>除了使用本地托管网络服务外,互联网上还有许多示例网络服务。简单的搜索会产生许多结果。</p>
<h3 id="xsp-关闭">XSP 关闭</h3>
<p>要关闭 <code>xsp</code> 网络服务器,只需在执行它的终端中按 <em>回车键</em>。</p>
<h2 id="参见-35">参见</h2>
<p>在这一章中:</p>
<ul>
<li>
<p><em>调用网络服务</em></p>
</li>
<li>
<p><em>消费 WCF 服务</em></p>
</li>
</ul>
<h1 id="调用网络服务">调用网络服务</h1>
<p>在这个菜谱中,我们将讨论如何正确使用 MonoTouch 中的网络服务。</p>
<h2 id="准备工作-45">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>WebServiceApp2</code>。启动 <code>xsp</code> 网络服务器,并在项目中添加对 <code>TTestWebService</code> 网络服务的引用,具体操作如前一个任务所述。</p>
<h2 id="如何做到-3">如何做到...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个标签和一个按钮。</p>
</li>
<li>
<p>在 MonoDevelop 中的 <code>MainController</code> 类中,覆盖 <code>ViewDidLoad</code> 方法,并在其中输入以下代码:</p>
<pre><code class="language-swift">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);
} ;
</code></pre>
</li>
<li>
<p>最后,添加以下方法:</p>
<pre><code class="language-swift">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);
} );
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮,查看网络服务的结果在标签上显示。</p>
</li>
</ol>
<h3 id="它是如何工作的-37">它是如何工作的...</h3>
<p>正如你可能已经注意到的,在前一个任务中,当应用程序与网络服务通信时,它冻结了,直到收到结果。在这个任务中,我们使用异步调用,这样在应用程序联系网络服务时用户界面就不会冻结。</p>
<p>我们希望在应用程序从网络服务收到响应时得到通知,但我们还需要它的结果。在调用我们感兴趣的 web 方法之前,我们订阅了网络服务对象的 <code>MultiplyNumbersCompleted</code> 事件,如 <code>ViewDidLoad</code> 覆盖中突出显示的代码所示。这个事件是 MonoDevelop 在我们添加网络引用时创建的类的一部分,每个 web 方法都有一个对应的事件。然后我们通过访问 <code>MultiplyNumbersAsync</code> 方法异步调用 web 方法。这个调用立即返回。下一个调用很有趣:</p>
<pre><code class="language-swift">UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
</code></pre>
<p>通过 <code>UIApplication.SharedApplication</code> 静态属性,我们可以访问一些应用程序范围内的组件,例如屏幕上显示的状态栏。当有进程挂起时,建议向用户提供某种信息。状态栏包含一个活动指示器,这是在原生 iOS 应用程序中设备连接到互联网时显示的内容。因此,用户习惯于这个控件,并知道当它显示时,设备正在连接以接收数据。通过将 <code>NetworkActivityIndicatorVisible</code> 属性设置为 <code>true</code>,网络指示器被激活并显示。</p>
<p>当对 Web 方法的调用完成时,将触发相应的事件。在 <code>MultiplyNumbers_CompletedHandler</code> 方法内部,我们首先确保隐藏网络指示器,以通知用户应用程序不再连接。我们可以通过 <code>MultiplyNumbersCompletedEventArgs.Result</code> 属性访问 Web 方法的返回结果。</p>
<p>在这个示例中,我们希望在处理程序内部直接在标签中显示结果。因为 Web 方法是异步调用的,处理程序很可能会在主线程之外的其他线程上执行。因此,我们将结果赋值给标签的操作包装在一个匿名方法中,并在主线程上执行,如处理程序实现中突出显示的代码所示。</p>
<h3 id="更多内容-20">更多内容...</h3>
<p>Web 服务对象包含了一组更常见的异步调用,我们可以使用。它遵循 <code>BeginInvoke EndInvoke</code> 模式,方法名称根据服务的 Web 方法进行重命名。在这种情况下,这些方法被命名为 <code>BeginMultiplyNumbers</code> 和 <code>EndMultiplyNumbers</code>。</p>
<h4 id="错误处理">错误处理</h4>
<p><code>MultiplyNumbersCompletedEventArgs</code> 类还包含一个 <code>Error</code> 属性。它返回 <code>System.Exception</code> 类型的值,如果发生错误,例如由于网络连接问题,它将包含适当的信息。如果没有发生错误,<code>Error</code> 属性将返回 <code>null</code>。在检索 Web 方法的返回结果之前,建议始终检查此属性:</p>
<pre><code class="language-swift">if (null != args.Error){
// Something went wrong, handle appropriately.
}
</code></pre>
<h3 id="参见-36">参见</h3>
<p>在本章中:</p>
<ul>
<li><em>消费 WCF 服务</em></li>
</ul>
<h1 id="消费-wcf-服务">消费 WCF 服务</h1>
<p>在本食谱中,我们将学习如何使用 MonoTouch 消费 WCF 服务。</p>
<h2 id="准备工作-46">准备工作</h2>
<p>对于此项目,我们需要一个正在运行的 WCF 服务。本章的代码下载中可以找到 WCF 服务。要启动服务,打开终端并转到项目的目录。通过运行 <code>start_wcfservice.sh</code> 脚本来启动服务:</p>
<pre><code class="language-swift">cd <code_directory>/CH06_code/ WcfService/WcfService
./start_wcfservice.sh
</code></pre>
<p>服务启动后,在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>WcfServiceApp</code>。</p>
<h2 id="如何操作-22">如何操作...</h2>
<ol>
<li>
<p>将 <code>System.Runtime.Serialization</code> 和 <code>System.ServiceModel</code> 的引用添加到项目中,并在 <code>MainController.cs</code> 文件中添加它们对应的 <code>using</code> 指令。</p>
</li>
<li>
<p>MonoTouch 尚未提供对 WCF 服务的全面支持。为了生成客户端代理,我们将在 Windows 机器上使用<code>slsvcutil</code>工具。在 Windows 的终端中运行以下命令:</p>
<pre><code class="language-swift">"c:\Program Files\Microsoft SDKs\Silverlight\v3.0\Tools\slsvcutil /noconfig http://192.168.1.18:8080/WcfService.svc?wsdl"
</code></pre>
<p>此命令将生成一个名为<code>service.cs</code>的 C#源文件。将此文件添加到 MonoDevelop 项目中。</p>
</li>
<li>
<p>在<code>MainController</code>视图上添加一个标签和一个按钮。重写<code>MainController</code>类的<code>ViewDidLoad</code>方法,并在其中输入以下代码:</p>
<pre><code class="language-swift">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;
} ;
</code></pre>
</li>
<li>
<p>最后,添加以下事件处理器:</p>
<pre><code class="language-swift">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);
} );
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮,并观察从服务返回的数据填充到标签中。</p>
</li>
</ol>
<h3 id="它是如何工作的-38">它是如何工作的...</h3>
<p>MonoTouch 依赖于 Mono Framework 对 WCF 服务的支持,但这并不完整。然而,仅 WCF 服务可以在 iOS 应用程序中使用这一事实就使 MonoTouch 对.NET 开发者更具吸引力。</p>
<p>例如,没有工具可以在 Mac 上创建客户端代理,因此我们必须能够访问 Windows 机器来执行此操作,使用<strong>Silverlight 服务模型代理生成工具(slsvcutil.exe)</strong>。该工具生成的源文件允许我们在项目中消耗 WCF 服务。它基本上做了 MonoDevelop 在我们添加 Web 引用到 ASMX Web 服务时自动执行的事情,就像在前两个任务中一样。</p>
<h3 id="注意-29">注意</h3>
<p>重要的一点是使用<strong>Silverlight 版本 3.0</strong>的<code>slsvcutil</code>来创建客户端代理。</p>
<p>除了 Mono Framework 的支持外,还有一个限制:iOS 不允许动态代码生成。这使得任何依赖于<code>System.Reflection.Emit</code>命名空间的代码都无法使用。实际上,<code>System.Reflection.Emit</code>命名空间在 MonoTouch 中根本不可用。</p>
<p>在 Mac 上复制生成的文件后,我们将其添加到项目中,我们就可以使用 WCF 服务了。之前高亮显示的代码显示了如何实例化服务对象。请注意,服务对象的默认构造函数不能使用,因为 MonoTouch 不支持<code>System.Configuration</code>命名空间。</p>
<p>实际通信是通过调用方法异步实现来完成的,在设置其对应完成事件的处理器之后。请注意,在这种情况下,没有使用同步调用或<code>BeginInvoke EndInvoke</code>模式的替代方案:</p>
<pre><code class="language-swift">client.GetBookInfoCompleted += WcfTestServiceClient_GetBookInfoCompleted;
client.GetBookInfoAsync ();
</code></pre>
<p>服务返回的结果可以通过指定的<code>EventArgs</code>派生类的<code>Result</code>属性检索:</p>
<pre><code class="language-swift">this.labelResult.Text = String.Format ( "Book title: {0}\nAuthor: {1}", e.Result.Title, e.Result.Name);
</code></pre>
<h3 id="还有更多-25">还有更多...</h3>
<p>当调试消耗 WCF 服务的项目时,请记住设置服务运行在的机器的地址,而不是<code>localhost</code>或<code>127.0.0.1</code>。这是因为当我们将应用程序运行在设备上时,应用程序将无法连接到服务。</p>
<h4 id="关于-monodevelop-的-wcf-支持的更多信息">关于 MonoDevelop 的 WCF 支持的更多信息</h4>
<p>在<em>消耗 Web 服务</em>配方中显示的<strong>添加 Web 引用</strong>窗口中,可以通过 MonoDevelop 添加 WCF Web 引用。然而,它尚未完成。</p>
<h4 id="wcf-服务创建">WCF 服务创建</h4>
<p>从 <code>WcfService</code> 服务返回的对象以及实际的服务本身都是在 Mac 上使用 MonoDevelop 完全创建的。由于没有 WCF 项目模板,使用了 <strong>空项目</strong> 模板。</p>
<h3 id="相关内容-11">相关内容</h3>
<p>在本章中:</p>
<ul>
<li><em>消费 Web 服务</em></li>
</ul>
<h1 id="读取-json-数据">读取 JSON 数据</h1>
<p>在本食谱中,我们将学习如何读取 <strong>JavaScript 对象表示法 (JSON )</strong> 数据。</p>
<h2 id="准备工作-47">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>JsonDataApp</code>。在 <code>MainController</code> 的视图中添加一个按钮和一个标签。</p>
<h2 id="如何实现-6">如何实现...</h2>
<ol>
<li>
<p>将项目添加到 <code>System.Json</code> 程序集的引用中。</p>
</li>
<li>
<p>在 <code>MainController.cs</code> 文件中添加以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using System.Json;
using System.Net;
using System.IO;
</code></pre>
</li>
<li>
<p>输入以下方法:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>将处理程序附加到按钮的 <code>TouchUpInside</code> 事件,并在其中输入以下代码:</p>
<pre><code class="language-swift">JsonValue json = this.GetJsonObject ();
this.labelResponse.Text = String.Format ("File name: {0}\n Description: {1}", json ["filename"], json ["description"]);
</code></pre>
</li>
<li>
<p>最后,在项目目录中运行 <code>xsp</code> 服务器。文件 "mtjson.txt" 包含了 JSON 格式的数据。</p>
</li>
</ol>
<h2 id="它是如何工作的-39">它是如何工作的...</h2>
<p>JSON 是一种特定的文本格式,它易于阅读和实现。许多流行的网站使用此格式来分发数据。其主要优势是它不受语言限制。JSON 的结构基于名称/值对和数组。在本任务中使用的 JSON 数据对象相当简单:</p>
<pre><code class="language-swift">{
"filename":"mtjson.txt",
"description":"a sample json object"
}
</code></pre>
<p>要从网络服务器读取数据,我们只需创建一个 <code>HttpWebRequest</code> 对象,将其 <code>Method</code> 属性设置为 <code>HTTP GET:</code>。</p>
<pre><code class="language-swift">HttpWebRequest request = new HttpWebRequest (uri);
request.Method = "GET";
</code></pre>
<p>然后,我们需要从服务器获取响应。我们通过检索请求的响应对象,并使用 <code>StreamReader</code> 从其底层流中读取数据来完成此操作:</p>
<pre><code class="language-swift">HttpWebResponse response = request.GetResponse () as HttpWebResponse;
using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
responseString = sr.ReadToEnd ();
}
</code></pre>
<p>现在的 <code>responseString</code> 变量包含了之前显示的原始 JSON 数据。为了解析 JSON 数据,MonoTouch 提供了 <code>JsonValue</code> 类。要创建一个 <code>JsonValue</code> 对象,我们使用它的 <code>Parse</code> 静态方法,并将包含 JSON 数据的字符串传递给它:</p>
<pre><code class="language-swift">return JsonValue.Parse (responseString);
</code></pre>
<p>要访问 <code>JsonValue</code> 对象解析的数据,我们使用索引器:</p>
<pre><code class="language-swift">this.labelResponse.Text = String.Format ("File name: {0}\nDescription: {1}", json ["filename"], json ["description"]);
</code></pre>
<h2 id="更多内容-21">更多内容...</h2>
<p>如果传递了一个在 JSON 对象中不存在的名称,将会抛出异常。如果不知道 JSON 对象的名称,我们可以使用整数来检索数据:</p>
<pre><code class="language-swift">//json, json etc...
</code></pre>
<p><code>JsonValue</code> 类继承自 <code>IEnumerable</code> 接口。</p>
<h3 id="序列化">序列化</h3>
<p><code>System.Json</code> 命名空间提供了用于简单解析 JSON 数据的对象。它不提供 JSON 序列化功能。然而,我们可以从一组 <code>KeyValuePair<string, JsonValue></code> 对象创建一个 <code>JsonObject</code>。要创建之前的 JSON 对象,我们将编写如下内容:</p>
<pre><code class="language-swift">JsonObject obj = new JsonObject (new KeyValuePair<string, JsonValue> ("filename", JsonValue.Parse ("\"mtjson.txt\"")), new KeyValuePair<string, JsonValue>("description", JsonValue.Parse("\"a sample json object\"")));
</code></pre>
<h2 id="相关内容-12">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>消费 Web 服务</em></li>
</ul>
<h1 id="第七章-多媒体资源">第七章. 多媒体资源</h1>
<p>在本章中,我们将介绍以下内容:</p>
<ul>
<li>
<p>选择图像和视频</p>
</li>
<li>
<p>使用相机捕获媒体</p>
</li>
<li>
<p>播放视频</p>
</li>
<li>
<p>播放音乐和声音</p>
</li>
<li>
<p>使用麦克风录制</p>
</li>
<li>
<p>直接管理多个相册项目</p>
</li>
</ul>
<h1 id="简介-6">简介</h1>
<p>今天智能手机和平板电脑最重要的功能之一是它们捕捉和管理多媒体资源的能力。无论是照片、视频还是音频,针对这些设备且能够有效处理多媒体的应用程序非常重要。</p>
<p>在本章中,我们将学习如何管理设备上存储的媒体。我们还将了解如何使用设备的多媒体捕获设备(相机和麦克风)来捕捉内容并创建一个将为用户提供丰富体验的应用程序。</p>
<p>更具体地说,我们将讨论以下内容:</p>
<ul>
<li>
<p><code>UIImagePickerController:</code> 这是一个控制器,不仅通过用户界面提供对设备上已保存的照片和视频的访问,还提供了一个用于捕获的相机界面</p>
</li>
<li>
<p><code>MPMoviePlayerController:</code> 这是一个允许我们播放和流式传输视频文件的控制器</p>
</li>
<li>
<p><code>MPMediaPickerController:</code> 这是访问已保存内容的默认用户界面,由原生 iPod 应用程序管理</p>
</li>
<li>
<p><code>MPMusicPlayerController:</code> 这是一个负责播放 iPod 内容的对象</p>
</li>
<li>
<p><code>AVAudioPlayer:</code> 这是一个允许我们播放声音文件的类</p>
</li>
<li>
<p><code>AVAudioRecorder:</code> 这是一个允许我们使用麦克风录制音频的类</p>
</li>
<li>
<p><code>ALAssetsLibrary:</code> 这是一个提供对设备可用资源和其元数据的访问的类</p>
</li>
</ul>
<h1 id="选择图像和视频">选择图像和视频</h1>
<p>在这个菜谱中,我们将学习如何为用户提供从设备相册导入图像和视频的能力。</p>
<h2 id="准备工作-48">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>ImagePickerApp</code>。</p>
<h2 id="如何操作-23">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>的主视图中添加一个<code>UIImageView</code>和一个<code>UIButton</code>。</p>
</li>
<li>
<p>覆盖<code>MainController</code>类的<code>ViewDidLoad</code>方法,并在其中输入以下代码:</p>
<pre><code class="language-swift">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);
} ;
</code></pre>
</li>
<li>
<p>实现处理<code>FinishedPickingMedia</code>和<code>Canceled</code>事件的处理器方法:</p>
<pre><code class="language-swift">private void ImagePicker_FinishedPickingMedia (object sender, UIImagePickerMediaPickedEventArgs e){
UIImage pickedImage = e.Info as UIImage;
this.imageView.Image = pickedImage;
this.imagePicker.DismissModalViewControllerAnimated(true);
}
private void ImagePicker_Cancelled (object sender, EventArgs e){
this.imagePicker.DismissModalViewControllerAnimated(true);
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮以显示图像选择器,并通过点击缩略图选择图像。图像将在图像视图中显示。<code>UIImagePickerController</code>在以下屏幕截图中显示:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_07_01.jpg"></p>
<h2 id="它是如何工作的-40">它是如何工作的...</h2>
<p><code>UIImagePickerController</code> 是 iOS 提供的一个特殊视图控制器,用于选择保存在设备相册中的图像和视频,或从相机中获取。</p>
<h3 id="注意-30">注意</h3>
<p>默认情况下,iOS 模拟器中没有存储在相册中的图像。要将图像添加到模拟器中,取消注释已下载项目源代码中的<code>AddImagesToAlbum</code>方法,并调用一次,将包含图像的计算机上的物理路径作为参数传递。</p>
<p>在初始化图片选择器对象后,我们需要订阅其<code>FinishedPickingMedia</code>事件,该事件为我们提供了用户所选的媒体。在分配给它的处理程序中,我们获取所选的图片:</p>
<pre><code class="language-swift">UIImage pickedImage = e.Info as UIImage;
</code></pre>
<p><code>Info</code>属性返回一个包含有关所选媒体各种信息的<code>NSDictionary</code>对象。我们通过传递常量<code>UIImagePickerController.OriginalImage</code>作为键来检索图像。因为字典的值是<code>NSObject</code>类型,所以我们把返回值转换为<code>UIImage</code>。在我们将图像分配给要显示的<code>UIImageView</code>之后,我们关闭控制器:</p>
<pre><code class="language-swift">this.imagePicker.DismissModalViewControllerAnimated(true);
</code></pre>
<p>当用户点击控制器的<strong>取消</strong>按钮时,会触发<code>Canceled</code>事件。我们必须订阅它以关闭控制器,因为它在用户点击<strong>取消</strong>按钮时不会自动关闭。</p>
<h2 id="还有更多-26">还有更多...</h2>
<p>我们可以通过图片选择器的<code>SourceType</code>属性定义图片选择器将从中读取的图片/视频的来源。在此示例中,我们使用<code>UIImagePickerController.PhotoLibrary</code>,因为模拟器不支持相机硬件。</p>
<h3 id="选择视频">选择视频</h3>
<p><code>UIImagePickerController</code>默认只显示图片。要支持视频,其<code>MediaType</code>属性必须被设置。它接受一个<code>string[]</code>,包含指定的媒体名称:</p>
<pre><code class="language-swift">this.imagePicker.MediaTypes = new string[] { "public.image", "public.movie" };
</code></pre>
<p>要确定用户选择的媒体类型,我们在<code>FinishedPickingMedia</code>处理程序中的字典中检查<code>MediaType</code>键。如果是视频,我们使用<code>MediaUrl</code>键获取其 URL:</p>
<pre><code class="language-swift">if (e.Info.ToString() == "public.movie"){
NSUrl mediaUrl = e.Info as NSUrl;
// Do something useful with the media url.
}
</code></pre>
<h2 id="参见-37">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>使用相机捕捉媒体</em></p>
</li>
<li>
<p><em>直接管理相册项目</em></p>
</li>
</ul>
<h1 id="使用相机捕捉媒体">使用相机捕捉媒体</h1>
<p>在本食谱中,我们将学习如何使用设备相机来捕捉媒体。</p>
<h2 id="准备工作-49">准备工作</h2>
<p>打开之前任务中讨论的<code>ImagePickerApp</code>项目。</p>
<h3 id="注意-31">注意</h3>
<p>在 iOS 模拟器上不可用相机功能。此示例只能在设备上运行。有关更多信息,请参阅第十四章<em>部署</em>。</p>
<h2 id="如何做-9">如何做...</h2>
<ol>
<li>
<p>在<code>ViewDidLoad</code>方法内部,替换以下行:</p>
<pre><code class="language-swift">this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
</code></pre>
</li>
<li>
<p>使用以下代码块:</p>
<pre><code class="language-swift">if (UIImagePickerController.IsSourceTypeAvailable( UIImagePickerControllerSourceType.Camera)){
this.imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
} else{
this.imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
}
</code></pre>
</li>
<li>
<p>在<code>FinishedPickingMedia</code>处理程序中,在关闭图片选择器之前添加以下代码:</p>
<pre><code class="language-swift">pickedImage.SaveToPhotosAlbum(delegate( UIImage image, NSError error) {
if (null != error){
Console.WriteLine("Image not saved! Message: {0}", error.LocalizedDescription);
}
} );
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮打开相机并拍照。照片将被保存到设备相册中。</p>
</li>
</ol>
<h2 id="它是如何工作的-41">它是如何工作的...</h2>
<p>在展示相机取景器之前,我们必须确保应用程序运行的实际设备确实具有适当的硬件。我们通过调用<code>UIImagePickerController</code>类的静态<code>IsSourceTypeAvailable</code>方法来完成此操作:</p>
<pre><code class="language-swift">if (UIImagePickerController.IsSourceTypeAvailable( UIImagePickerControllerSourceType.Camera))
</code></pre>
<p>如果它返回<code>true</code>,我们将源类型设置为<code>Camera:</code></p>
<pre><code class="language-swift">this.imagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
</code></pre>
<p>这将导致图片选择器控制器启动相机设备而不是加载设备相册。</p>
<p>当用户拍照(或录像)时,它不会自动保存在设备上。要保存它,我们使用<code>UIImage</code>类的<code>SaveToPhotosAlbum</code>方法。此方法接受一个类型为<code>UIImage.SaveStatus</code>的委托,如果发生错误,它会报告错误:</p>
<pre><code class="language-swift">if (null != error){
Console.WriteLine("Image not saved! Message: {0}", error.LocalizedDescription);
}
</code></pre>
<h2 id="更多-4">更多...</h2>
<p>摄像头视图也可以自定义。要禁用默认的摄像头控件,将<code>ShowsCameraControls</code>属性设置为<code>false</code>。然后,将您想要的控件的自定义视图传递给<code>CameraOverlayView</code>属性。要触发摄像头的快门,请调用<code>TakePicture</code>方法。</p>
<h3 id="图像编辑">图像编辑</h3>
<p>摄像头支持简单的编辑功能,在捕获图像后。此编辑功能允许用户选择图像的特定部分,甚至可以放大到特定区域。要显示编辑控件,将<code>AllowsEditing</code>属性设置为<code>true</code>。编辑后的图像可以从<code>FinishedPickingMedia</code>处理程序中的字典中检索,传递<code>UIImagePickerController.EditedImage</code>键。编辑界面如下截图所示:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_07_02.jpg"></p>
<h2 id="参见-38">参见</h2>
<p>在这一章:</p>
<ul>
<li><em>选择图像和视频</em></li>
</ul>
<h1 id="播放视频">播放视频</h1>
<p>在这个菜谱中,我们将学习如何显示视频播放器界面并播放视频文件。</p>
<h2 id="准备工作-50">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>PlayVideoApp</code>。</p>
<h2 id="如何做-10">如何做...</h2>
<ol>
<li>
<p>在<code>MainController</code>的主视图中添加一个按钮。</p>
</li>
<li>
<p>将视频文件添加到项目中,并将其<strong>构建操作</strong>设置为<strong>内容</strong>。</p>
</li>
<li>
<p>在<code>MainController.cs</code>文件中输入以下<code>using</code>指令:</p>
<pre><code class="language-swift">using MonoTouch.MediaPlayer;
</code></pre>
</li>
<li>
<p>覆盖<code>MainController</code>类的<code>ViewDidLoad</code>方法,并输入以下代码:</p>
<pre><code class="language-swift">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();
} ;
</code></pre>
</li>
<li>
<p>在<code>MainController</code>类中输入以下方法:</p>
<pre><code class="language-swift">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.");
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮,视频将加载并开始播放。在 MonoDevelop 的<strong>应用程序输出</strong>中查看显示的消息。</p>
</li>
</ol>
<h3 id="工作原理-9">工作原理...</h3>
<p><code>MPMoviePlayerController</code>播放存储在本地的视频文件或从网络流式传输的视频文件。我们使用接受<code>NSUrl</code>参数的构造函数来初始化它:</p>
<pre><code class="language-swift">this.moviePlayer = new MPMoviePlayerController( new NSUrl("videos/video.mov"));
</code></pre>
<p><code>NSUrl</code>对象映射到我们添加到项目中的本地文件。</p>
<p>创建实例后,我们为其视图定义一个框架并将其添加到我们的视图中:</p>
<pre><code class="language-swift">this.moviePlayer.View.Frame = this.View.Bounds;
this.View.AddSubview(this.moviePlayer.View);
</code></pre>
<p>突出的代码向默认的通知中心添加观察者,这样当播放状态改变或完成时,我们会收到通知。然后,我们调用它的<code>Play</code>方法,<code>MPMoviePlayerController</code>的视图就会显示,视频开始播放。</p>
<p>在<code>MoviePlayer_PlaybackStateChanged</code>方法内部,我们输出<code>PlaybackState</code>属性:</p>
<pre><code class="language-swift">Console.WriteLine("Movie player load state changed: {0}", this.moviePlayer.PlaybackState);
</code></pre>
<p>此属性告诉我们播放状态,例如<code>Paused</code>、<code>Playing</code>、<code>SeekingForward</code>、<code>SeekingBackward</code>等。</p>
<h3 id="更多-5">更多...</h3>
<p>除了这个例子中使用的之外,我们还可以为<code>MPMoviePlayerController</code>添加更多通知的观察者,其中一些是:</p>
<ul>
<li>
<p><code>DidEnterFullscreenNotification:</code> 此通知表示用户点击了全屏控制,控制器已进入 <code>fullscreen</code> 模式。</p>
</li>
<li>
<p><code>DidExitFullscreenNotification:</code> 此通知表示控制器已离开 <code>fullscreen</code> 模式。</p>
</li>
<li>
<p><code>DurationAvailableNotification:</code> 此通知表示控制器已收到视频持续时间的信息。</p>
</li>
<li>
<p><code>LoadStateDidChangeNotification:</code> 此通知对于网络播放很有用,当控制器在缓冲区中完成预加载媒体时被触发。</p>
</li>
<li>
<p><code>NaturalSizeAvailableNotification:</code> 当电影帧的尺寸可用时,此通知被触发。大小可以通过播放器的 <code>NaturalSize</code> 属性检索。</p>
</li>
<li>
<p><code>NowPlayingMovieDidChangeNotification:</code> 当播放器的视频内容发生变化时,此通知被触发。当前内容可以通过其 <code>ContentUrl</code> 属性获取。</p>
</li>
</ul>
<h4 id="无线流媒体">无线流媒体</h4>
<p>从 iOS 版本 4.3 开始,<code>MPMoviePlayerController</code> 可以用来将视频流式传输到 Apple 的 AirPlay 兼容设备。要启用它,将其 <code>AllowsAirPlay</code> 属性设置为 <code>true</code>。当 <code>MPMoviePlayerController</code> 显示时,它将提供一个界面,允许用户选择它检测到的设备。</p>
<h3 id="相关内容-13">相关内容</h3>
<p>在本章中:</p>
<ul>
<li><em>播放音乐和声音</em></li>
</ul>
<h1 id="播放音乐和声音">播放音乐和声音</h1>
<p>在本教程中,我们将学习如何播放存储在设备上的简单音频文件和歌曲。</p>
<h2 id="准备工作-51">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>PlayMusicApp</code>。</p>
<h3 id="注意-32">注意</h3>
<p>此示例在模拟器上无法工作。您还需要在设备上至少存储一首歌曲。</p>
<h2 id="如何操作-24">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加三个按钮。</p>
</li>
<li>
<p>在 <code>MainController.cs</code> 文件中添加以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using MonoTouch.MediaPlayer;
</code></pre>
</li>
<li>
<p>在类中添加两个字段:</p>
<pre><code class="language-swift">private MPMusicPlayerController musicPlayerController;
private MPMediaPickerController mediaPicker;
</code></pre>
</li>
<li>
<p>重写 <code>MainController</code> 类的 <code>ViewDidLoad</code> 方法,并输入以下代码:</p>
<pre><code class="language-swift">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();
} ;
</code></pre>
</li>
<li>
<p>添加以下方法:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>选择歌曲</strong> 按钮,并选择一首或多首歌曲。</p>
</li>
</ol>
<h3 id="它是如何工作的-42">它是如何工作的...</h3>
<p><code>MPMediaPickerController</code> 提供与原生 iPod 应用程序相同的用户界面。<code>MPMusicPlayerController</code> 负责播放设备上存储的歌曲。</p>
<p>我们首先初始化媒体选择器,通过其构造函数传递我们想要它查找的媒体类型:</p>
<pre><code class="language-swift">this.mediaPicker = new MPMediaPickerController(MPMediaType.Music);
</code></pre>
<p>之后,我们订阅其 <code>ItemsPicked</code> 和 <code>DidCancel</code> 事件,以便我们可以捕获用户的反馈:</p>
<pre><code class="language-swift">this.mediaPicker.ItemsPicked += MediaPicker_ItemsPicked;
this.mediaPicker.DidCancel += MediaPicker_DidCancel;
</code></pre>
<p>突出的代码显示了如何初始化音乐播放器对象。这里演示的选项 <code>MPMusicPlayerController.ApplicationMusicPlayer</code> 创建了一个仅针对应用程序的实例。另一个可用的选项 <code>MPMusicPlayerController.iPodMusicPlayer</code> 创建了一个实例,允许在应用程序处于后台时播放媒体,类似于 iPod 应用程序。</p>
<p>在 <code>MediaPicker_ItemsPicked</code> 处理程序中,我们通过其 <code>SetQueue</code> 方法将用户选择的歌曲设置到音乐播放器中:</p>
<pre><code class="language-swift">this.musicPlayerController.SetQueue(e.MediaItemCollection);
</code></pre>
<p>之后,我们取消显示模态媒体选择器控制器。播放和停止歌曲分别通过 <code>MPMusicPlayerController</code> 的 <code>Play()</code> 和 <code>Stop()</code> 方法实现。</p>
<h3 id="还有更多-27">还有更多...</h3>
<p><code>MPMusicPlayerController</code> 包含有关当前播放项的信息。此信息可以通过其 <code>NowPlayingItem</code> 属性访问。它是 <code>MPMediaItem</code> 类型,包含有关当前播放媒体的各种类型信息。以下示例获取正在播放的歌曲的标题:</p>
<pre><code class="language-swift">Console.WriteLine(this.musicPlayerController .NowPlayingItem.ValueForProperty(MPMediaItem.TitleProperty));
</code></pre>
<h4 id="播放声音文件">播放声音文件</h4>
<p><code>MPMusicPlayerController</code> 是一个专门设计用来管理和播放存储在设备 iPod 库中的项目和播放列表的对象。</p>
<p>对于播放简单的声音文件,MonoTouch 为 iOS 的 <code>AVAudioPlayer</code> 类提供了一个包装器。以下是其最简单用法的示例:</p>
<pre><code class="language-swift">using MonoTouch.AVFoundation;
//...
AVAudioPlayer audioPlayer = AVAudioPlayer.FromUrl( new NSUrl("path/to/sound file"));
audioPlayer.Play();
</code></pre>
<h3 id="参见-39">参见</h3>
<p>在本章中:</p>
<ul>
<li><em>播放视频</em></li>
</ul>
<h1 id="使用麦克风录制">使用麦克风录制</h1>
<p>在本食谱中,我们将学习如何使用设备的麦克风来录制声音。</p>
<h2 id="准备工作-52">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>RecordSoundApp</code>。</p>
<h3 id="注意-33">注意</h3>
<p>此示例在模拟器上无法工作。</p>
<h2 id="如何操作-25">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加两个按钮。</p>
</li>
<li>
<p>在 <code>MainController.cs</code> 文件中输入以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using System.IO;
using MonoTouch.AVFoundation;
using MonoTouch.AudioToolbox;
</code></pre>
</li>
<li>
<p>覆盖 <code>ViewDidLoad</code> 方法,并在其中添加以下代码:</p>
<pre><code class="language-swift">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();
} ;
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>开始录制</strong> 按钮开始录制音频,例如,可以说些话来录制你的声音。</p>
</li>
<li>
<p>点击 <strong>停止录制</strong> 按钮停止录制并播放回放。</p>
</li>
</ol>
<h2 id="工作原理-10">工作原理...</h2>
<p><code>AVAudioRecorder</code> 类提供了录制功能。它是通过将捕获的音频直接流式传输到文件系统来实现的。要初始化 <code>AVAudioRecorder</code> 的实例,我们使用它的静态 <code>ToUrl</code> 方法:</p>
<pre><code class="language-swift">this.audioRecorder = AVAudioRecorder.ToUrl( soundFileUrl, recordingSettings, out error);
</code></pre>
<p>如果与 <code>NSUrl</code> 变量对应的文件已经存在,它将被覆盖。</p>
<p><code>recordingSettings</code> 变量是 <code>NSDictionary</code> 类型,包含输出声音文件的设置。在初始化 <code>AVAudioRecorder</code> 时,我们必须提供至少一些最小设置。在这里,我们将声音格式设置为纯 wav:</p>
<pre><code class="language-swift">NSDictionary recordingSettings = NSDictionary.FromObjectAndKey( AVAudioSettings.AVFormatIDKey, NSNumber .FromInt32((int)AudioFileType.WAVE));
</code></pre>
<p>要指示记录器开始录制,我们只需调用它的 <code>Record()</code> 方法:</p>
<pre><code class="language-swift">this.audioRecorder.Record();
</code></pre>
<p>当用户点击 <strong>停止录制</strong> 按钮时,录制停止,保存的声音开始通过 <code>AVAudioPlayer:</code> 播放。</p>
<pre><code class="language-swift">this.audioRecorder.Stop();
AVAudioPlayer player = AVAudioPlayer.FromUrl(soundFileUrl);
player.Play();
</code></pre>
<h2 id="还有更多-28">还有更多...</h2>
<p><code>AVAudioRecorder</code> 类还提供了声音计测选项。要启用声音计测,将它的 <code>MeteringEnabled</code> 属性设置为 <code>true</code>。然后我们可以在特定通道上输出峰值功率(以分贝为单位)。为了对我们录制的第一个通道执行此操作,请在 <code>Record()</code> 方法调用之后立即添加以下代码:</p>
<pre><code class="language-swift">ThreadPool.QueueUserWorkItem(delegate {
while (this.audioRecorder.Recording){
this.audioRecorder.UpdateMeters();
Console.WriteLine(this.audioRecorder.PeakPower(0));
}
} );
</code></pre>
<p><code>PeakPower</code> 方法接受通道的零基索引并返回该通道的峰值(以分贝为单位)。在调用 <code>PeakPower</code> 方法之前,请立即调用 <code>UpdateMeters()</code> 方法以获取最新的读数。</p>
<p>注意,在录制器上启用测光会使用 CPU 资源。如果您不打算使用测光值,请勿启用它。</p>
<h3 id="预定义时间录制">预定义时间录制</h3>
<p>要录制预定义时间的音频,而无需用户停止录制,请调用 <code>RecordFor(double)</code> 方法。其参数指定了录制的时间(以秒为单位)。</p>
<h2 id="相关内容-14">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>播放音乐和声音</em></li>
</ul>
<h1 id="直接管理多个相册项目">直接管理多个相册项目</h1>
<p>在本食谱中,我们将讨论以编程方式访问设备的照片相册。</p>
<h2 id="准备工作-53">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>ManageAlbumApp</code>。</p>
<h3 id="注意-34">注意</h3>
<p>此示例适用于模拟器。照片相册中必须至少存在一个图像。</p>
<h2 id="如何操作-26">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的主视图中添加一个按钮。</p>
</li>
<li>
<p>在 <code>MainController.cs</code> 文件中输入以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using MonoTouch.AssetsLibrary;
</code></pre>
</li>
<li>
<p>重写 <code>ViewDidLoad</code> 方法,并在其中输入以下代码:</p>
<pre><code class="language-swift">this.buttonEnumerate.TouchUpInside += delegate {
this.assetsLibrary = new ALAssetsLibrary();
this.assetsLibrary.Enumerate(ALAssetsGroupType.All, this.GroupsEnumeration, this.GroupsEnumerationFailure);
} ;
</code></pre>
</li>
<li>
<p>在类中添加以下方法:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>枚举资产</strong> 按钮,并观察保存照片的 URL 在 <strong>应用程序输出</strong> 面板中显示。</p>
</li>
</ol>
<h2 id="它是如何工作的-43">它是如何工作的...</h2>
<p><code>ALAssetsLibrary</code> 类提供了对设备相册项的访问。这些项由 <code>ALAsset</code> 类表示,并按组划分,由 <code>ALAssetGroup</code> 类表示。</p>
<p>我们需要做的第一件事是枚举资产组。为此,调用 <code>Enumerate</code> 方法:</p>
<pre><code class="language-swift">this.assetsLibrary.Enumerate(ALAssetsGroupType.All, this.GroupsEnumeration, this.GroupsEnumerationFailure);
</code></pre>
<p>第一个参数是 <code>ALAssetGroupTypes</code> 类型,它指示资产库要枚举哪些资产组。传递 <code>ALAssetGroupTypes.All</code> 表示我们想要枚举所有资产组。其他两个参数是委托类型。<code>GroupsEnumeration</code> 方法是我们读取组数据的地方,而 <code>GroupsEnumerationFailure</code> 将在发生错误时触发。当第一次调用 <code>Enumerate</code> 方法时,会要求用户授权应用程序访问设备的资产。如果用户拒绝访问,将触发失败方法。下次调用 <code>Enumerate</code> 方法时,访问消息会再次出现。</p>
<p><code>GroupsEnumeration</code> 方法的签名如下:</p>
<pre><code class="language-swift">private void GroupsEnumeration(ALAssetsGroup assetGroup, ref bool stop)
</code></pre>
<p><code>assetGroup</code> 参数包含组的信息。</p>
<p>注意 <code>stop</code> 参数,它被声明为 <code>ref</code>。当枚举发生时,方法会被触发一次以返回第一个组,并且第二次不会调用,无论还有多少组存在。为了强制它继续被调用以枚举所有组,我们必须将 <code>stop</code> 变量设置为 <code>false</code>。当所有组都被枚举后,方法会最后一次被调用,此时 <code>assetGroup</code> 变量被设置为 <code>null</code>。因此,我们需要检查这一点。将这些放入代码中:</p>
<pre><code class="language-swift">if (null != assetGroup){
// Continue enumerating
stop = false;
// Determine what assets to enumerate
assetGroup.SetAssetsFilter(ALAssetsFilter.AllPhotos);
// Enumerate assets
assetGroup.Enumerate(this.AssetEnumeration);
}
</code></pre>
<p>在 <code>ALAssetGroup</code> 类的实例上调用 <code>SetAssetsFilter</code> 方法,我们指示它过滤我们希望它查找的资产类型。之后,过程与组枚举类似。<code>ALAssetGroup</code> 类还包含一个 <code>Enumerate</code> 方法。它接受一个代表类型(在这里由 <code>AssetsEnumeration</code> 方法表示)的参数。其实现与 <code>GroupsEnumeration</code> 方法类似:</p>
<pre><code class="language-swift">if (null != asset){
// Continue enumerating assets
stop = false;
// Output the asset url
Console.WriteLine("Asset url: {0}", asset.DefaultRepresentation.Url.AbsoluteString);
</code></pre>
<p><code>ALAsset</code> 类包含各种信息和属性。大部分信息存储在其 <code>DefaultRepresentation</code> 属性中,该属性的类型为 <code>ALAssetRepresentation</code>。</p>
<h2 id="更多内容-22">更多内容...</h2>
<p>如果我们感兴趣的资产是一张图片,我们可以通过 <code>DefaultRepresentation</code> 属性获取实际图片:</p>
<pre><code class="language-swift">CGImage image = asset.DefaultRepresentation.GetImage();
</code></pre>
<h3 id="读取-exif-数据">读取 EXIF 数据</h3>
<p>我们可以通过 <code>ALAssetRepresentation</code> 的 <code>Metadata</code> 属性读取照片的 <strong>交换图像文件格式</strong> (EXIF) 元数据,<code>Metadata</code> 属性的类型为 <code>NSDictionary</code>,如下所示:</p>
<pre><code class="language-swift">NSDictionary metaData = asset.DefaultRepresentation.Metadata;
if (null != metaData){
NSDictionary exifData = (NSDictionary)metaData[ new NSString("{Exif}")];
}
</code></pre>
<h3 id="获取单个资产">获取单个资产</h3>
<p>如果我们知道资产的 URL,我们也可以通过 <code>ALAssetLibrary</code> 的 <code>AssetForUrl</code> 方法检索单个资产。</p>
<h2 id="参见-40">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>选择图片和视频</em></li>
</ul>
<h1 id="第八章集成-ios-功能">第八章。集成 iOS 功能</h1>
<p>在本章中,我们将介绍以下内容:</p>
<ul>
<li>
<p>开始电话通话</p>
</li>
<li>
<p>发送短信和电子邮件</p>
</li>
<li>
<p>在我们的应用程序中使用短信</p>
</li>
<li>
<p>在我们的应用程序中使用电子邮件消息</p>
</li>
<li>
<p>管理地址簿</p>
</li>
<li>
<p>显示联系人</p>
</li>
<li>
<p>管理日历</p>
</li>
</ul>
<h1 id="简介-7">简介</h1>
<p>移动设备为用户提供了一系列功能。创建一个与应用程序这些功能交互以向用户提供完整体验的应用程序当然可以被视为一种优势。</p>
<p>在本章中,我们将讨论 iOS 的一些最常见功能以及如何将它们的一些或全部功能集成到我们的应用程序中。我们将看到如何使用原生平台应用程序或通过在我们的项目中集成原生用户界面来使用户能够进行电话通话、发送短信和电子邮件。此外,我们还将讨论以下组件:</p>
<ul>
<li>
<p><code>MFMessageComposeViewController:</code> 这个控制器适合发送文本(SMS)消息</p>
</li>
<li>
<p><code>MFMailComposeViewController:</code> 这是一个用于发送带或不带附件的电子邮件的控制器</p>
</li>
<li>
<p><code>ABAddressBook:</code> 这是一个提供我们访问地址簿数据库的类</p>
</li>
<li>
<p><code>ABPersonViewController:</code> 这是一个显示和/或编辑地址簿中联系人信息的控制器</p>
</li>
<li>
<p><code>EKEventStore:</code> 这是一个负责管理日历事件的类</p>
</li>
</ul>
<p>此外,我们还将学习如何读取和保存联系人信息,如何显示联系人详细信息,以及如何与设备日历交互。</p>
<p>注意,本章中的一些示例可能需要设备。例如,模拟器不包含消息应用程序。要将应用程序部署到设备,您需要通过 Apple 的开发者门户注册为 iOS 开发者,并获得 MonoTouch 的商业许可证。</p>
<h1 id="开始电话通话">开始电话通话</h1>
<p>在本食谱中,我们将学习如何调用原生电话应用程序,允许用户进行通话。</p>
<h2 id="准备工作-54">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>PhoneCallApp</code>。</p>
<h3 id="注意-35">注意</h3>
<p>原生电话应用程序在模拟器上不可用。它仅在 iPhone 设备上可用。</p>
<h2 id="如何操作-27">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个按钮,并重写 <code>ViewDidLoad</code> 方法。使用以下代码实现它。如果您实际上想要拨打电话,请将数字替换为真实的电话号码:</p>
<pre><code class="language-swift">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);
}
} ;
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。点击 <strong>Call!</strong> 按钮开始通话。以下截图显示了电话应用程序正在拨打电话:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_08_01.jpg"></p>
<h2 id="它是如何工作的-44">它是如何工作的...</h2>
<p>通过 <code>UIApplication.SharedApplication</code> 静态属性,我们可以访问应用程序的 <code>UIApplication</code> 对象。我们可以使用它的 <code>OpenUrl</code> 方法,该方法接受一个 <code>NSUrl</code> 变量来发起通话:</p>
<pre><code class="language-swift">UIApplication.SharedApplication.OpenUrl(url);
</code></pre>
<p>由于并非所有 iOS 设备都支持原生电话应用程序,因此首先检查其可用性将是有用的:</p>
<pre><code class="language-swift">if (UIApplication.SharedApplication.CanOpenUrl(url))
</code></pre>
<p>当调用 <code>OpenUrl</code> 方法时,原生电话应用将被执行,并且它将立即开始拨打电话。请注意,需要 <code>tel:</code> 前缀来发起通话。</p>
<h2 id="更多内容-23">更多内容...</h2>
<p>MonoTouch 也支持 <code>CoreTelephony</code> 框架,通过 <code>MonoTouch.CoreTelephony</code> 命名空间。这是一个简单的框架,提供了有关通话状态、连接、运营商信息等方面的信息。请注意,当通话开始时,原生电话应用进入前台,导致应用挂起。以下是对 <code>CoreTelephony</code> 框架的简单使用:</p>
<pre><code class="language-swift">CTCallCenter callCenter = new CTCallCenter();
callCenter.CallEventHandler = delegate(CTCall call) {
Console.WriteLine(call.CallState);
} ;
</code></pre>
<p>注意,处理程序是用等号(=)而不是常见的加等号(+=)组合赋值的。这是因为 <code>CallEventHandler</code> 是一个属性而不是一个事件。当应用进入后台时,事件不会分配给它。当应用返回前台时,只有最后发生的事件会被分配。</p>
<h3 id="关于-openurl-的更多信息">关于 OpenUrl 的更多信息</h3>
<p><code>OpenUrl</code> 方法可以用来打开各种原生和非原生应用。例如,要在 Safari 中打开网页,只需创建一个包含以下链接的 <code>NSUrl</code> 对象:</p>
<pre><code class="language-swift">NSUrl url = new NSUrl("http://www.packtpub.com");
</code></pre>
<h2 id="相关链接">相关链接</h2>
<p>本章内容:</p>
<ul>
<li><em>发送短信和电子邮件</em></li>
</ul>
<h1 id="发送短信和电子邮件">发送短信和电子邮件</h1>
<p>在这个菜谱中,我们将学习如何在我们的应用中调用原生邮件和消息应用。</p>
<h2 id="准备工作-55">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>SendTextApp</code>。</p>
<h2 id="如何操作-28">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的主视图中添加两个按钮。覆盖 <code>MainController</code> 类的 <code>ViewDidLoad</code> 方法,并使用以下代码实现:</p>
<pre><code class="language-swift">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!");
}
} ;
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。点击其中一个按钮以打开相应的应用。</p>
</li>
</ol>
<h2 id="它是如何工作的-45">它是如何工作的...</h2>
<p>再次使用 <code>OpenUrl</code> 方法,我们可以发送文本或电子邮件消息。在这个示例代码中,只需使用 <code>sms:</code> 前缀就会打开原生短信应用。在 <code>sms:</code> 前缀后添加手机号码将打开原生消息应用:</p>
<pre><code class="language-swift">UIApplication.SharedApplication.OpenUrl(new NSUrl("sms:+123456789012"));
</code></pre>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_08_02.jpg"></p>
<h3 id="注意-36">注意</h3>
<p>除了收件人号码外,在显示原生短信应用之前,没有其他数据可以设置。</p>
<p>对于打开原生电子邮件应用,过程类似。传递 <code>mailto:</code> 前缀将打开编辑邮件控制器。</p>
<pre><code class="language-swift">UIApplication.SharedApplication.OpenUrl(new NSUrl("mailto:"));
</code></pre>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_08_03.jpg"></p>
<p><code>mailto:</code> URL 方案支持各种参数来定制电子邮件消息。这些参数允许我们输入发送者地址、主题和消息:</p>
<pre><code class="language-swift">UIApplication.SharedApplication.OpenUrl("mailto:recipient@example.com?subject=Email%20with%20MonoTouch!&body=This%20is%20the%20message%20body!");
</code></pre>
<h2 id="更多内容-24">更多内容...</h2>
<p>虽然 iOS 提供了打开原生消息应用的方法,但在电子邮件的情况下,预先定义消息内容,这里的控制从应用内部停止。实际上通过代码发送消息是没有办法的。是否发送消息将由用户决定。</p>
<h3 id="关于打开外部应用的更多信息">关于打开外部应用的更多信息</h3>
<p><code>OpenUrl</code>方法提供了一个打开原生消息应用程序的接口。打开外部应用程序有一个缺点:调用<code>OpenUrl</code>方法的程序会转到后台。在 iOS 版本 3.*之前,这是通过应用程序提供消息的唯一方式。从 iOS 版本 4.0 开始,Apple 向 SDK 提供了消息控制器。以下菜谱讨论了它们的用法。</p>
<h2 id="相关内容-15">相关内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>开始电话</em></p>
</li>
<li>
<p><em>在我们的应用程序中使用文本消息</em></p>
</li>
</ul>
<h1 id="在我们的应用程序中使用文本消息">在我们的应用程序中使用文本消息</h1>
<p>在这个菜谱中,我们将学习如何在我们的应用程序中使用原生消息用户界面提供文本消息功能。</p>
<h2 id="准备工作-56">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>TextMessageApp</code>。</p>
<h2 id="如何做到这一点-9">如何做到这一点...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加一个按钮。在<code>MainController.cs</code>文件中输入以下指令:</p>
<pre><code class="language-swift">using MonoTouch.MessageUI;
</code></pre>
</li>
<li>
<p>使用以下代码实现<code>ViewDidLoad</code>方法,根据您的意愿更改接收者号码和/或消息正文:</p>
<pre><code class="language-swift">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!");
}
} ;
}
</code></pre>
</li>
<li>
<p>添加以下嵌套类:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>发送消息</strong>按钮以打开消息控制器。点击<strong>发送</strong>按钮发送消息,或点击<strong>取消</strong>按钮返回应用程序。</p>
</li>
</ol>
<h2 id="它是如何工作的-46">它是如何工作的...</h2>
<p><code>MonoTouch.MessageUI</code>命名空间包含允许我们在 iOS 应用程序中实现消息的必要 UI 元素。对于文本消息(SMS),我们需要<code>MFMessageComposeViewController</code>类。</p>
<p>只有 iPhone 能够直接发送短信。在 iOS 5 中,iPod 和 iPad 也可以发送短信,但用户可能没有在设备上启用此功能。因此,检查可用性是最佳实践。《MFMessageComposeViewController》类包含一个名为<code>CanSendText</code>的静态方法,它返回一个布尔值,指示我们是否可以使用此功能。在这种情况下,重要的是我们应该在初始化控制器之前检查发送短信是否可用。这是因为当你在不支持短信的设备或模拟器上尝试初始化控制器时,你将在屏幕上看到以下消息:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_08_04.jpg"></p>
<p>为了确定用户在消息 UI 中采取了哪些操作,我们实现一个<code>Delegate</code>对象并重写<code>Finished</code>方法:</p>
<pre><code class="language-swift">private class MessageComposerDelegate : MFMessageComposeViewControllerDelegate
</code></pre>
<p>MonoTouch 提供的另一个选项是订阅<code>MFMessageComposeViewController</code>类的<code>Finished</code>事件。</p>
<p>在<code>Finished</code>方法中,我们可以根据<code>MessageComposeResult</code>参数提供功能。它的值可以是以下三个之一:</p>
<ol>
<li>
<p><code>Sent:</code> 这个值表示消息已成功发送。</p>
</li>
<li>
<p><code>Cancelled:</code> 这个值表示用户点击了<strong>取消</strong>按钮,消息将不会发送。</p>
</li>
<li>
<p><code>Failed:</code> 这个值表示消息发送失败。</p>
</li>
</ol>
<p>最后要做的就是取消消息控制器,操作如下:</p>
<pre><code class="language-swift">controller.DismissModalViewControllerAnimated(true);
</code></pre>
<p>在初始化控制器后,我们可以将收件人和正文消息设置到相应的属性中:</p>
<pre><code class="language-swift">this.messageController.Recipients = new string[] { "+123456789012" };
this.messageController.Body = "Text from MonoTouch";
</code></pre>
<p><code>Recipients</code>属性接受一个字符串数组,允许有多个收件人号码。</p>
<p>你可能已经注意到,消息控制器的<code>Delegate</code>对象被设置为它的<code>MessageComposeDelegate</code>属性,而不是常见的<code>Delegate</code>。这是因为<code>MFMessageComposeViewController</code>类直接继承自<code>UINavigationController</code>类,所以<code>Delegate</code>属性接受<code>UINavigationControllerDelegate</code>类型的值。</p>
<h2 id="还有更多-29">还有更多...</h2>
<p>SDK 提供了发送文本消息的用户界面,并不意味着它是可定制的。就像调用原生消息应用一样,是否发送消息或丢弃消息的决定权在用户手中。实际上,在控制器显示在屏幕上之后,任何尝试更改实际对象或其任何属性的操作都将失败。此外,用户可以更改或删除收件人和消息正文。真正的优势在于,消息用户界面是在我们的应用程序内显示的,而不是单独运行。</p>
<h3 id="仅限短信">仅限短信</h3>
<p><code>MFMessageComposeViewController</code>只能用于发送<strong>短消息服务(SMS)</strong>消息,而不能发送<strong>多媒体消息服务(MMS)</strong>。</p>
<h1 id="在我们的应用程序中使用电子邮件消息">在我们的应用程序中使用电子邮件消息</h1>
<p>在这个菜谱中,我们将学习如何在应用程序中使用电子邮件消息界面。</p>
<h2 id="准备工作-57">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>EmailMessageApp</code>。</p>
<h2 id="如何做到这一点-10">如何做到这一点...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加一个按钮,并在<code>MainController.cs</code>文件中的<code>MonoTouch.MessageUI</code>命名空间中。</p>
</li>
<li>
<p>在<code>ViewDidLoad</code>方法中输入以下代码:</p>
<pre><code class="language-swift">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!");
}
} ;
</code></pre>
</li>
<li>
<p>添加以下方法:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>发送电子邮件</strong>按钮以显示邮件用户界面。发送或取消消息。应用程序将在模拟器上工作,并且行为与设备上的原生邮件应用相同,只是消息实际上不会发送或保存。</p>
</li>
</ol>
<h2 id="它是如何工作的-47">它是如何工作的...</h2>
<p><code>MFMailComposeViewController</code>类提供了原生邮件编写界面。为了确定设备是否能够发送电子邮件,我们首先检查其<code>CanSendMail</code>属性。</p>
<p>与<code>MFMessageComposeViewController</code>类似,它包含一个<code>Finished</code>事件,我们使用它来响应用户操作,而无需实现<code>Delegate</code>对象。我们通过<code>MailController_Finished</code>方法来实现,基于<code>MFComposeResultEventArgs.Result</code>属性,该属性的类型为<code>MFMailComposeResult</code>。其可能的值将包括以下之一:</p>
<ul>
<li>
<p><code>Sent:</code> 这个值表示电子邮件消息已排队待发送</p>
</li>
<li>
<p><code>Saved:</code> 这个值表示用户点击了<strong>取消</strong>按钮,动作表单中的<strong>保存草稿</strong>选项自动出现</p>
</li>
</ul>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_08_05.jpg"></p>
<ul>
<li>
<p><code>Cancelled:</code> 此值表示用户在控制器上点击 <strong>取消</strong> 按钮并在动作表中选择了 <strong>删除草稿</strong> 选项</p>
</li>
<li>
<p><code>Failed:</code> 此值表示电子邮件消息发送失败</p>
</li>
</ul>
<p>在初始化对象之后,我们可以通过相应的 <code>Set</code> 前缀方法来分配收件人列表、主题和消息正文:</p>
<pre><code class="language-swift">this.mailController.SetToRecipients(new string[] { "recipient@example.com" });
this.mailController.SetSubject("Email from MonoTouch!");
this.mailController.SetMessageBody("This is the message body!", false);
</code></pre>
<p><code>SetMessageBody</code> 消息的第二个参数,如果设置为 <code>true</code>,则通知控制器该消息应被视为 HTML 格式。</p>
<h2 id="更多内容-25">更多内容...</h2>
<p>除了简单的或 HTML 格式的文本外,我们还可以发送附件。我们可以使用 <code>AddAttachmentData</code> 方法来完成此操作:</p>
<pre><code class="language-swift">this.mailController.AddAttachmentData(UIImage.FromFile("image.jpg"). AsJPEG(), "image/jpg", "image.jpg");
</code></pre>
<p>第一个参数是 <code>NSData</code> 类型,应包含附件的内容。在这种情况下,我们通过 <code>UIImage.AsJPEG()</code> 方法附加一个图片,该方法返回一个包含在 <code>NSData</code> 对象中的图片内容。第二个参数代表附件的 <strong>Multipurpose Internet Mail Extensions (MIME)</strong> 类型,第三个参数是文件名。项目源代码包含一个完整并带有注释的示例。</p>
<h3 id="草稿动作表">草稿动作表</h3>
<p>当用户点击 <strong>取消</strong> 按钮时显示的动作表由 <code>MFMailComposeViewController</code> 自动处理。</p>
<h2 id="相关内容-16">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>在我们的应用程序中使用短信</em></li>
</ul>
<h1 id="管理地址簿">管理地址簿</h1>
<p>在本食谱中,我们将讨论如何访问和管理设备地址簿中存储的用户联系人。</p>
<h2 id="准备中-3">准备中</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>AddressBookApp</code>。</p>
<h2 id="如何操作-29">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个按钮。在 <code>MainController.cs</code> 文件中输入以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using MonoTouch.AddressBook;
</code></pre>
</li>
<li>
<p>覆盖 <code>ViewDidLoad</code> 方法:</p>
<pre><code class="language-swift">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));
}
} ;
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>获取联系人</strong> 按钮,并观察联系人名称在 MonoDevelop 的 <strong>应用程序输出</strong> 面板中显示。</p>
</li>
</ol>
<h3 id="注意-37">注意</h3>
<p>在安装 iOS SDK 之后,模拟器不包含任何联系人。您可以像在设备上一样添加联系人。</p>
<h2 id="如何工作-3">如何工作...</h2>
<p><code>MonoTouch.AddressBook</code> 命名空间包含所有允许我们管理设备地址簿的类。要直接访问数据,我们需要 <code>ABAddressBook</code> 类的一个实例:</p>
<pre><code class="language-swift">ABAddressBook addressBook = new ABAddressBook();
</code></pre>
<p>要获取地址簿中存储的所有联系人,我们调用其 <code>GetPeople()</code> 方法:</p>
<pre><code class="language-swift">ABPerson[] contacts = addressBook.GetPeople();
</code></pre>
<p>此方法返回一个 <code>ABPerson</code> 对象数组,其中包含所有单个联系人的信息。要读取联系人的详细信息,我们遍历 <code>ABPerson</code> 数组,并使用 <code>FirstName</code> 和 <code>LastName</code> 属性分别获取每个联系人的名和姓:</p>
<pre><code class="language-swift">Console.WriteLine(string.Format("{0} {1}", eachPerson.LastName, eachPerson.FirstName));
</code></pre>
<h2 id="更多内容-26">更多内容...</h2>
<p>要获取联系人的存储电话号码,请调用 <code>GetPhones()</code> 方法:</p>
<pre><code class="language-swift">ABMultiValue<string> phones = eachPerson.GetPhones();
Console.WriteLine(phones.Value);
</code></pre>
<p>它返回一个 <code>ABMultiValue<string></code> 类型的对象。<code>ABMultiValue<T></code> 是一个泛型集合,特别设计用于地址簿的多个值。</p>
<h3 id="向联系人添加电话号码">向联系人添加电话号码</h3>
<p>要向联系人添加电话号码,我们可以使用<code>ABPerson</code>类的<code>SetPhones</code>方法。它接受一个<code>ABMultiValue<string></code>对象作为其参数,但我们不能向<code>ABMultiValue</code>对象添加新值。然而,我们可以将值写入<code>ABMutableMultiValue<T></code>对象:</p>
<pre><code class="language-swift">ABMutableMultiValue<string> newPhones = phones.ToMutableMultiValue();
</code></pre>
<p>这行代码创建了一个新的<code>ABMutableMultiValue<string></code>对象实例,然后我们使用它来添加我们想要的电话号码:</p>
<pre><code class="language-swift">newPhones.Add("+120987654321", ABPersonPhoneLabel.iPhone);
eachPerson.SetPhones(newPhones);
addressBook.Save();
</code></pre>
<p><code>Add</code>方法的第二个参数是电话号码在保存到联系人时将拥有的标签。调用<code>ABAddressBook.Save()</code>方法很重要,否则更改将不会保存。</p>
<h1 id="显示联系人">显示联系人</h1>
<p>在这个菜谱中,我们将学习如何使用原生地址簿用户界面来显示联系人信息。</p>
<h2 id="准备工作-58">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>DisplayContactApp</code>。在<code>MainController</code>的视图中添加一个按钮。</p>
<h2 id="如何做到这一点-11">如何做到这一点...</h2>
<ol>
<li>
<p>在<code>AppDelegate</code>类中为<code>UINavigationController</code>创建一个字段:</p>
<pre><code class="language-swift">UINavigationController navController;
</code></pre>
</li>
<li>
<p>实例化导航控制器,将其根控制器传递为<code>MainController</code>的实例:</p>
<pre><code class="language-swift">this.navController = new UINavigationController(new MainController());
</code></pre>
</li>
<li>
<p>将导航控制器设置为窗口的根视图控制器:</p>
<pre><code class="language-swift">window.RootViewController = this.navController;
</code></pre>
</li>
<li>
<p>在<code>MainController.cs</code>文件中添加命名空间<code>MonoTouch.AddressBook</code>和<code>MonoTouch.AddressBookUI</code>。</p>
</li>
<li>
<p>覆盖<code>MainController</code>类的<code>ViewDidLoad</code>方法,并使用以下代码实现:</p>
<pre><code class="language-swift">ABAddressBook addressBook = new ABAddressBook();
ABPerson[] contacts = addressBook.GetPeople();
ABPersonViewController personController = new ABPersonViewController();
personController.DisplayedPerson = contacts;
this.buttonDisplayContact.TouchUpInside += delegate {
this.NavigationController.PushViewController( personController, true);
} ;
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>显示第一个联系人</strong>按钮以显示联系人详细信息。</p>
</li>
</ol>
<h2 id="它是如何工作的-48">它是如何工作的...</h2>
<p><code>MonoTouch.AddressBookUI</code>命名空间包含原生<code>Contacts</code>应用程序使用的控制器,允许用户显示和管理联系人。每个联系人的详细信息都可以使用<code>ABPersonViewController</code>查看。此控制器必须通过<code>UINavigationController</code>来展示,否则将无法正确显示。</p>
<p>初始化后,我们将想要显示的<code>ABPerson</code>对象设置为其<code>DisplayedPerson</code>属性:</p>
<pre><code class="language-swift">ABPersonViewController personController = new ABPersonViewController();
personController.DisplayedPerson = contacts;
</code></pre>
<p>然后,我们将其推送到导航控制器的堆栈中:</p>
<pre><code class="language-swift">this.NavigationController.PushViewController(personController, true);
</code></pre>
<h2 id="还有更多-30">还有更多...</h2>
<p><code>ABPersonViewController</code>也可以用于编辑。为此,将<code>AllowsEditing</code>属性设置为<code>true</code>:</p>
<pre><code class="language-swift">personController.AllowsEditing = true;
</code></pre>
<p>结果将与原生<code>Contacts</code>应用程序完全相同:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_08_06.jpg"></p>
<p>注意,更改通常通过<code>ABPersonViewController</code>保存。</p>
<h3 id="其他地址簿控制器">其他地址簿控制器</h3>
<p><code>MonoTouch.AddressBookUI</code>命名空间包含我们创建自定义联系人应用程序所需的所有控制器:</p>
<ul>
<li>
<p><code>ABPeoplePickerNavigationController</code>:这是一个显示已保存联系人的导航控制器。用户可以从列表中选择一个联系人。</p>
</li>
<li>
<p><code>ABPersonViewController</code>:这个控制器在之前的示例中有描述。</p>
</li>
<li>
<p><code>ABNewPersonViewController</code>:这是创建新联系人的控制器。</p>
</li>
<li>
<p><code>ABUnknownPersonViewController:</code> 这是用于创建新联系人的部分数据显示的控制器。这与我们在设备上最近通话列表中点击未知号码时显示的控制器类似。</p>
</li>
</ul>
<h2 id="相关内容-17">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>管理地址簿</em></li>
</ul>
<h1 id="管理日历">管理日历</h1>
<p>在本食谱中,我们将学习如何创建一个事件并将其保存到设备的日历数据库中。</p>
<h2 id="准备工作-59">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>CalendarEventsApp</code>。</p>
<h3 id="注意-38">注意</h3>
<p>此项目必须在设备上执行。本机 <code>Calendar</code> 应用程序未安装在模拟器上。</p>
<h2 id="如何操作-30">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的主视图中添加一个按钮。在 <code>MainController.cs</code> 文件中添加命名空间 <code>MonoTouch.EventKit</code>。</p>
</li>
<li>
<p>最后,在 <code>ViewDidLoad</code> 方法中输入以下代码:</p>
<pre><code class="language-swift">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);
}
} );
} ;
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>显示事件</strong> 按钮以在 <strong>应用程序输出</strong> 面板中输出未来 30 天的日历事件。</p>
</li>
</ol>
<h2 id="工作原理-11">工作原理...</h2>
<p><code>MonoTouch.EventKit</code> 命名空间负责管理日历事件。为了读取已存储的事件,我们首先初始化一个 <code>EKEventStore</code> 对象:</p>
<pre><code class="language-swift">EKEventStore evStore = new EKEventStore();
</code></pre>
<p><code>EKEventStore</code> 类为我们提供了访问已存储事件的权限。要检索日历事件,我们需要一个类型为 <code>NSPredicate</code> 的谓词。我们可以通过 <code>EKEventStore</code> 类的 <code>PredicateForEvents</code> 方法创建一个实例:</p>
<pre><code class="language-swift">NSPredicate evPredicate = evStore.PredicateForEvents(DateTime.Now, DateTime.Now.AddDays(30), evStore.Calendars);
</code></pre>
<p>前两个参数的类型为 <code>NSDate</code>(可以隐式转换为 <code>DateTime</code>),表示要搜索事件的开始和结束日期。第三个参数的类型为 <code>EKCalendar[]</code>,它是要搜索的日历数组。要搜索所有可用的日历,我们传递 <code>EKEventStore.Calendars</code> 属性。</p>
<p>最后,我们调用 <code>EnumerateEvents</code> 方法:</p>
<pre><code class="language-swift">evStore.EnumerateEvents(evPredicate, delegate(EKEvent calEvent, ref bool stop) {
//...
</code></pre>
<p>我们将之前创建的谓词传递给第一个参数。第二个参数是类型为 <code>EKEventSearchCallback</code> 的委托。为了读取每个事件的数据,我们使用其 <code>EKEvent</code> 对象。请注意,枚举日历事件的过程与在上一章中讨论的从资产库枚举资产的过程类似。</p>
<h2 id="更多信息-1">更多信息...</h2>
<p>除了枚举事件外,<code>EKEventStore</code> 允许我们创建新的事件。以下示例创建并保存了一个新的日历事件:</p>
<pre><code class="language-swift">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);
</code></pre>
<p>为了创建一个新的 <code>EKEvent</code> 实例,我们使用 <code>EKEvent.FromStore</code> 静态方法。然后我们设置开始和结束日期、标题以及事件将要存储的日历。在这里,我们使用 <code>EKEventStore.DefaultCalendarForNewEvents</code> 属性可以获取的默认日历。当一切设置完毕后,我们调用 <code>SaveEvent</code> 方法来保存它。</p>
<h3 id="关于日历的信息">关于日历的信息</h3>
<p>默认情况下,设备已设置两个日历:<code>Home</code> 和 <code>Work</code>。尽管我们无法在设备上创建新日历,但我们在用于同步设备的计算机上创建的新日历在同步时将自动添加。</p>
<h2 id="相关内容-18">相关内容</h2>
<p>在本书中:</p>
<p>第七章, 多媒体资源:</p>
<ul>
<li><em>直接管理专辑项目</em></li>
</ul>
<h1 id="第九章与设备硬件交互">第九章.与设备硬件交互</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>检测设备方向</p>
</li>
<li>
<p>调整 UI 方向</p>
</li>
<li>
<p>接近传感器</p>
</li>
<li>
<p>获取电池信息</p>
</li>
<li>
<p>处理运动事件</p>
</li>
<li>
<p>处理触摸事件</p>
</li>
<li>
<p>识别手势</p>
</li>
<li>
<p>自定义手势</p>
</li>
<li>
<p>使用加速度计</p>
</li>
<li>
<p>使用陀螺仪</p>
</li>
</ul>
<h1 id="简介-8">简介</h1>
<p>今天的移动设备配备了非常先进的硬件。无论是加速度计来检测运动和方向,接近传感器,GPS 模块,以及许多其他组件,多触控屏幕相当复杂。</p>
<p>在本章中,我们将重点关注如何在我们的应用程序中使用这些硬件,为用户提供一个扩展到 3D 世界的体验。具体来说,我们将讨论如何根据设备的位置调整用户界面方向,如何使用接近传感器,以及读取电池信息。在一系列四个任务中,我们将学习如何捕获屏幕上的用户触摸并识别手势。</p>
<p>最后但同样重要的是,我们将创建高级应用程序,从加速度计和陀螺仪传感器读取原始数据以检测设备运动和旋转,并提供详细且简单的指南。</p>
<h1 id="检测设备方向">检测设备方向</h1>
<p>在这个菜谱中,我们将学习如何制作一个能够感知设备方向变化的应用程序。</p>
<h2 id="准备工作-60">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>DeviceOrientationApp</code>。</p>
<h2 id="如何做到这一点-12">如何做到这一点...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加一个标签。在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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();
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>通过在 Mac 上按住<strong>Command</strong>键并按左或右箭头键来旋转模拟器。</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_09_01.jpg"></p>
<h2 id="它是如何工作的-49">它是如何工作的...</h2>
<p>虽然模拟器缺少加速度计硬件,但它支持方向变化的通知。</p>
<p>可以通过<code>UIDevice.CurrentDevice</code>单例对象访问设备方向通知机制。为了接收通知,我们首先需要指示运行时发出它们。我们使用以下方法来完成此操作:</p>
<pre><code class="language-swift">UIDevice.CurrentDevice. BeginGeneratingDeviceOrientationNotifications();
</code></pre>
<p>此方法打开加速度计并开始生成方向通知。然后我们需要开始观察通知,以便响应变化:</p>
<pre><code class="language-swift">this.orientationObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIDevice.OrientationDidChangeNotification, delegate {
this.lblOrientation.Text = UIDevice.CurrentDevice.Orientation.ToString();
} );
</code></pre>
<p>每当设备方向改变时,观察者触发匿名方法。在其中,我们将从<code>Orientation</code>属性获取的方向输出到标签。</p>
<p><code>ViewDidUnload</code>方法是在视图控制器卸载其视图时被调用的方法。在其中,我们确保移除方向观察者,并指示运行时停止生成方向通知:</p>
<pre><code class="language-swift">NSNotificationCenter.DefaultCenter. RemoveObserver(this.orientationObserver);
UIDevice.CurrentDevice.EndGeneratingDeviceOrientationNotifications();
</code></pre>
<h2 id="还有更多-31">还有更多...</h2>
<p><code>Orientation</code>属性返回类型为<code>UIDeviceOrientation</code>的枚举。其值如下:</p>
<ul>
<li>
<p><code>Unknown:</code> 此值指定设备方向未知</p>
</li>
<li>
<p><code>Portrait:</code> 此值指定设备处于正常的纵向方向,<strong>主页</strong>按钮在底部</p>
</li>
<li>
<p><code>PortraitUpsideDown:</code> 这个值指定设备处于颠倒的纵向方向,<strong>主页</strong>按钮在顶部</p>
</li>
<li>
<p><code>LandscapeLeft:</code> 这个值指定设备处于横向方向,<strong>主页</strong>按钮在左侧</p>
</li>
<li>
<p><code>LandscapeRight:</code> 这个值指定设备处于横向方向,<strong>主页</strong>按钮在右侧</p>
</li>
<li>
<p><code>FaceUp:</code> 这个值指定设备与地面平行,屏幕向上</p>
</li>
<li>
<p><code>FaceDown:</code> 这个值指定设备与地面平行,屏幕向下</p>
</li>
</ul>
<p><code>FaceUp</code>和<code>FaceDown</code>是模拟器上无法复制的两个值。</p>
<h3 id="设备方向和用户界面方向">设备方向和用户界面方向</h3>
<p>在这个例子中可以明显看出,设备方向和用户界面方向之间存在差异。如果设备旋转,标签会更新为新方向值,但用户界面不会对变化做出响应。在下一个菜谱中,我们将讨论如何旋转用户界面。</p>
<h2 id="参见-41">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>调整 UI 方向</em></p>
</li>
<li>
<p><em>使用加速度计</em></p>
</li>
</ul>
<h1 id="调整-ui-方向">调整 UI 方向</h1>
<p>在这个菜谱中,我们将学习如何根据屏幕方向旋转<strong>用户界面 (UI)</strong>。</p>
<h2 id="准备工作-61">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>UIOrientationApp</code>。</p>
<h2 id="如何操作-31">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>视图上添加一个标签。在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">public override bool ShouldAutorotateToInterfaceOrientation ( UIInterfaceOrientation toInterfaceOrientation){
return true;
}
public override void DidRotate ( UIInterfaceOrientation fromInterfaceOrientation){
base.DidRotate (fromInterfaceOrientation);
this.lblOutput.Text = this.InterfaceOrientation.ToString();
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>使用<em>Command</em> + 方向键旋转模拟器。以下图像显示了<strong>LandscapeRight</strong>方向:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_09_02.jpg"></p>
<h2 id="它是如何工作的-50">它是如何工作的...</h2>
<p>要使我们的 UI 适应设备方向,我们只需要重写视图控制器的<code>ShouldAutorotateToInterfaceOrientation</code>方法:</p>
<pre><code class="language-swift">public override bool ShouldAutorotateToInterfaceOrientation ( UIInterfaceOrientation toInterfaceOrientation)
</code></pre>
<p>当视图控制器加载时,它会检查每个可用方向的方法结果。第一次接收到<code>true</code>时,它会自动将界面旋转到该方向。加载后,每次设备旋转时,都会重复相同的流程。</p>
<p>方法参数是类型为<code>UIInterfaceOrientation</code>的枚举,每次方法被调用时,它包含界面检查的方向值。</p>
<p>在界面方向完成旋转后,会调用<code>DidRotate</code>方法。我们使用<code>UIViewController.InterfaceOrientation</code>属性,它包含视图控制器当前方向的信息,来更新标签:</p>
<pre><code class="language-swift">this.lblOutput.Text = this.InterfaceOrientation.ToString();
</code></pre>
<h2 id="更多内容-27">更多内容...</h2>
<p>从<code>ShouldAutorotateToInterfaceOrientation</code>方法返回<code>true</code>意味着界面将在所有设备方向上旋转。在大多数情况下,这并不是必要的,甚至应该根据我们的应用程序设计避免。要使我们的界面仅旋转到横向方向,方法应实现如下:</p>
<pre><code class="language-swift">public override bool ShouldAutorotateToInterfaceOrientation ( UIInterfaceOrientation toInterfaceOrientation){
return toInterfaceOrientation == UIInterfaceOrientation.LandscapeLeft || toInterfaceOrientation == UIInterfaceOrientation.LandscapeRight;
}
</code></pre>
<p>注意,此实现将强制 UI 以横屏模式加载。</p>
<h3 id="模拟器中的用户界面方向">模拟器中的用户界面方向</h3>
<p>如果你只实现<code>ShouldAutorotateToInterfaceOrientation</code>方法以支持横屏方向,那么加载模拟器“设备”的控制器也将以横屏方向旋转。然而,这只是为了方便,因为如果你检查<code>UIDevice.CurrentDevice.Orientation</code>属性,其值将是<code>UIDeviceOrientation.Portrait</code>。</p>
<h2 id="参见-42">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>检测设备方向</em></p>
</li>
<li>
<p><em>使用加速度计</em></p>
</li>
</ul>
<h1 id="接近传感器">接近传感器</h1>
<p>在这个菜谱中,我们将讨论使用接近传感器来禁用设备屏幕。</p>
<h2 id="准备工作-62">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>ProximitySensorApp</code>。</p>
<h3 id="注意-39">注意</h3>
<p>模拟器不支持接近传感器。</p>
<h2 id="如何做到这一点-13">如何做到这一点...</h2>
<p>对于这个任务,除了<code>MainController</code>本身之外,不需要任何控件。</p>
<ol>
<li>
<p>声明一个<code>NSObject</code>字段,它将包含通知观察者:</p>
<pre><code class="language-swift">private NSObject proximityObserver;
</code></pre>
</li>
<li>
<p>在<code>ViewDidLoad</code>重写方法中输入以下代码:</p>
<pre><code class="language-swift">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);
} );
}
</code></pre>
</li>
<li>
<p>最后,在<code>ViewDidUnload</code>重写方法中输入以下代码:</p>
<pre><code class="language-swift">if (UIDevice.CurrentDevice.ProximityMonitoringEnabled){
NSNotificationCenter.DefaultCenter. RemoveObserver(this.proximityObserver);
UIDevice.CurrentDevice.ProximityMonitoringEnabled = false;
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>将手指放在接近传感器上,或者就像在通话时那样,将其靠近你的耳朵。观察 MonoDevelop 中的<strong>应用程序输出</strong>面板显示传感器的状态。</p>
</li>
</ol>
<h2 id="它是如何工作的-51">它是如何工作的...</h2>
<p>尽管接近传感器的功能相当简单,但它提供了一个非常重要的特性。iOS 设备正面只有一个按钮,那就是<strong>主</strong>按钮。几乎每一次用户与设备的交互都是基于触摸屏的。这在 iPhone 上造成了一个问题:除了它的多种功能外,它也是一个电话。这意味着它很可能会在用户的面部侧面花费一些时间来进行通话。</p>
<p>为了避免意外触碰到虚拟按钮,当手机应用程序运行时,接近传感器会被激活,以在设备靠近用户的耳朵或传感器上方的任何物体时禁用屏幕。</p>
<p>要启用接近传感器,将<code>UIDevice.CurrentDevice.ProximityMonitoringEnabled</code>属性的值设置为<code>true</code>:</p>
<pre><code class="language-swift">UIDevice.CurrentDevice.ProximityMonitoringEnabled = true;
</code></pre>
<p>如果设备不支持接近传感器,即使将其设置为<code>true</code>,此属性也将返回<code>false</code>。因此,在将其设置为<code>true</code>之后,我们可以检查它以查看设备是否支持传感器:</p>
<pre><code class="language-swift">if (UIDevice.CurrentDevice.ProximityMonitoringEnabled)
</code></pre>
<p>在检查后,我们可以使用<code>UIDevice.ProximityStateDidChangeNotification</code>键添加一个观察者来通知传感器的状态:</p>
<pre><code class="language-swift">this.proximityObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIDevice.ProximityStateDidChangeNotification, delegate(NSNotification ntf) {
Console.WriteLine("Proximity state: {0}", UIDevice.CurrentDevice.ProximityState);
} );
</code></pre>
<p><code>ProximityState</code>属性返回<code>true</code>表示传感器已经关闭了屏幕,如果它重新打开屏幕则返回<code>false</code>。</p>
<h2 id="还有更多-32">还有更多...</h2>
<p>接近传感器的使用不仅限于电话功能。例如,如果你正在开发一个在设备放在用户的口袋或钱包中时可以执行一些工作的应用程序,启用接近传感器可以确保不会意外触碰到控制按钮。甚至可以通过仅关闭屏幕来节省电池电量。</p>
<h3 id="传感器支持">传感器支持</h3>
<p>并非所有设备都支持接近传感器。如果你针对各种 iOS 设备,请考虑传感器可能不会在所有设备上可用。</p>
<h2 id="相关内容-19">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>检索电池信息</em></li>
</ul>
<h1 id="检索电池信息">检索电池信息</h1>
<p>在这个菜谱中,我们将学习如何读取设备的充电状态及其电池使用情况。</p>
<h2 id="准备工作-63">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>BatteryInfoApp</code>。</p>
<h2 id="如何做到-4">如何做到...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个标签。在 <code>MainController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">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);
} );
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>应用程序加载后,断开并/或连接设备的 USB 线缆。观察标签上的电池状态。</p>
</li>
</ol>
<h2 id="它是如何工作的-52">它是如何工作的...</h2>
<p>我们可以通过 <code>UIDevice</code> 类检索电池信息。我们必须做的第一件事是启用电池监控:</p>
<pre><code class="language-swift">UIDevice.CurrentDevice.BatteryMonitoringEnabled = true;
</code></pre>
<p>在不支持电池监控的模拟器上,即使我们将其设置为 true,此属性也将返回 false。</p>
<p>我们可以通过 <code>UIDevice.BatteryStateDidChangeNotification</code> 键添加一个观察者来监听电池状态变化通知,如之前高亮显示的代码所示。可以通过 <code>BatteryState</code> 属性检索电池状态:</p>
<pre><code class="language-swift">this.lblOutput.Text = string.Format("Battery state: {0}", UIDevice.CurrentDevice.BatteryState);
</code></pre>
<p><code>BatteryState</code> 属性的可能值包括:</p>
<ul>
<li>
<p><code>Unknown:</code> 此值指定无法确定电池状态,或者电池监控被禁用</p>
</li>
<li>
<p><code>Unplugged:</code> 此值指定设备正在使用电池供电</p>
</li>
<li>
<p><code>Charging:</code> 此值指定设备电池正在充电,并且 USB 线缆已连接</p>
</li>
<li>
<p><code>Full:</code> 此值指定设备电池已满电,并且 USB 线缆已连接</p>
</li>
</ul>
<h2 id="更多内容-28">更多内容...</h2>
<p>除了电池状态外,我们还可以获取其电量信息。为此,我们需要为 <code>UIDevice.BatteryLevelDidChangeNotification</code> 键添加一个观察者:</p>
<pre><code class="language-swift">private NSObject batterLevelChangeObserver;
//...
this.batterLevelChangeObserver = NSNotificationCenter.DefaultCenter .AddObserver(UIDevice.BatteryLevelDidChangeNotification, delegate(NSNotification ntf) {
this.lblOutput.Text = string.Format("Battery level: {0}", UIDevice.CurrentDevice.BatteryLevel);
} );
</code></pre>
<p><code>BatteryLevel</code> 属性返回一个范围从 <code>0.0</code>(电池耗尽)到 <code>1.0</code>(电池满电 100%)的浮点值。如果电池监控被禁用,它将返回 <code>-1.0</code> 的值。</p>
<h3 id="禁用电池监控">禁用电池监控</h3>
<p>在不需要时始终禁用电池监控。实际的监控机制本身会消耗电池电量。</p>
<h2 id="相关内容-20">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>接近传感器</em></li>
</ul>
<h1 id="处理运动事件">处理运动事件</h1>
<p>在这个菜谱中,我们将学习如何拦截和响应摇动手势。</p>
<h2 id="准备工作-64">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>MotionEventsApp</code>。</p>
<h2 id="如何做到-5">如何做到...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个标签。在 <code>MainController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">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!";
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>摇动设备并观察标签上的输出。你还可以在模拟器上测试这个应用程序。</p>
</li>
<li>
<p>加载完成后,点击菜单栏上的 <strong>硬件 | 摇动手势</strong>。</p>
</li>
</ol>
<h2 id="它是如何工作的-53">它是如何工作的...</h2>
<p>通过重写<code>UIViewController</code>类的运动方法,我们可以拦截并响应用户系统发送的运动事件。尽管如此,仅仅重写这些方法是不够的。为了使控制器接收运动事件,它需要成为第一个响应者。为了确保这一点,我们首先重写<code>CanBecomeFirstResponder</code>属性并从中返回<code>true</code>:</p>
<pre><code class="language-swift">public override bool CanBecomeFirstResponder{
get { return true; }
}
</code></pre>
<p>然后,我们通过在<code>ViewDidAppear</code>重写方法中调用<code>BecomeFirstResponder</code>方法来确保当其视图出现时,我们的控制器成为第一个响应者:</p>
<pre><code class="language-swift">public override void ViewDidAppear (bool animated){
base.ViewDidAppear (animated);
this.BecomeFirstResponder();
}
</code></pre>
<p><code>ViewDidAppear</code>方法在视图出现在屏幕上之后被调用。</p>
<p>系统确定一个运动是否是摇动手势,并调用适当的方法。我们可以使用以下三个方法来重写并捕获摇动手势:</p>
<ul>
<li>
<p><code>MotionBegan:</code> 此方法指定摇动动作开始</p>
</li>
<li>
<p><code>MotionEnded:</code> 此方法指定摇动动作结束</p>
</li>
<li>
<p><code>MotionCancelled:</code> 此方法指定摇动动作取消</p>
</li>
</ul>
<p>当设备开始移动时,会调用<code>MotionBegan</code>方法。如果运动持续大约一秒或更短,则调用<code>MotionEnded</code>方法。如果持续时间更长,系统将其分类为非摇动手势,并调用<code>MotionCancelled</code>方法。当我们在应用程序中实现摇动手势时,建议重写所有三个方法并相应地做出反应。</p>
<h2 id="更多-6">更多...</h2>
<p>只有从继承自<code>UIResponder</code>类的对象才会发送运动事件。这包括<code>UIView</code>和<code>UIViewController</code>类。</p>
<h3 id="更多信息运动事件">更多信息:运动事件</h3>
<p>运动事件机制相当简单。它仅检测几乎瞬间的设备摇动,而不提供有关其方向或速率的任何信息。为了根据不同的特性处理运动事件,可以将加速度计与组合使用。</p>
<h2 id="相关内容-21">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>使用加速度计</em></li>
</ul>
<h1 id="处理触摸事件">处理触摸事件</h1>
<p>在本食谱中,我们将学习如何拦截和响应用户触摸。</p>
<h2 id="准备工作-65">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>TouchEventsApp</code>。</p>
<h2 id="如何实现-7">如何实现...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加一个标签,并在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>在模拟器屏幕上使用光标进行侧向点击和拖动,并观察视图的背景颜色逐渐从白色变为黑色。请注意,在模拟器屏幕上使用光标点击相当于用手指触摸设备的屏幕。</p>
</li>
</ol>
<h2 id="它是如何工作的-54">它是如何工作的...</h2>
<p>为了响应用户的触摸,作为触摸接收器的对象必须将其 <code>UserInteractionEnabled</code> 属性设置为 <code>true</code>。几乎每个对象默认都启用了用户交互,除非其主要用途不是直接用于用户交互,例如,<code>UILabel</code> 和 <code>UIImageView</code>。我们需要明确地将 <code>UserInteractionEnabled</code> 设置到这些对象上。除此之外,可以处理触摸事件的对象必须继承自 <code>UIResponder</code> 类。请注意,尽管 <code>UIViewController</code> 类继承自 <code>UIResponder</code>,因此可以捕获触摸事件,但它没有 <code>UserInteractionEnabled</code> 属性,而是其主要的 <code>UIView</code> 属性控制着触摸事件的传递。这意味着,如果你重写了 <code>UIViewController</code> 的触摸方法,但它的视图的 <code>UserInteractionEnabled</code> 属性设置为 <code>false</code>,则这些方法将不会响应用户的触摸。</p>
<p>负责处理触摸事件的以下方法:</p>
<ul>
<li>
<p><code>TouchesBegan:</code> 当用户触摸屏幕时,会调用此方法。</p>
</li>
<li>
<p><code>TouchesMoved:</code> 当用户在屏幕上拖动手指时,会调用此方法。</p>
</li>
<li>
<p><code>TouchesEnded:</code> 当用户从屏幕上抬起手指时,会调用此方法。</p>
</li>
<li>
<p><code>TouchesCancelled:</code> 当触摸事件被系统事件取消时,会调用此方法,例如,当显示通知警报时。</p>
</li>
</ul>
<p>整个项目可以在下载的源代码中找到。<code>TouchesMoved</code> 方法的实现在此处解释。</p>
<p>每个触摸方法都有两个参数。第一个参数是 <code>NSSet</code> 类型,包含 <code>UITouch</code> 对象。<code>NSSet</code> 类表示对象的集合,而 <code>UITouch</code> 类保存每个用户触摸的信息。第二个参数是 <code>UIEvent</code> 类型,包含实际事件的详细信息。</p>
<p>我们可以通过 <code>NSSet.AnyObject</code> 返回值检索与实际触摸相关的 <code>UITouch</code> 对象:</p>
<pre><code class="language-swift">UITouch touch = touches.AnyObject as UITouch;
</code></pre>
<p>它返回一个 <code>NSObject</code> 类型的对象,我们将它转换为 <code>UITouch</code>。我们可以通过以下方法获取触摸的先前和当前位置:</p>
<pre><code class="language-swift">PointF previousLocation = touch.PreviousLocationInView(this.View);
PointF touchLocation = touch.LocationInView(this.View);
</code></pre>
<p>它们都返回一个包含触摸接收器坐标系中触摸位置的 <code>PointF</code> 结构体。在接收到触摸位置后,我们相应地调整背景颜色。</p>
<h2 id="更多-7">更多...</h2>
<p>此示例基于单个用户的触摸。为了使视图能够响应用户的多个触摸,我们必须将其 <code>MultipleTouchEnabled</code> 属性设置为 <code>true</code>。然后我们可以获取数组中的所有 <code>UITouch</code> 对象:</p>
<pre><code class="language-swift">UITouch[] allTouches = touches.ToArray<UITouch>();
</code></pre>
<h3 id="获取点击次数">获取点击次数</h3>
<p>我们可以通过 <code>ToucheEnded</code> 方法中的 <code>UITouch.TapCount</code> 属性确定连续用户点击的次数。</p>
<h2 id="参见-43">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>触摸事件</em></p>
</li>
<li>
<p><em>识别手势</em></p>
</li>
<li>
<p><em>自定义手势</em></p>
</li>
</ul>
<h1 id="识别手势">识别手势</h1>
<p>在本食谱中,我们将讨论如何识别触摸手势并相应地做出反应。</p>
<h2 id="准备工作-66">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>GestureApp</code>。</p>
<h2 id="如何做到这一点-14">如何做到这一点...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加一个标签。在<code>MainController</code>类的源文件中输入以下<code>using</code>指令:</p>
<pre><code class="language-swift">using MonoTouch.ObjCRuntime;
</code></pre>
</li>
<li>
<p>在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">public override void ViewDidLoad (){
base.ViewDidLoad ();
UIPinchGestureRecognizer pinchGesture = new UIPinchGestureRecognizer(this, new Selector("PinchHandler:"));
this.View.AddGestureRecognizer(pinchGesture);
}
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;
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>按住<strong>Option</strong>键,并用鼠标点击拖动以在模拟器屏幕上执行捏合动作的等效操作。</p>
</li>
</ol>
<h2 id="它是如何工作的-55">它是如何工作的...</h2>
<p>随着 iOS 3.2 版本的发布,iPad 一同推出,苹果引入了<code>UIGestureRecognizer</code>类及其派生类。手势识别器利用 iOS 设备上的多点触控屏幕。<strong>手势</strong>基本上是触摸组合,可以执行特定操作。</p>
<p>例如,在原生<strong>照片</strong>应用程序的全屏图像上捏合将缩小视图。捏合动作是用户执行的手势,而手势识别器负责识别并将手势事件传递给接收者。</p>
<p>在这个例子中,我们创建了一个<code>UIPinchGestureRecognizer</code>,它将识别屏幕上的捏合手势。其实例使用以下代码创建:</p>
<pre><code class="language-swift">UIPinchGestureRecognizer pinchGesture = new UIPinchGestureRecognizer(this, new Selector("PinchHandler:"));
</code></pre>
<p>初始化实例的构造函数接受两个参数。第一个参数是<code>NSObject</code>类型,它是将接收手势的目标对象。在这种情况下,它是<code>MainController</code>实例,我们使用<code>this</code>关键字传递。第二个参数是<code>MonoTouch.ObjCRuntime</code>命名空间中的类型,它表示当识别器接收到手势时将被调用的方法。简单来说,Objective-C 中的<code>Selector</code>基本上是一个方法签名。传递给其构造函数的字符串代表将被调用的 Objective-C 方法。</p>
<p>由于我们使用 C#,我们可以轻松地将一个方法暴露为 Objective-C 的<code>Selector</code>。我们只需创建我们想要的方法,并用<code>ExportAttribute</code>装饰它,确保传递给它的字符串与传递给<code>Selector</code>构造函数的字符串相同:</p>
<pre><code class="language-swift">
private void PinchHandler(UIGestureRecognizer gesture)
</code></pre>
<p>在方法内部,我们读取手势识别器对象的<code>State</code>属性,并相应地做出反应。</p>
<h2 id="更多内容-29">更多内容...</h2>
<p>每个手势识别器的状态由类型为<code>UIGestureRecognizerState</code>的枚举表示。其值包括:</p>
<ul>
<li>
<p><code>Possible:</code> 此值指定手势尚未被识别,并且是默认值</p>
</li>
<li>
<p><code>Began:</code> 此值指定手势已开始</p>
</li>
<li>
<p><code>Changed:</code> 此值指定手势已更改</p>
</li>
<li>
<p><code>Ended:</code> 此值指定手势已结束</p>
</li>
<li>
<p><code>Cancelled:</code> 此值指定手势已被取消</p>
</li>
<li>
<p><code>Failed:</code> 此值指定手势无法被识别</p>
</li>
<li>
<p><code>Recognized:</code> 此值指定手势已被识别</p>
</li>
</ul>
<h3 id="手势识别器的优势">手势识别器的优势</h3>
<p>手势识别器的优点是它们可以节省开发者创建自己的手势识别机制的时间,通过触摸事件。此外,它们基于用户在 iOS 设备上习惯使用的手势。</p>
<h2 id="相关内容-22">相关内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>触摸事件</em></p>
</li>
<li>
<p><em>自定义手势</em></p>
</li>
</ul>
<h1 id="自定义手势">自定义手势</h1>
<p>在这个食谱中,我们将学习如何创建一个自定义手势识别器来创建我们自己的手势。</p>
<h2 id="准备工作-67">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>CustomGestureApp</code>。</p>
<h2 id="如何操作-32">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 视图中添加一个标签。在 <code>MainController</code> 类中创建以下嵌套类:</p>
<pre><code class="language-swift">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;
}
}
}
</code></pre>
</li>
<li>
<p>使用前面食谱中展示的自定义手势识别器。</p>
</li>
</ol>
<h2 id="工作原理-12">工作原理...</h2>
<p>要创建一个手势识别器,声明一个继承自 <code>UIGestureRecognizer</code> 类的类。在这个例子中,我们创建一个手势,通过在屏幕上拖动手指指向左下角的一个 <code>50x50</code> 点矩形来识别这个手势。</p>
<pre><code class="language-swift">private class DragLowerLeftGesture : UIGestureRecognizer
</code></pre>
<p><code>UIGestureRecognizer</code> 类包含我们在视图中拦截触摸事件时使用的相同触摸方法。我们还可以通过其 <code>View</code> 属性访问它所添加的视图。在 <code>TouchesBegan</code> 方法中,我们确定初始触摸位置。如果它在视图的左下部分之外,我们将 <code>State</code> 属性设置为 <code>Began</code>。如果它在左下部分内部,我们将 <code>State</code> 属性设置为 <code>Failed</code>,这样就不会调用选择器。</p>
<p>在 <code>TouchesEnded</code> 方法中,如果触摸的位置在视图的左下部分内部,我们考虑手势为 <code>Ended</code>。如果没有,则手势识别被认为是 <code>Failed</code>。</p>
<p><code>TouchesMoved</code> 方法是设置 <code>Changed</code> 状态的地方。对于这个简单的手势识别器,我们不需要在其中添加其他逻辑。</p>
<h2 id="更多内容-30">更多内容...</h2>
<p>这是一个简单的手势识别器,它依赖于单个触摸。通过触摸方法提供的信息,我们可以创建更复杂的支持多个触摸的手势。</p>
<h3 id="自定义手势识别器的另一种用法">自定义手势识别器的另一种用法</h3>
<p>有些视图继承自 <code>UIView</code> 类,根据苹果开发者文档,这些视图不应该被子类化。<code>MKMapView</code> 就是这些视图之一,用于显示地图。如果我们想拦截这些视图的触摸事件,这会带来一个问题。虽然我们可以在其上方使用另一个视图并拦截其触摸事件,但这有点复杂。一个更简单的方法是创建一个简单的自定义手势识别器并将其添加到我们无法子类化的视图中。这样,我们就可以在不子类化的情况下拦截其触摸事件。</p>
<h2 id="相关内容-23">相关内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>识别手势</em></p>
</li>
<li>
<p><em>触摸事件</em></p>
</li>
</ul>
<h1 id="使用加速度计">使用加速度计</h1>
<p>在这个食谱中,我们将学习如何接收加速度计事件来创建一个能够感知设备运动的应用程序。</p>
<h2 id="准备工作-68">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>AccelerometerApp</code>。</p>
<h3 id="注意-40">注意</h3>
<p>模拟器不支持加速度计硬件。本例中的项目将在设备上正确运行。</p>
<h2 id="如何做-11">如何做...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加两个按钮和一个标签。</p>
</li>
<li>
<p>覆盖<code>ViewDidLoad</code>方法,并使用以下代码实现它:</p>
<pre><code class="language-swift">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;
} ;
</code></pre>
</li>
<li>
<p>在类中添加以下方法:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>开始加速度计</strong>按钮,在移动或摇晃设备时,观察标签上显示的值。</p>
</li>
</ol>
<h2 id="如何工作-4">如何工作...</h2>
<p><code>UIAccelerometer</code>类通过其<code>SharedAccelerometer</code>静态属性提供对加速度计硬件的访问。要激活它,我们只需要将其<code>Acceleration</code>事件分配给一个处理程序:</p>
<pre><code class="language-swift">UIAccelerometer.SharedAccelerometer.Acceleration += this.Acceleration_Received;
</code></pre>
<p>在处理程序内部,我们通过<code>UIAccelerometerEventArgs.Acceleration</code>属性接收加速度计的值。该属性返回一个<code>UIAcceleration</code>类型的对象,它包含三个属性:<code>X</code>、<code>Y</code>和<code>Z</code>,分别表示加速度计在三个轴上的量。</p>
<p>这些属性表示在<code>X</code>、<code>Y</code>和<code>Z</code>轴上的运动。考虑以下图示:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_09_03.jpg"></p>
<p>这些值中的每一个都测量设备在每个轴上移动的重力量。例如,如果<code>X</code>的值为<code>1</code>,则设备在<code>X 轴</code>上向右移动,加速度为<code>1g</code>。如果<code>X</code>的值为<code>-1</code>,则设备在<code>X 轴</code>上向左移动,加速度为<code>1g</code>。当设备放在桌子上,背面朝向地面且不移动时,加速度的正常值应接近或等于以下值:</p>
<ul>
<li>
<p><code>X: 0</code></p>
</li>
<li>
<p><code>Y: 0</code></p>
</li>
<li>
<p><code>Z: -1</code></p>
</li>
</ul>
<p>尽管设备没有移动,<code>Z</code>的值将是<code>-1</code>,因为设备测量地球的重力。</p>
<p>我们可以通过设置其<code>UpdateInterval</code>属性来设置加速度计发布加速度事件的时间间隔:</p>
<pre><code class="language-swift">UIAccelerometer.SharedAccelerometer.UpdateInterval = 1 / 10;
</code></pre>
<p>它接受一个<code>double</code>,表示加速度计在秒内发布加速度事件的时间间隔。设置更新间隔时必须小心,因为加速度计在特定时间段内需要发布的事件越多,它消耗的电量就越多。</p>
<p>要停止使用加速度计,我们只需要将其处理程序从<code>Acceleration</code>事件中取消绑定:</p>
<pre><code class="language-swift">UIAccelerometer.SharedAccelerometer.Acceleration -= this.Acceleration_Received;
</code></pre>
<h2 id="更多内容-31">更多内容...</h2>
<p><code>UIAcceleration</code>类还包含另一个有用的属性,名为<code>Time</code>。它是一个表示加速度事件发生相对时间的<code>double</code>值。它是相对于 CPU 时间的,不建议使用此值来计算事件的精确时间戳。</p>
<h3 id="使用加速度计的注意事项">使用加速度计的注意事项</h3>
<p>尽管 iPhone 的加速度计是一个非常精确和敏感的传感器,但它不应该用于精确测量。此外,即使是同一型号的设备,不同 iOS 设备产生的结果也可能会有所不同。</p>
<h2 id="相关内容-24">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>使用陀螺仪</em></li>
</ul>
<h1 id="使用陀螺仪">使用陀螺仪</h1>
<p>在本食谱中,我们将学习如何使用内置的陀螺仪。</p>
<h2 id="准备工作-69">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>GyroscopeApp</code>。</p>
<h3 id="注意-41">注意</h3>
<p>模拟器不支持陀螺仪硬件。此外,只有较新的设备才包含陀螺仪。如果在这个没有陀螺仪的设备或模拟器上运行此应用程序,不会发生错误,但不会显示任何数据。</p>
<h2 id="如何操作-33">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加两个按钮和一个标签。在<code>MainController.cs</code>文件中添加命名空间<code>MonoTouch.CoreMotion</code>。在类中输入以下私有字段:</p>
<pre><code class="language-swift">private CMMotionManager motionManager;
</code></pre>
</li>
<li>
<p>重写<code>ViewDidLoad</code>方法,并使用以下代码实现它:</p>
<pre><code class="language-swift">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();
} ;
</code></pre>
</li>
<li>
<p>添加以下方法:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>开始陀螺仪</strong>按钮,并在所有轴上旋转设备。观察<strong>应用程序输出</strong>中显示的值。</p>
</li>
</ol>
<h2 id="它是如何工作的-56">它是如何工作的...</h2>
<p>陀螺仪是一种测量方向的机制。较新的 iOS 设备支持陀螺仪硬件,以及加速度计,以提供更精确的设备运动测量。</p>
<p><code>MonoTouch.CoreMotion</code>命名空间封装了原生<strong>CoreMotion 框架</strong>中包含的对象。在代码中使用陀螺仪硬件的过程与加速度计类似。第一个区别是<code>UIApplication</code>类中没有陀螺仪的单例对象。因此,我们需要创建<code>CMMotionManager</code>类的实例:</p>
<pre><code class="language-swift">private CMMotionManager motionManager;
//...
this.motionManager = new CMMotionManager();
</code></pre>
<p>就像使用加速度计一样,我们可以设置接收陀螺仪事件的间隔,单位为秒:</p>
<pre><code class="language-swift">this.motionManager.GyroUpdateInterval = 1 / 10;
</code></pre>
<p>要开始接收陀螺仪事件,我们调用对象的<code>StartGyroUpdates</code>方法:</p>
<pre><code class="language-swift">this.motionManager.StartGyroUpdates(NSOperationQueue.MainQueue, this.GyroData_Received);
</code></pre>
<p>此方法被重载;第一个重载是无参数的,当调用时,将陀螺仪测量的值设置为<code>GyroData</code>属性。使用此重载非常简单且容易,但没有触发任何事件,我们必须提供一个机制从属性中读取测量值。</p>
<p>第二个重载,在此示例中使用,接受两个参数。第一个参数是<code>NSOperationQueue</code>,更新将在其上发生,第二个参数是当发生更新时将被执行的处理器。</p>
<p><code>NSOperationQueue</code>类表示 iOS 管理<code>NSOperation</code>对象执行的一种机制。我们通过静态属性<code>NSOperationQueue.MainQueue</code>访问运行时的主操作队列。基本上,这种方式,我们指示运行时以更有效的方式管理处理器的传递。</p>
<p>第二个参数是<code>CMGyroHandler</code>类型的委托。其签名,由我们创建的方法表示,如下所示:</p>
<pre><code class="language-swift">private void GyroData_Received(CMGyroData gyroData, NSError error)
</code></pre>
<p><code>CMGyroData</code>对象包含通过其<code>RotationRate</code>属性从陀螺仪接收的实际测量值:</p>
<pre><code class="language-swift">Console.WriteLine("rotation rate x: {0}, y: {1}, z: {2}", gyroData.RotationRate.x, gyroData.RotationRate.y, gyroData.RotationRate.z);
</code></pre>
<p>旋转速率反映在<code>X, Y</code>和<code>Z</code>轴上,分别由相应的<code>X, Y</code>和<code>Z</code>属性表示。每个值是该轴上发生的每秒旋转角度的数量,以弧度为单位。</p>
<p>虽然一开始可能看起来有点复杂,但实际上很简单。例如,<code>Z 轴</code>上的<code>0.5</code>值表示设备以<code>0.5</code>弧度/秒的速率向左旋转。<code>Z 轴</code>上的<code>-0.5</code>值表示设备以<code>0.5</code>弧度/秒的速率向右旋转。确定旋转方向的模式基于<em>右手定则</em>。</p>
<h2 id="更多内容-32">更多内容...</h2>
<p>如果您希望您的应用程序仅适用于支持陀螺仪的设备,那么请在项目的<code>Info.plist</code>文件中添加键<code>UIRequiredDeviceCapabilities</code>,其值为<code>gyroscope</code>。如果您的应用程序的功能完全基于陀螺仪,添加此键必须被视为必要,以避免应用程序被使用较旧设备的用户下载,最终导致应用程序无法使用。</p>
<h3 id="确定陀螺仪可用性">确定陀螺仪可用性</h3>
<p>要确定应用程序运行在的设备是否支持陀螺仪硬件,请检查<code>CMMotionManager</code>实例的<code>GyroAvailable</code>属性的值。</p>
<h3 id="将弧度转换为度">将弧度转换为度</h3>
<p>弧度是角度的测量单位。要将角度测量值从弧度转换为度,请考虑以下方法:</p>
<pre><code class="language-swift">public static double RadiansToDegrees (double radians){
return (radians * 180 / Math.PI);
}
</code></pre>
<h2 id="参见-44">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>使用加速度计</em></li>
</ul>
<h1 id="第十章位置服务和地图">第十章。位置服务和地图</h1>
<p>在这一章中,我们将涵盖:</p>
<ul>
<li>
<p>确定位置</p>
</li>
<li>
<p>确定航向</p>
</li>
<li>
<p>使用区域监控</p>
</li>
<li>
<p>使用显著变化位置服务</p>
</li>
<li>
<p>背景中的位置服务</p>
</li>
<li>
<p>显示地图</p>
</li>
<li>
<p>地理编码</p>
</li>
<li>
<p>添加地图标注</p>
</li>
<li>
<p>添加地图叠加</p>
</li>
</ul>
<h1 id="简介-9">简介</h1>
<p>今天的手持设备和智能手机都配备了高精度的全球定位系统(GPS)硬件。GPS 硬件从一组卫星接收位置信息。除了卫星之外,iOS 设备还利用蜂窝和 Wi-fi 网络向用户提供位置信息。</p>
<p>在这一章中,我们将讨论如何使用适当的框架来利用设备的位置服务。此外,我们还将学习如何显示地图并对其进行标注。具体来说,我们将关注以下主题:</p>
<ul>
<li>
<p><strong>位置服务</strong>:它们提供设备上提供位置信息的可用服务。这些服务包括:</p>
<ul>
<li>
<p><strong>标准位置服务</strong>:这是完全依赖于设备 GPS 模块的位置服务,提供最高精度的位置数据</p>
</li>
<li>
<p><strong>区域监控服务</strong>:这是监控边界穿越的位置服务</p>
</li>
<li>
<p><strong>显著变化位置服务</strong>:这是监控位置显著变化的服务</p>
</li>
</ul>
</li>
<li>
<p><code>CLLocationManager:</code> 这是一个允许我们使用位置服务的类</p>
</li>
<li>
<p><code>Compass:</code> 这是一个显示我们如何使用内置指南针的类</p>
</li>
<li>
<p><code>MKMapView:</code> 这是一个用于显示地图的视图</p>
</li>
<li>
<p><code>MKAnnotation:</code> 这是一个允许我们在地图上添加标注的类</p>
</li>
<li>
<p><code>MKOverlay:</code> 这是一个允许我们在地图上添加叠加的类</p>
</li>
</ul>
<h1 id="确定位置">确定位置</h1>
<p>在这个菜谱中,我们将讨论如何从内置的 GPS 硬件接收位置信息。</p>
<h2 id="准备工作-70">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>LocationApp</code>。在<code>MainController</code>的视图中添加两个按钮和一个标签。</p>
<h2 id="如何做到-6">如何做到...</h2>
<p>要从内置的 GPS 硬件中检索位置数据,我们需要使用<code>CoreLocaction</code>框架。它通过<code>MonoTouch.CoreLocation</code>命名空间暴露:</p>
<pre><code class="language-swift">using MonoTouch.CoreLocation;
</code></pre>
<ol>
<li>
<p>在<code>MainController</code>类中添加以下代码:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击开始按钮,在屏幕上查看您的位置坐标。</p>
</li>
</ol>
<h3 id="注意-42">注意</h3>
<p>使用<code>CoreLocation</code>框架确定当前位置的项目可以在模拟器上运行。然而,坐标将是固定的,值为加利福尼亚州苹果公司的总部或运行模拟器的 Mac 电脑所在国家的中心坐标。</p>
<h2 id="它是如何工作的-57">它是如何工作的...</h2>
<p>GPS 模块提供的位置数据可以通过<code>CLLocationManager</code>类访问。初始化类的实例后,我们需要订阅其<code>UpdatedLocation</code>事件:</p>
<pre><code class="language-swift">this.locationManager = new CLLocationManager();
this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
</code></pre>
<p>当这些事件发布时,位置数据将变得可用。订阅<code>Failed</code>事件也是一个好的实践:</p>
<pre><code class="language-swift">this.locationManager.Failed += this.LocationManager_Failed;
</code></pre>
<p>当位置管理器首次请求位置更新时,用户会通过系统特定的警报得到通知,类似于以下截图:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_01.jpg"></p>
<p>此警报基本上是请求用户允许应用程序检索位置数据。如果用户拒绝此请求,将触发带有适当信息的<code>Failed</code>事件。未来的位置请求将不会触发权限警报,用户必须通过设备设置启用应用程序的位置服务,因此我们需要相应地处理这种情况。</p>
<p>在订阅了适当的事件之后,我们通过<code>StartUpdatingLocation</code>方法请求发送位置更新:</p>
<pre><code class="language-swift">this.locationManager.StartUpdatingLocation();
</code></pre>
<p>要停止接收位置更新,我们调用<code>StopUpdatingLocation</code>方法:</p>
<pre><code class="language-swift">this.locationManager.StopUpdatingLocation();
</code></pre>
<h2 id="还有更多-33">还有更多...</h2>
<p><code>UpdatedLocation</code>事件接受类型为<code>EventHandler<CLLocationUpdatedEventArgs></code>的委托。<code>CLLocationUpdatedEventArgs</code>参数包含两个类型为<code>CLLocationCoordinate2D</code>的属性。<code>NewLocation</code>属性包含最新的位置信息,而<code>OldLocation</code>属性包含之前的位置信息。在第一次位置更新时,<code>OldLocation</code>属性将返回<code>null</code>。</p>
<p>坐标以<code>double</code>类型的值返回,并代表位置的坐标(以度为单位):</p>
<pre><code class="language-swift">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);
</code></pre>
<p>负的<code>latitude</code>值表示南坐标,而正的值表示北坐标。负的<code>longitude</code>值表示西坐标,而正的<code>longitude</code>值表示东坐标。</p>
<p><code>HorizontalAccuracy</code>属性返回 GPS 定位的精度(以米为单位)。例如,<code>17m</code>的值表示位置是在直径为 17 米的圆内确定的。较低的值表示更好的精度。</p>
<h3 id="gps-精度">GPS 精度</h3>
<p><code>UpdateLocation</code>事件可能会在没有新的 GPS 读取的情况下被触发。这就是为什么我们提供了之前的位置,以便我们可以比较两个值以确定是否发生了位置变化。此外,位置数据总是存在一定的误差范围,这与 GPS 硬件无关,并且存在一些变量因素来定义它,例如周围的建筑物、各种障碍物等等。您会注意到,当设备在户外时,<code>HorizontalAccuracy</code>将返回较低的值,而当我们在室内使用 GPS 或在高楼林立的街道上时,将返回较高的值。</p>
<h3 id="位置服务可用性">位置服务可用性</h3>
<p>并非所有设备都配备了位置服务硬件。此外,即使设备配备了适当的硬件,用户也可能已禁用位置服务。</p>
<p>要确定设备上是否启用了位置服务,我们在初始化位置管理器对象之前读取<code>CLLocationManager.LocationServicesEnabled</code>静态属性的返回值:</p>
<pre><code class="language-swift">if (CLLocationManager.LocationServicesEnabled) {
// Initialize the location manager
//...
}
</code></pre>
<h3 id="位置服务使用指示器">位置服务使用指示器</h3>
<p>当使用任何类型的定位服务时,定位服务图标将出现在状态栏的右侧,紧邻电池指示器:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_02.jpg"></p>
<p>此指示器仅在设备上显示,而不在模拟器上显示。</p>
<h2 id="参见-45">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>确定航向</em></p>
</li>
<li>
<p><em>后台位置服务</em></p>
</li>
</ul>
<h1 id="确定航向">确定航向</h1>
<p>在本配方中,我们将学习如何使用内置指南针来确定航向。</p>
<h2 id="准备工作-71">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>HeadingApp</code>。就像在之前的任务中一样,在<code>MainController</code>的视图中添加两个按钮和一个标签。</p>
<h3 id="注意-43">注意</h3>
<p>本任务中的项目无法在模拟器上测试。需要一个带有指南针硬件(磁力计)的设备。</p>
<h2 id="如何操作-34">如何操作...</h2>
<ol>
<li>
<p>航向信息再次通过<code>CLLocationManager</code>类检索。在<code>MainController</code>类中创建并初始化一个实例。</p>
</li>
<li>
<p>在<code>ViewDidLoad</code>方法中添加以下代码:</p>
<pre><code class="language-swift">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.";
} ;
</code></pre>
</li>
<li>
<p>添加以下方法:</p>
<pre><code class="language-swift">private void LocationManager_UpdatedHeading (object sender, CLHeadingUpdatedEventArgs e){
this.lblOutput.Text = string.Format("Magnetic heading: {0}", Math.Round(e.NewHeading.MagneticHeading, 1));
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击开始按钮并旋转设备以查看不同的航向值。</p>
</li>
</ol>
<h2 id="它是如何工作的-58">它是如何工作的...</h2>
<p>要检索航向信息,我们首先需要订阅定位管理器的<code>UpdatedHeading</code>事件:</p>
<pre><code class="language-swift">this.locationManager.UpdatedHeading += this.LocationManager_UpdatedHeading;
</code></pre>
<p>要启动航向信息的传输,我们调用<code>StartUpdatingHeading</code>方法:</p>
<pre><code class="language-swift">this.locationManager.StartUpdatingHeading();
</code></pre>
<p>在<code>UpdatedHeading</code>事件处理程序内部,我们通过事件参数的<code>NewHeading</code>属性,通过<code>CLHeading</code>对象的<code>MagneticHeading</code>属性检索航向信息:</p>
<pre><code class="language-swift">this.lblOutput.Text = string.Format("Magnetic heading: {0}", Math.Round(e.NewHeading.MagneticHeading, 1));
</code></pre>
<p>要停止检索航向更新,我们调用<code>StopUpdatingHeading</code>方法:</p>
<pre><code class="language-swift">this.locationManager.StopUpdatingHeading();
</code></pre>
<h2 id="更多内容-33">更多内容...</h2>
<p>航向以度为单位测量。地平线四个方向的值,如简单指南针上所示,如下:</p>
<ul>
<li>
<p><code>0</code> <strong>或</strong> <code>360</code> <strong>度:</strong> 北;磁力计将返回最多<code>359.99</code>度的值,当设备朝北时,将回到<code>0</code>。</p>
</li>
<li>
<p><code>90</code> <strong>度:</strong> 东</p>
</li>
<li>
<p><code>180</code> <strong>度:</strong> 南</p>
</li>
<li>
<p><code>270</code> <strong>度:</strong> 西</p>
</li>
</ul>
<h3 id="磁航向与真航向">磁航向与真航向</h3>
<p><strong>磁航向</strong>是基于普通指南针显示的北方。<strong>真航向</strong>是基于地球北极实际位置的北方方向。两者之间略有差异,随时间变化,通常约为<code>2</code>度。</p>
<p><code>CLHeading</code>类通过<code>MagneticHeading</code>和<code>TrueHeading</code>属性提供两种读数。这对开发者来说非常有帮助,因为计算两者之间的差异可能需要昂贵的设备,或者基于年份和其他因素进行非常复杂的计算。</p>
<h3 id="罗盘可用性">罗盘可用性</h3>
<p>磁力计,一个可以确定航向(以度为单位)并为设备提供指南针功能的模块,并非所有设备都可用。要检查设备是否可以提供航向信息,请从<code>CLLocationManager.HeadingAvailable</code>静态属性中检索值:</p>
<pre><code class="language-swift">if (CLLocationManager.HeadingAvailable) {
// Start updating heading
//...
}
</code></pre>
<h2 id="参见-46">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>确定位置</em></p>
</li>
<li>
<p><em>后台位置服务</em></p>
</li>
</ul>
<h1 id="使用区域监控">使用区域监控</h1>
<p>在这个菜谱中,我们将讨论如何使用 GPS 来响应特定区域的位置变化。</p>
<h2 id="准备工作-72">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>RegionApp</code>。在 <code>MainController</code> 的视图中添加两个按钮和一个标签。</p>
<h2 id="如何操作-35">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 类中创建两个字段:</p>
<pre><code class="language-swift">private CLLocationManager locationManager;
private CLRegion region;
</code></pre>
</li>
<li>
<p>在 <code>ViewDidLoad</code> 方法中,对其进行初始化,并订阅 <code>UpdatedLocation</code>、<code>RegionEntered</code> 和 <code>RegionLeft</code> 事件:</p>
<pre><code class="language-swift">this.locationManager.RegionEntered += this.LocationManager_RegionEntered;
this.locationManager.RegionLeft += this.LocationManager_RegionLeft;
this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
</code></pre>
</li>
<li>
<p>在类中输入以下事件处理程序:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在开始按钮的 <code>TouchUpInside</code> 处理程序中,调用 <code>StartUpdatingLocation</code> 方法:</p>
<pre><code class="language-swift">this.locationManager.StartUpdatingLocation();
</code></pre>
</li>
<li>
<p>在停止按钮的 <code>TouchUpInside</code> 处理程序中,调用 <code>StopMonitoring</code> 方法:</p>
<pre><code class="language-swift">this.locationManager.StopMonitoring(this.region);
</code></pre>
</li>
</ol>
<p>此应用程序需要在支持区域监控的设备上进行测试。</p>
<h2 id="它是如何工作的-59">它是如何工作的...</h2>
<p>区域监控是一个监控边界穿越的功能。当特定区域的边界被穿越时,<code>CLLocationManager</code> 对象会触发适当的事件:</p>
<pre><code class="language-swift">this.locationManager.RegionEntered += this.LocationManager_RegionEntered;
this.locationManager.RegionLeft += this.LocationManager_RegionLeft;
this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
</code></pre>
<p>在这个例子中,我们根据当前位置定义区域;因此,我们也订阅了 <code>UpdatedLocation</code> 事件。</p>
<p>当应用程序开始接收位置更新时,它首先检查位置精度:</p>
<pre><code class="language-swift">if (e.NewLocation.HorizontalAccuracy < 100)
</code></pre>
<p>如果达到了所需的精度(<100m,可自行修改),我们初始化 <code>CLRegion</code> 对象:</p>
<pre><code class="language-swift">this.region = new CLRegion(e.NewLocation.Coordinate, 100, "Home");
</code></pre>
<p><code>CLRegion</code> 类用于定义区域。在这里,我们根据我们的当前位置在第一个参数中创建要监控的区域。第二个参数声明了坐标周围的半径(以米为单位),定义了区域边界。第三个参数是区域的字符串标识符。</p>
<p>要开始监控区域,我们调用 <code>StartMonitoring</code> 方法:</p>
<pre><code class="language-swift">this.locationManager.StartMonitoring(this.region, 65);
</code></pre>
<p>第一个参数是要监控的区域,第二个参数定义了边界穿越所需的精度(以米为单位)。此值作为精度偏移量,防止系统在用户接近区域边界时触发连续的 <code>进入</code> 和 <code>离开</code> 事件。</p>
<p>当区域监控开始时,当设备进入或离开区域时,将根据所需的精度值触发适当的事件。</p>
<h2 id="还有更多-34">还有更多...</h2>
<p>区域监控是一个非常实用的功能。例如,一个应用程序可以根据用户接近的不同区域提供特定的信息。此外,它可以在应用程序在后台时通知边界穿越。</p>
<h3 id="区域监控可用性">区域监控可用性</h3>
<p>要检查设备是否支持区域监控,检索 <code>RegionMonitoringAvailable</code> 静态属性的值:</p>
<pre><code class="language-swift">if (CLLocationManager.RegionMonitoringAvailable) {
// Start monitoring a region
//...
}
</code></pre>
<h2 id="参见-47">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>使用显著变化的位置服务</em></p>
</li>
<li>
<p><em>后台位置服务</em></p>
</li>
</ul>
<h1 id="使用显著变化的位置服务">使用显著变化的位置服务</h1>
<p>在这个菜谱中,我们将学习如何使用显著变化的位置监控功能。</p>
<h2 id="准备工作-73">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>SLCApp</code>。在 <code>MainController</code> 的视图中添加一个标签和两个按钮。</p>
<h2 id="如何做-12">如何做...</h2>
<ol>
<li>
<p>在<code>ViewDidLoad</code>方法中添加以下代码:</p>
<pre><code class="language-swift">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.";
} ;
</code></pre>
</li>
<li>
<p>添加以下方法:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>开始</strong>按钮以开始监控显著位置变化。</p>
</li>
</ol>
<h2 id="它是如何工作的-60">它是如何工作的...</h2>
<p>显著变化位置服务监控显著的位置变化,并在这些变化发生时提供位置信息。在功耗方面,它是要求较低的位置服务。它使用设备的蜂窝无线电收发器来确定用户的位置。只有配备了蜂窝无线电收发器的设备才能使用此服务。</p>
<p>使用显著变化位置服务的代码与标准位置服务的代码类似。唯一的不同之处在于启动和停止服务的方法。要启动服务,我们调用<code>StartMonitoringSignificantLocationChanges</code>方法:</p>
<pre><code class="language-swift">this.locationManager.StartMonitoringSignificantLocationChanges();
</code></pre>
<p>位置更新通过<code>UpdatedLocation</code>事件处理程序发布,这与我们用于标准位置服务的事件相同:</p>
<pre><code class="language-swift">this.locationManager.UpdatedLocation += this.LocationManager_UpdatedLocation;
//...
private void LocationManager_UpdatedLocation (object sender, CLLocationUpdatedEventArgs e){
//...
}
</code></pre>
<h2 id="更多内容-34">更多内容...</h2>
<p>显著变化位置服务可以在后台报告位置变化,唤醒应用程序。</p>
<h3 id="显著变化位置服务可用性">显著变化位置服务可用性</h3>
<p>要确定设备是否能够使用显著变化位置服务,检索<code>SignificantLocationChangeMonitoringAvailable</code>静态属性的值:</p>
<pre><code class="language-swift">if (CLLocationManager.SignificantLocationChangeMonitoringAvailable) {
// Start monitoring for significant location changes.
//...
}
</code></pre>
<h2 id="参见-48">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>使用区域监控</em></p>
</li>
<li>
<p><em>后台位置服务</em></p>
</li>
</ul>
<h1 id="后台位置服务">后台位置服务</h1>
<p>在这个菜谱中,我们将讨论如何在应用处于后台时使用位置服务。</p>
<h2 id="准备工作-74">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>BackgroundLocationApp</code>。就像在之前的任务中一样,在<code>MainController</code>的视图中添加一个标签和两个按钮。</p>
<h2 id="如何做-13">如何做...</h2>
<ol>
<li>
<p>在<strong>解决方案</strong>面板中,双击<strong>Info.plist</strong>文件以打开它。</p>
</li>
<li>
<p>在<strong>高级</strong>选项卡下,通过单击加号(+)或通过右键单击并从上下文菜单中选择<strong>新建键</strong>来添加一个新键。</p>
</li>
<li>
<p>从下拉列表中选择<strong>必需的后台模式</strong>,或者在字段中直接输入<code>UIBackgroundModes</code>。</p>
</li>
<li>
<p>展开键,在其下方的空白项上右键单击。在上下文菜单中单击<strong>新建键</strong>。在其<strong>值</strong>字段中输入单词<code>location</code>。</p>
</li>
<li>
<p>保存文档。完成后,你应该会有以下截图所示的内容:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_03.jpg"></p>
</li>
<li>
<p>在<code>MainController</code>类中,输入与本章中<em>确定位置</em>菜谱中使用的相同代码。</p>
</li>
<li>
<p>在<code>LocationManager_UpdatedLocation</code>方法的底部添加以下行:</p>
<pre><code class="language-swift">Console.WriteLine("{0}:\n\t{1} ", DateTime.Now, this.lblOutput.Text);
</code></pre>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>开始</strong>按钮以开始接收位置更新。按设备上的<strong>主页</strong>按钮使应用程序进入后台。观察 MonoDevelop 的<strong>应用程序输出</strong>面板显示位置更新。</p>
</li>
</ol>
<h2 id="它是如何工作的-61">它是如何工作的...</h2>
<p>要在应用程序处于后台时接收位置更新,我们需要在 <strong>Info.plist</strong> 文件中将位置值设置为 <code>UIBackgroundModes</code> 键。这基本上确保了应用程序在后台时具有接收位置更新的适当权限,并且它不会挂起。</p>
<p>为了确保应用程序正在接收位置更新,请检查状态栏。位置服务图标应该显示:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_04.jpg"></p>
<h2 id="更多内容-35">更多内容...</h2>
<p>为位置服务设置 <code>UIBackgroundModes</code> 键仅适用于标准位置服务。默认情况下,区域监控和重大变化位置服务都支持在应用程序处于后台时传输位置更新。当其中一个位置服务开始更新位置数据时,应用程序甚至可以被终止。当接收到位置更新时,应用程序将被启动或从挂起状态唤醒,并给予有限的时间来执行代码。</p>
<p>要确定应用程序是否由这两个位置服务之一启动,请检查 <code>AppDelegate</code> 类中 <code>FinishedLaunching</code> 方法的选项参数:</p>
<pre><code class="language-swift">if (null != options){
if (options.ContainsKey (UIApplication.LaunchOptionsLocationKey)){
Console.WriteLine ("Woken from location service!");
CLLocationManager locationManager = new CLLocationManager();
locationManager.UpdatedLocation += this.LocationUpdatedHandler;
locationManager.StartMonitoringSignificantLocationChanges();
}
}
</code></pre>
<p>选项参数的类型为 <code>NSDictionary</code>。如果这个字典包含 <code>LaunchOptionsLocationKey</code>,则应用程序是由于位置服务而被启动或从挂起状态唤醒的。在这种情况下,我们需要在 <code>CLLocationManager</code> 实例上再次调用 <code>StartMonitoringSignificantLocationChanges</code> 方法来检索位置数据。</p>
<p>同样适用于区域监控位置服务。请注意,如果我们使用这两个位置服务之一,但我们的应用程序不支持位置事件的后台传输,那么我们必须确保在不再需要时停止监控位置更新。如果我们不这样做,那么位置服务将继续运行,导致电池消耗显著增加。</p>
<h3 id="限制到支持的硬件">限制到支持的硬件</h3>
<p>如果我们的应用程序的功能完全依赖于位置服务,并且不能在不支持它们的设备上正确运行,我们必须在 <strong>Info.plist</strong> 文件中添加键 <code>UIRequiredDeviceCapabilities</code>,其值为 <code>location-services</code>。</p>
<p>此外,当应用程序需要使用使用 GPS 硬件的标准位置服务时,我们需要将值 <code>gps</code> 添加到这个键中。这样,我们确保应用程序不会通过应用商店提供给未配备适当硬件的设备。</p>
<h2 id="参见-49">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>确定位置</em></li>
</ul>
<p>在本书中:</p>
<p>第一章,开发工具:</p>
<ul>
<li><em>在 MonoDevelop 中创建 iPhone 项目</em></li>
</ul>
<h1 id="显示地图">显示地图</h1>
<p>在本食谱中,我们将学习如何在屏幕上显示地图。</p>
<h2 id="准备工作-75">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>MapDisplayApp</code>。</p>
<h2 id="如何操作-36">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个 <code>MKMapView</code>。输入以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using MonoTouch.MapKit;
using MonoTouch.CoreLocation;
</code></pre>
</li>
<li>
<p>在 <code>MainController</code> 类中添加以下代码:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。如果应用程序在模拟器上运行,默认位置将是苹果公司的总部位于 <strong>库比蒂诺</strong>:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_05.jpg"></p>
</li>
<li>
<p>放大或平移地图以在 <strong>应用程序输出</strong> 中输出当前位置。</p>
</li>
</ol>
<h2 id="它是如何工作的-62">它是如何工作的...</h2>
<p><code>MonoTouch.MapKit</code> 命名空间封装了 <code>MapKit</code> 框架中包含的所有对象。<code>MapKit</code> 框架使用谷歌地图来显示地图。</p>
<p><code>MKMapView</code> 是默认的 iOS 视图,用于显示地图。它专门为此目的而设计,不应被子类化。</p>
<p>要在地图上显示用户的位置,我们将它的 <code>ShowsUserLocation</code> 属性设置为 <code>true</code>:</p>
<pre><code class="language-swift">this.mapView.ShowsUserLocation = true;
</code></pre>
<p>这将激活标准位置服务以开始接收位置更新并将它们内部传递给 <code>MKMapView</code> 对象。</p>
<p>为了确定用户何时放大或平移地图,我们订阅 <code>RegionChanged</code> 事件:</p>
<pre><code class="language-swift">this.mapView.RegionChanged += this.MapView_RegionChanged;
</code></pre>
<p>在事件处理程序内部,我们通过 <code>UserLocation</code> 属性检索当前位置:</p>
<pre><code class="language-swift">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);
}
</code></pre>
<p>如果将 <code>ShowsUserLocation</code> 属性设置为 <code>false</code>,则位置服务将不会激活,并且 <code>UserLocation.Location</code> 属性将返回 <code>null</code>。当应用程序首次运行时,它也会返回 <code>null</code>,因为它会请求用户使用位置服务的权限。然而,只要设备或模拟器有活跃的互联网连接,就会显示地图。</p>
<h2 id="还有更多-35">还有更多...</h2>
<p>我们可以使用 <code>SetCenterCoordinate</code> 方法设置要显示的地图的中心坐标:</p>
<pre><code class="language-swift">CLLocationCoordinate2D mapCoordinates = new CLLocationCoordinate2D(0, 0);
this.mapView.SetCenterCoordinate(mapCoordinates, true);
</code></pre>
<p>第一个参数是我们希望地图中心对齐的地图坐标,表示为 <code>CLLocationCoordinate2D</code> 类型的对象。第二个参数声明我们是否希望地图中心对齐是动画的。</p>
<p>除了对齐地图外,我们还可以设置其缩放级别。我们通过 <code>SetRegion</code> 方法来完成:</p>
<pre><code class="language-swift">this.mapView.SetRegion(MKCoordinateRegion.FromDistance( mapCoordinates, 1000, 1000), true);
</code></pre>
<p>第一个参数是 <code>MKCoordinateRegion</code> 类型。在这里,使用其 <code>FromDistance</code> 静态方法创建一个实例。其第一个参数是区域中心的坐标,接下来的两个参数代表要显示的地图的水平范围和垂直范围,单位为米。这基本上意味着由这个 <code>MKCoordinateRegion</code> 实例表示的区域将以 <code>mapCoordinates</code> 为中心,地图的水平部分和垂直部分将分别代表地图上的 <code>1000</code> 米。</p>
<p>注意,<code>MKMapView</code>会将实际区域设置为<code>MKCoordinateRegion</code>值的近似。这是因为<code>MKMapView</code>的尺寸不能总是与提供的水平和垂直跨度值匹配。例如,这里我们设置了一个<code>1000x1000</code>米的正方形区域,但我们的<code>MKMapView</code>布局并不是一个绝对的正方形,因为它基本上占据了整个屏幕。我们可以通过其<code>Region</code>属性检索<code>MKMapView</code>显示的实际地图区域。</p>
<h3 id="使用-mapkit-时需要注意的事项">使用 MapKit 时需要注意的事项</h3>
<p><code>MapKit</code>框架使用 Google Maps 和 Google Earth API 来显示地图。使用此框架将开发者绑定到 Google 的服务条款,可在<code>code.google.com/apis/maps/iphone/terms.html</code>查看。</p>
<p>一个可能直接影响你的应用程序是否会在应用商店被拒绝的重要术语是在地图上使用 Google 标志。应确保在显示地图时标志始终可见。</p>
<h2 id="参见-50">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>地理编码</em></p>
</li>
<li>
<p><em>添加地图标注</em></p>
</li>
<li>
<p><em>添加地图覆盖</em></p>
</li>
</ul>
<h1 id="地理编码">地理编码</h1>
<p>在本配方中,我们将学习如何根据位置坐标提供地址、城市或国家信息。</p>
<h2 id="准备工作-76">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>GeocodingApp</code>。</p>
<h2 id="如何操作-37">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>视图的上半部分添加一个<code>MKMapView</code>,在下半部分添加一个标签和一个按钮。添加<code>MonoTouch.MapKit</code>和<code>MonoTouch.CoreLocation</code>命名空间。</p>
</li>
<li>
<p>在<code>MainController</code>类中输入以下<code>ViewDidLoad</code>重写方法:</p>
<pre><code class="language-swift">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();
} ;
}
</code></pre>
</li>
<li>
<p>创建以下嵌套类:</p>
<pre><code class="language-swift">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;
}
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。如果在设备上运行,当你点击按钮时,将在标签上显示你当前所在的国家和地区的位置信息。</p>
</li>
</ol>
<h2 id="它是如何工作的-63">它是如何工作的...</h2>
<p><strong>地理编码</strong>是将地址信息与地理坐标匹配的过程。<strong>反向地理编码</strong>是将地理坐标与地址信息匹配的过程。iOS 上仅提供后者。然而,在线上还有可用的正向地理编码服务。</p>
<p>要进行反向地理编码,我们使用<code>MKReverseGeocoder</code>类:</p>
<pre><code class="language-swift">private MKReverseGeocoder reverseGeocoder;
</code></pre>
<p>此类需要一个提供信息的代理对象。我们将为<code>MKReverseGeocoder</code>的代理对象创建的类必须继承自<code>MKReverseGeocoderDelegate</code>类:</p>
<pre><code class="language-swift">private class ReverseGeocoderDelegate : MKReverseGeocoderDelegate
</code></pre>
<p>在代理对象内部,我们需要重写两个方法。第一个是<code>FoundWithPlacemark:</code></p>
<pre><code class="language-swift">public override void FoundWithPlacemark (MKReverseGeocoder geocoder, MKPlacemark placemark)
</code></pre>
<p>这是当反向地理编码器检索地理编码信息时将被触发的方法。信息包含在<code>placemark</code>参数中,该参数是<code>MKPlacemark</code>类型。如前所述的高亮代码所示,信息可以通过<code>MKPlacemark</code>类的各种属性获取。</p>
<p>我们需要重写的第二个方法是<code>FailedWithError:</code></p>
<pre><code class="language-swift">public override void FailedWithError (MKReverseGeocoder geocoder, NSError error)
</code></pre>
<p>当由于某种原因反向地理编码失败时,此方法会被触发。信息包含在 <code>error</code> 参数中。</p>
<p>要初始化 <code>MKReverseGeocoder</code> 类的实例,我们将我们想要地理编码信息的坐标与类型为 <code>CLLocationCoordinate2D</code> 的对象传递给其构造函数:</p>
<pre><code class="language-swift">this.reverseGeocoder = new MKReverseGeocoder(currentLocation);
</code></pre>
<p>在分配其代理对象后,我们调用 <code>Start</code> 方法来反向地理编码坐标:</p>
<pre><code class="language-swift">this.reverseGeocoder.Delegate = new ReverseGeocoderDelegate(this);
this.reverseGeocoder.Start();
</code></pre>
<h2 id="更多-8">更多...</h2>
<p>可以通过 <code>MKPlacemark</code> 类的 <code>AddressDictionary</code> 属性检索位置地址的详细信息,该属性是 <code>NSDictionary</code> 类型。</p>
<h3 id="使用-mkreversegeocoder-类时需要注意的事项">使用 <code>MKReverseGeocoder</code> 类时需要注意的事项</h3>
<p><code>MKReverseGeocoder</code> 类是 <code>MapKit</code> 框架的一部分。使用此类将开发者绑定到 Google 的服务条款:<code>code.google.com/apis/maps/iphone/terms.html</code>。</p>
<p>使用反向地理编码服务的一个重要条款是,它应该始终与 Google 地图结合使用。此外,为了避免滥用服务,Apple 建议每分钟不要进行超过一次反向地理编码调用。</p>
<h2 id="参见-51">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示地图</em></p>
</li>
<li>
<p><em>添加地图注释</em></p>
</li>
<li>
<p><em>添加地图覆盖</em></p>
</li>
</ul>
<h1 id="添加地图注释">添加地图注释</h1>
<p>在本食谱中,我们将讨论如何注释地图以显示各种信息。</p>
<h2 id="准备工作-77">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>MapAnnotateApp</code>。</p>
<h2 id="如何操作-38">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个 <code>MKMapView</code>。在底部留出一些空间,并添加一个按钮。</p>
</li>
<li>
<p>添加命名空间 <code>MonoTouch.MapKit</code> 和 <code>Monotouch.CoreLocation</code>,并在 <code>ViewDidLoad</code> 覆盖方法中输入以下代码:</p>
<pre><code class="language-swift">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);
} ;
</code></pre>
</li>
<li>
<p>创建以下嵌套类:</p>
<pre><code class="language-swift">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;
}
}
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。如果在模拟器上运行,当你点击按钮时,结果应该类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_06.jpg"></p>
<ul>
<li>点击“添加引脚”会显示带有注释标题和副标题的呼叫气泡。</li>
</ul>
<h2 id="它是如何工作的-64">它是如何工作的...</h2>
<p>注释地图对于提供与地图数据相关的各种信息非常有用。我们可以使用 <code>MKPointAnnotation</code> 类来创建一个简单的注释:</p>
<pre><code class="language-swift">MKPointAnnotation myAnnotation = new MKPointAnnotation();
myAnnotation.Coordinate = mapCoordinate;
myAnnotation.Title = "MyAnnotation";
myAnnotation.Subtitle = "Standard annotation";
this.mapView.AddAnnotation(myAnnotation);
</code></pre>
<p>我们为注释将出现的地图坐标分配,以及可选的标题和副标题。然后我们使用 <code>AddAnnotation</code> 方法将注释添加到地图视图中。</p>
<p>仅向地图视图添加注释对象是不够的。注释需要一个显示其信息的视图。这是通过为地图视图创建一个代理对象并覆盖其 <code>GetViewForAnnotation</code> 方法来实现的:</p>
<pre><code class="language-swift">public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation)
</code></pre>
<p>由于地图已经显示了用户位置,已经存在一个注释,并且它是 <code>MKUserLocation</code> 类型。在 <code>GetViewForAnnotation</code> 中,我们必须确保通过检查注释参数的类型来为我们自己的注释提供一个视图:</p>
<pre><code class="language-swift">if (annotation is MKUserLocation)
</code></pre>
<p>在这个例子中,我们只返回 <code>null</code>。如果注释参数是 <code>MKPointAnnotation</code> 类型,那么我们首先尝试检索它的视图,类似于 <code>UITableView</code> 创建它包含的单元格:</p>
<pre><code class="language-swift">MKPinAnnotationView pinView = mapView.DequeueReusableAnnotation( reuseIdentifier) as MKPinAnnotationView;
</code></pre>
<p>如果 <code>DequeueReusableAnnotation</code> 方法的返回结果是 <code>null</code>,那么我们为我们的注释视图初始化一个新的实例:</p>
<pre><code class="language-swift">pinView = new MKPinAnnotationView(annotation, reuseIdentifier);
pinView.PinColor = MKPinAnnotationColor.Purple;
pinView.AnimatesDrop = true;
pinView.CanShowCallout = true;
</code></pre>
<p>我们为注释创建的视图是 <code>MKPinAnnotationView</code> 类型。这是由地图上的针表示的标准视图。我们设置的属性相当直接,定义了其外观和行为。<code>PinColor</code> 属性定义了针的颜色,<code>AnimatesDrop</code> 属性定义了针是否以动画形式显示在地图上,而 <code>CanShowCallout</code> 属性定义了注释视图是否在呼出气泡中显示其底层注释的信息。</p>
<p>在我们创建了注释视图之后,我们只需从方法中返回它:</p>
<pre><code class="language-swift">return pinView;
</code></pre>
<h2 id="更多内容-36">更多内容...</h2>
<p>我们还可以创建自定义注释和注释视图。对于注释,我们必须重写 <code>MKAnnotation</code> 类,而对于注释视图,我们可以重写 <code>MKAnnotationView</code> 类。</p>
<h3 id="注释性能">注释性能</h3>
<p>理论上,我们可以向地图视图添加任意数量的注释。虽然 <code>MKMapView</code> 可以高效地管理大量注释,但强烈建议考虑性能下降。一种克服这个问题的方法是根据当前地图区域显示注释,这基本上管理了地图的缩放级别。另一种方法是确保我们为不需要不同注释视图的注释使用相同的注释视图实例。</p>
<h2 id="相关内容-25">相关内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示地图</em></p>
</li>
<li>
<p><em>添加地图覆盖</em></p>
</li>
</ul>
<p>在本书中:</p>
<p>第五章, 显示数据:</p>
<ul>
<li><em>在表中显示数据</em></li>
</ul>
<h1 id="添加地图覆盖">添加地图覆盖</h1>
<p>在这个菜谱中,我们将讨论使用覆盖层在地图上绘制。</p>
<h2 id="准备工作-78">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>MapOverlayApp</code>。</p>
<h2 id="如何做-14">如何做...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 的视图中添加一个 <code>MKMapView</code>。在底部留出一些空间,并添加一个按钮。</p>
</li>
<li>
<p>添加命名空间 <code>MonoTouch.MapKit</code> 和 <code>Monotouch.CoreLocation</code>,并在 <code>ViewDidLoad</code> 重写中输入以下代码:</p>
<pre><code class="language-swift">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);
} ;
</code></pre>
</li>
<li>
<p>添加以下嵌套类:</p>
<pre><code class="language-swift">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;
}
}
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。如果在模拟器上运行,在点击按钮后,结果应该类似于以下内容:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_10_07.jpg"></p>
<h2 id="工作原理-13">工作原理...</h2>
<p>虽然 <code>MKAnnotation</code> 代表地图上的一个点,但 <code>MKOverlay</code> 对象可以代表地图上的一个区域。在这个例子中,我们使用继承自 <code>MKOverlay</code> 的 <code>MKCircle</code> 类来在地图上的区域上显示一个圆。</p>
<p>初始化 <code>MKCircle</code> 实例使用其 <code>Circle</code> 静态方法:</p>
<pre><code class="language-swift">MKCircle circleOverlay = MKCircle.Circle(mapCoordinate, 250);
</code></pre>
<p>第一个参数表示圆心的坐标,而第二个参数表示圆的半径,单位为米。初始化后,我们使用<code>AddOverlay</code>方法将叠加添加到地图视图中:</p>
<pre><code class="language-swift">this.mapView.AddOverlay(circleOverlay);
</code></pre>
<p>就像注释一样,叠加需要视图来显示其信息。为了为我们的叠加提供视图,我们在地图视图的代理对象实现中重写了<code>GetViewForOverlay</code>方法:</p>
<pre><code class="language-swift">public override MKOverlayView GetViewForOverlay (MKMapView mapView, NSObject overlay)
</code></pre>
<p>在此方法内部,我们首先检查叠加参数是否是我们想要的类型;在这种情况下,是一个<code>MKCircle</code>:</p>
<pre><code class="language-swift">MKCircle circleOverlay = overlay as MKCircle;
if (null != circleOverlay)
</code></pre>
<p>然后,我们创建<code>MKCircleView</code>类的实例并返回它:</p>
<pre><code class="language-swift">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;
</code></pre>
<p>我们设置适当的属性来定义叠加的外观。在这种情况下,我们设置了<code>FillColor</code>、<code>StrokeColor</code>和<code>LineWidth</code>属性。</p>
<h2 id="更多内容-37">更多内容...</h2>
<p>地图视图有效地处理叠加。地图视图为我们处理的一个重要事项是,当我们缩放地图时,叠加会自动缩放到匹配每个缩放级别。这样,我们就不需要在代码中手动缩放叠加。</p>
<h3 id="创建自定义叠加">创建自定义叠加</h3>
<p>我们可以创建自己的自定义叠加。为此,我们需要为叠加重写<code>MKOverlay</code>类,为叠加视图重写<code>MKOverlayView</code>类。</p>
<h3 id="标准叠加对象">标准叠加对象</h3>
<p>除了<code>MKCircle</code>之外,其他标准叠加对象还包括<code>MKPolygon</code>用于创建多边形形状和<code>MKPolyline</code>用于创建折线,如在轨迹记录应用程序中。</p>
<h2 id="参考内容-1">参考内容</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>显示地图</em></p>
</li>
<li>
<p><em>添加地图注释</em></p>
</li>
</ul>
<h1 id="第十一章-图形和动画">第十一章. 图形和动画</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>动画化视图</p>
</li>
<li>
<p>变换视图</p>
</li>
<li>
<p>使用图像进行动画</p>
</li>
<li>
<p>动画化层</p>
</li>
<li>
<p>绘制线条和曲线</p>
</li>
<li>
<p>绘制形状</p>
</li>
<li>
<p>绘制文本</p>
</li>
<li>
<p>一个简单的绘图应用程序</p>
</li>
<li>
<p>创建图像上下文</p>
</li>
</ul>
<h1 id="简介-10">简介</h1>
<p>在本章中,我们将讨论自定义绘制和动画。iOS SDK 包含两个用于这些任务的非常有用的框架:Core Graphics 和 Core Animation。</p>
<p>这两个框架简化了在 UI 元素上动画化以及绘制 2D 图形的过程。有效使用这两个框架将在平淡无奇和令人惊叹的应用程序之间产生差异。毕竟,这两个框架在使 iOS 平台在其类别中独一无二方面发挥着非常重要的作用。</p>
<p>我们将学习如何为控件提供简单甚至更复杂的动画,以提供独特的用户体验。我们还将看到如何在屏幕上自定义绘制线条、曲线、形状和文本。最后,通过所有提供的示例,我们将创建两个绘图应用程序。</p>
<h1 id="动画化视图">动画化视图</h1>
<p>在这个菜谱中,我们将学习如何利用<code>UIKit</code>动画在屏幕上移动<code>UILabel</code>。</p>
<h2 id="准备工作-79">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>ViewAnimationApp</code>。在<code>MainController</code>的视图中添加一个标签和一个按钮。</p>
<h2 id="如何做到这一点-15">如何做到这一点...</h2>
<ol>
<li>
<p>添加<code>MonoTouch.ObjCRuntime</code>命名空间,并在以下<code>ViewDidLoad</code>重写中输入:</p>
<pre><code class="language-swift">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();
};
}
</code></pre>
</li>
<li>
<p>添加以下方法:</p>
<pre><code class="language-swift">
public void LabelAnimationStopped(){
this.lblOutput.Text = "Animation ended!";
this.lblOutput.BackgroundColor = UIColor.Red;
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>Animate!</strong>按钮,并观察标签移动到视图的下半部分。</p>
</li>
</ol>
<h2 id="它是如何工作的-65">它是如何工作的...</h2>
<p><code>UIView</code>类包含许多针对动画的静态方法。在这个例子中,我们只是通过动画改变了一个标签的位置。</p>
<p>要动画化位置的变化,我们需要在调用<code>BeginAnimations</code>方法之后应用这些更改:</p>
<pre><code class="language-swift">UIView.BeginAnimations("LabelPositionAnimation");
</code></pre>
<p>它接受一个字符串参数,该参数声明了动画的名称。在这次调用之后我们对视图所做的更改将被动画化。但,我们也可以调整各种动画参数:</p>
<pre><code class="language-swift">UIView.SetAnimationDuration(1);
UIView.SetAnimationCurve(UIViewAnimationCurve.EaseInOut);
</code></pre>
<p><code>SetAnimationDuration</code>方法定义了动画的持续时间(以秒为单位)。<code>SetAnimationCurve</code>方法定义了将在动画的起点和/或终点应用到的默认缓动函数。</p>
<p>当动画完成时,我们有执行代码的选项。为此,我们首先需要使用<code>SetAnimationDelegate</code>方法设置动画代理对象:</p>
<pre><code class="language-swift">UIView.SetAnimationDelegate(this);
</code></pre>
<p>在这个例子中,我们将我们的控制器对象<code>MainController</code>设置为动画代理对象。在设置代理对象后,我们需要设置当动画完成时将被调用的选择器:</p>
<pre><code class="language-swift">UIView.SetAnimationDidStopSelector(new Selector("LabelPositionAnimationStopped"));
</code></pre>
<p>要创建<code>Selector</code>实例,我们需要使用<code>MonoTouch.ObjCRuntime</code>命名空间:</p>
<pre><code class="language-swift">using MonoTouch.ObjCRuntime;
</code></pre>
<p>在对动画进行所有调整后,我们将新值设置到将被动画化的对象上,并调用<code>CommitAnimations</code>方法:</p>
<pre><code class="language-swift">this.lblOutput.Frame = labelFrame;
UIView.CommitAnimations();
</code></pre>
<p>注意,在 <code>BeginAnimations</code> 调用下面的代码将在 <code>CommitAnimations</code> 行执行。此外,每个使用 <code>BeginAnimations</code> 方法开始的动画都应该有一个相应的 <code>CommitAnimations</code> 方法调用,否则可能会出现意外结果;例如,对 UI 元素所做的每个更改都将被动画化。</p>
<h2 id="还有更多-36">还有更多...</h2>
<p><code>UIView</code> 类还包含一个重载的 <code>Animate</code> 方法。此方法基本上将我们在这里使用的方法封装在一个方法中。使用 <code>Animate</code> 方法的前一个示例表示为以下代码:</p>
<pre><code class="language-swift">UIView.Animate(1, 0, UIViewAnimationOptions.CurveEaseInOut, delegate { this.lblOutput.Frame = labelFrame; }, delegate { this.LabelAnimationStopped(); } );
</code></pre>
<p>此重载的第二个参数是动画开始后的延迟。</p>
<h3 id="uikit-动画和-ios-版本">UIKit 动画和 iOS 版本</h3>
<p><code>Animate</code> 方法是在 iOS 4.0 版本中引入的。当针对低于 4 的 iOS 版本时,使用由 <code>BeginAnimations</code> 和 <code>CommitAnimations</code> 方法定义的动画块。</p>
<h3 id="可动画属性">可动画属性</h3>
<p><code>UIKit</code> 动画支持一组特定的 <code>UIView</code> 属性。这些属性被称为 <strong>可动画属性</strong>。以下是可被动画化的 <code>UIView</code> 属性列表:</p>
<ul>
<li>
<p><code>Frame</code></p>
</li>
<li>
<p><code>Bounds</code></p>
</li>
<li>
<p><code>Center</code></p>
</li>
<li>
<p><code>Transform</code></p>
</li>
<li>
<p><code>Alpha</code></p>
</li>
<li>
<p><code>BackgroundColor</code></p>
</li>
<li>
<p><code>ContentStretch</code></p>
</li>
</ul>
<h1 id="视图变换">视图变换</h1>
<p>在这个菜谱中,我们将通过应用变换来旋转 <code>UILabel</code>。此外,旋转将会有动画效果。</p>
<h2 id="准备工作-80">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>TransformViewApp</code>。在 <code>MainController</code> 的视图中添加一个标签和一个按钮。</p>
<h2 id="如何实现-8">如何实现...</h2>
<ol>
<li>
<p>添加 <code>MonoTouch.CoreGraphics</code> 命名空间:</p>
<pre><code class="language-swift">using MonoTouch.CoreGraphics;
</code></pre>
</li>
<li>
<p>在 <code>MainController</code> 类中输入以下代码:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击按钮并观察标签旋转。</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_01.jpg"></p>
<h2 id="工作原理-14">工作原理...</h2>
<p><code>MonoTouch.CoreGraphics</code> 命名空间是 <code>CoreGraphics</code> 框架的包装器。这个框架是 iOS 的基本图形框架。</p>
<p>要旋转视图,我们需要一个变换对象,该对象将通过视图的 <code>Transform</code> 属性应用于视图:</p>
<pre><code class="language-swift">CGAffineTransform transform = CGAffineTransform.MakeRotation( (float)this.DegreesToRadians(this.rotationAngle));
</code></pre>
<p>变换对象是 <code>CGAffineTransform</code> 类的实例,并通过 <code>MakeRotation</code> 静态方法初始化。此方法接受一个浮点值,表示我们想要应用的角度,单位为弧度。可以使用 <code>DegreesToRadians</code> 方法将度转换为弧度。创建变换对象后,我们将其分配给标签的 <code>Transform</code> 属性,在动画块内部:</p>
<pre><code class="language-swift">UIView.BeginAnimations("RotateLabelAnimation");
UIView.SetAnimationDuration(0.5f);
this.lblOutput.Transform = transform;
UIView.CommitAnimations();
</code></pre>
<p>注意,每次按下按钮时,我们需要增加旋转角度,因为我们应用的变化不会被自动增加。如果我们应用另一个具有相同角度的旋转变换对象,将不会有任何效果,因为它基本上是相同的变化。</p>
<p>当标签旋转到完整圆(=360 度)时,我们重置 <code>rotationAngle</code> 值和变换对象:</p>
<pre><code class="language-swift">this.rotationAngle = 0;
this.lblOutput.Transform = CGAffineTransform.MakeIdentity();
</code></pre>
<p><code>MakeIdentity</code>静态方法创建一个恒等变换对象,这是在应用变换对象之前所有视图的默认变换。</p>
<h2 id="还有更多-37">还有更多...</h2>
<p><code>CGAffineTransform</code>类包含用于创建变换对象的静态方法。这些是:</p>
<ul>
<li>
<p><code>CGAffineTransformInvert:</code> 此方法反转当前变换并返回结果</p>
</li>
<li>
<p><code>MakeIdentity:</code> 此方法创建一个恒等变换</p>
</li>
<li>
<p><code>MakeRotation:</code> 此方法创建一个旋转变换</p>
</li>
<li>
<p><code>MakeScale:</code> 此方法创建一个缩放变换</p>
</li>
<li>
<p><code>MakeTranslation:</code> 此方法创建一个平移变换</p>
</li>
<li>
<p><code>Multiply:</code> 此方法将两个变换相乘并返回结果</p>
</li>
</ul>
<h3 id="变换和框架">变换和框架</h3>
<p>在对一个视图应用变换后,其<code>Frame</code>属性不应被考虑。如果需要在变换后更改视图的大小或位置,请分别使用<code>Bounds</code>和<code>Center</code>属性。</p>
<h2 id="相关内容-26">相关内容</h2>
<p>在这一章中:</p>
<ul>
<li>
<p><em>动画视图</em></p>
</li>
<li>
<p><em>动画层</em></p>
</li>
</ul>
<h1 id="使用图像的动画">使用图像的动画</h1>
<p>在这个菜谱中,我们将使用<code>UIImageView</code>内置的动画功能创建一个简单的图片幻灯片。</p>
<h2 id="准备工作-81">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>ImageAnimationApp</code>。在<code>MainController</code>的视图中添加一个<code>UIImageView</code>和两个按钮。此任务的示例项目包含三张图片。将两张或更多图片添加到项目中,并将它们的<strong>构建操作</strong>设置为<strong>内容</strong>。</p>
<h2 id="如何做到这一点-16">如何做到这一点...</h2>
<ol>
<li>
<p>输入以下<code>ViewDidLoad</code>重写:</p>
<pre><code class="language-swift">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();
}
};
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>动画图像</strong>按钮以开始动画。</p>
</li>
</ol>
<h2 id="它是如何工作的-66">它是如何工作的...</h2>
<p><code>UIImageView</code>可以接受一个<code>UIImage</code>对象的数组,并自动按顺序显示它们。</p>
<p>要加载视图将动画的图像,将图像的数组分配给其<code>AnimationImages</code>属性:</p>
<pre><code class="language-swift">this.imageView.AnimationImages = new UIImage[] {
UIImage.FromFile("images/Kastoria.jpg"),
UIImage.FromFile("images/Parga02.jpg"),
UIImage.FromFile("images/Toroni.jpg")
} ;
</code></pre>
<p>图像显示的顺序由它们在数组中的顺序定义。在设置将动画的图像后,我们设置动画的持续时间(以秒为单位)和它将发生的次数:</p>
<pre><code class="language-swift">this.imageView.AnimationDuration = 3;
this.imageView.AnimationRepeatCount = 10;
</code></pre>
<p>要开始或停止动画,分别调用<code>StartAnimating</code>或<code>StopAnimating</code>方法。</p>
<h2 id="还有更多-38">还有更多...</h2>
<p><code>UIImageView</code>的<code>AnimationImages</code>属性和<code>Image</code>属性之间没有关系。如果需要在没有动画时显示图像,请在动画前后将图像设置到<code>Image</code>属性中。</p>
<h3 id="检查动画">检查动画</h3>
<p>要确定是否发生动画,请检查<code>UIImageView</code>的<code>IsAnimating</code>属性。</p>
<h2 id="相关内容-27">相关内容</h2>
<p>在这一章中:</p>
<ul>
<li><em>动画视图</em></li>
</ul>
<p>在这本书中:</p>
<p>第二章,用户界面:视图:</p>
<ul>
<li><em>显示图像</em></li>
</ul>
<h1 id="动画层">动画层</h1>
<p>在这个菜谱中,我们将学习如何使用<code>Core Animation</code>框架通过动画其层来复制屏幕上的<code>UILabel</code>。</p>
<h2 id="准备工作-82">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>LayerAnimation</code>。在 <code>MainController</code> 的视图中添加两个标签和一个按钮。为第一个视图设置文本和背景颜色,并为第二个视图设置不同的背景颜色。</p>
<h2 id="如何做-15">如何做...</h2>
<ol>
<li>
<p>添加 <code>MonoTouch.CoreAnimation</code> 命名空间:</p>
<pre><code class="language-swift">using MonoTouch.CoreAnimation;
</code></pre>
</li>
<li>
<p>在类中添加一个 <code>CALayer</code> 类型的字段:</p>
<pre><code class="language-swift">private CALayer copyLayer;
</code></pre>
</li>
<li>
<p>在 <code>ViewDidLoad</code> 覆盖中添加以下代码:</p>
<pre><code class="language-swift">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");
} ;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击 <strong>复制</strong> 按钮以动画方式将第一个标签的内容复制到第二个标签。</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_02.jpg"></p>
<h2 id="它是如何工作的-67">它是如何工作的...</h2>
<p><code>MonoTouch.CoreAnimation</code> 命名空间是 <code>Core Animation</code> 框架的包装器。</p>
<p>每个视图都有一个 <code>Layer</code> 属性,它返回视图的 <code>CALayer</code> 对象。在这个任务中,我们正在创建一个动画,它以图形方式显示从一个标签复制内容到另一个标签。</p>
<p>我们不是创建另一个标签并用 <code>UIView</code> 动画移动它,而是创建一个层并移动它。我们通过设置其 <code>Frame</code> 和 <code>Contents</code> 属性来创建层,后者来自源标签的层。然后我们使用 <code>AddSublayer</code> 方法将层添加到主视图的层中。从这一点开始,主视图包含一个层,它显示相同的内容,并且位于源标签之上。</p>
<pre><code class="language-swift">this.copyLayer = new CALayer();
this.copyLayer.Frame = this.lblSource.Frame;
this.copyLayer.Contents = this.lblSource.Layer.Contents;
this.View.Layer.AddSublayer(this.copyLayer);
</code></pre>
<p>要动画化从源标签到目标标签的过渡,我们将使用 <code>CABasicAnimation</code> 类。前面的高亮代码展示了如何初始化和设置类的实例。<code>FromKeyPath</code> 静态方法创建一个新的实例,接受作为参数的将被动画化的层的属性名称。<code>To</code> 属性表示属性将被动画到的值。<code>Duration</code> 属性表示动画的持续时间(以秒为单位),而 <code>RemovedOnCompletion</code> 属性声明当动画完成时应该从层中移除动画对象。<code>TimingFunction</code> 属性设置动画的行为。当动画完成时,会触发 <code>AnimationStopped</code> 事件。在分配给它的处理程序内部,我们将源标签的内容设置为目标标签,从而完成复制。<code>AutoReverses</code> 属性表示当 <code>To</code> 属性的值达到时,动画应该反转。正是这个属性使得标签在达到最终位置时先变大然后变小。</p>
<p>当动画被添加到层中时,动画开始:</p>
<pre><code class="language-swift">this.copyLayer.AddAnimation(positionAnimation, "PositionAnimation");
this.copyLayer.AddAnimation(sizeAnimation, "SizeAnimation");
</code></pre>
<h2 id="还有更多-39">还有更多...</h2>
<p>可以在以下链接中找到 <code>FromKeyPath</code> 方法接受的字符串列表:<code>http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/KVCAdditions.html#//apple_ref/doc/uid/TP40005299</code>。</p>
<p>除了 <code>To</code> 属性外,<code>CABasicAnimation</code> 类还有两个用于定义动画的属性:<code>From</code> 和 <code>By</code>。它们都是 <code>NSObject</code> 类型,但应该分配给它们的实际值应该是 <code>NSValue</code> 类型。<code>NSValue</code> 类包含创建其实例的各种静态方法。</p>
<h3 id="层">层</h3>
<p><strong>层</strong>是非常强大且高效的用于绘制和动画的对象。强烈建议使用层在视图上执行动画,而不是使用实际的视图本身。</p>
<h2 id="参见-52">参见</h2>
<p>在本章</p>
<ul>
<li><em>动画视图</em></li>
</ul>
<h1 id="绘制线条和曲线">绘制线条和曲线</h1>
<p>在这个菜谱中,我们将实现自定义绘制,在 <code>UIView</code> 上绘制两条线。</p>
<h2 id="准备工作-83">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>DrawLineApp</code>。</p>
<h2 id="如何操作-39">如何操作...</h2>
<ol>
<li>
<p>向项目中添加一个新类,并将其命名为 <code>DrawingView</code>。从 <code>UIView</code> 派生它:</p>
<pre><code class="language-swift">public class DrawingView : UIView
</code></pre>
</li>
<li>
<p>在 <code>DrawingView.cs</code> 文件中添加以下 <code>using</code> 指令:</p>
<pre><code class="language-swift">using MonoTouch.CoreGraphics;
</code></pre>
</li>
<li>
<p>覆盖 <code>UIView</code> 的 <code>Draw</code> 方法,并使用以下代码实现它:</p>
<pre><code class="language-swift">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();
}
}
</code></pre>
</li>
<li>
<p>在 <code>MainController</code> 的 <code>ViewDidLoad</code> 覆盖中,初始化并添加视图:</p>
<pre><code class="language-swift">DrawingView drawingView = new DrawingView(this.View.Bounds);
drawingView.BackgroundColor = UIColor.Gray;
this.View.AddSubview(this.drawingView);
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。结果应该类似于以下内容:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_03a.jpg"></p>
</li>
</ol>
<h2 id="它是如何工作的-68">它是如何工作的...</h2>
<p><code>MonoTouch.CoreGraphics</code> 命名空间是对原生 <code>Core Graphics</code> 框架的封装。<code>Core Graphics</code> 框架包含了在视图中进行自定义绘制的必要对象。</p>
<p>要在视图上绘制,我们必须覆盖其 <code>Draw(RectangleF)</code> 方法:</p>
<pre><code class="language-swift">public override void Draw (RectangleF rect)
</code></pre>
<p>在 <code>Draw</code> 方法内部,我们需要当前图形上下文的实例:</p>
<pre><code class="language-swift">using (CGContext context = UIGraphics.GetCurrentContext())
</code></pre>
<p>图形上下文由 <code>CGContext</code> 类表示。<code>UIGraphics.GetCurrentContext</code> 静态方法返回当前上下文的一个实例。</p>
<p><code>CGContext</code> 类包含各种方法,允许我们在视图上绘制。我们需要设置线宽、颜色,然后添加绘制类型:</p>
<pre><code class="language-swift">context.SetLineWidth(5f);
context.SetStrokeColorWithColor(UIColor.Green.CGColor);
context.AddLines(new PointF[] {
new PointF(0f, this.Bounds.Height),
new PointF(this.Bounds.Width, 0f)
} );
</code></pre>
<p>要添加线条,我们使用接受包含每条线的起点和终点的 <code>PointF</code> 结构数组 <code>AddLines</code> 方法。仅仅将线条添加到上下文中是不够的。为了在视图上呈现绘制内容,我们调用 <code>StrokePath</code> 方法:</p>
<pre><code class="language-swift">context.StrokePath();
</code></pre>
<p>要向绘制中添加另一个项目,我们相应地重复步骤。<code>MoveTo</code> 方法将当前点移动,以便附加项目将有一个曲线的起点。</p>
<h2 id="还有更多-40">还有更多...</h2>
<p>当运行时需要绘制视图的内容时,会调用 <code>Draw</code> 方法。我们只能在 <code>Draw</code> 方法内部获取当前图形上下文的实例。我们不应该直接调用它,因为如果这样做,<code>UIGraphics.GetCurrentContext</code> 方法将返回 <code>null</code>。如果我们需要强制运行时调用 <code>Draw</code> 方法,我们需要调用 <code>SetNeedsDisplay()</code>。在调用它时应该小心,因为绘制操作在 CPU 使用方面代价高昂。</p>
<p>当不需要重新绘制整个视图区域时,我们可以调用<code>SetNeedsDisplayInRect</code>方法,传递视图坐标系中要更新的区域的<code>RectangleF</code>。</p>
<h3 id="在-uiimageview-上的图形上下文">在 UIImageView 上的图形上下文</h3>
<p><code>UIImageView</code>的当前图形上下文保留用于绘制图像内容。在继承自<code>UIImageView</code>的自定义视图中调用<code>SetNeedsDisplay</code>与直接调用<code>Draw</code>方法具有相同的效果。如果我们需要在自定义图像视图中绘制,我们必须要么在其上方添加另一个视图并在该视图上绘制,要么在自定义图层上绘制并将其添加到视图的主图层中。</p>
<h2 id="参见-53">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>绘制文本</em></li>
</ul>
<p>在本书中:</p>
<p>第二章, 用户界面:视图:</p>
<ul>
<li><em>创建</em>一个<em>自定义视图</em></li>
</ul>
<h1 id="绘制形状">绘制形状</h1>
<p>按照前一个食谱中的示例,我们将在屏幕上绘制一个圆和一个正方形。</p>
<h2 id="准备工作-84">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>DrawShapeApp</code>。添加一个类似于之前的任务的自定义视图,并将其命名为<code>DrawingView</code>。</p>
<h2 id="如何做-16">如何做...</h2>
<ol>
<li>
<p>在<code>Draw</code>方法的重写中添加以下代码:</p>
<pre><code class="language-swift">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();
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。屏幕上的结果应类似于以下内容:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_04.jpg"></p>
<h2 id="它是如何工作的-69">它是如何工作的...</h2>
<p>要在视图中绘制形状,我们需要调用适当的方法。我们首先设置<code>CGContext</code>实例的填充颜色:</p>
<pre><code class="language-swift">context.SetFillColorWithColor(UIColor.Blue.CGColor);
</code></pre>
<p>要绘制圆,我们调用<code>AddEllipseInRect</code>方法,传递一个包含圆边界矩形的<code>RectangleF</code>对象:</p>
<pre><code class="language-swift">context.AddEllipseInRect(new RectangleF(100f, 100f, 100f, 100f));
</code></pre>
<p>形状是否为椭圆或绝对圆由边界矩形的尺寸定义。然后我们调用<code>FillPath</code>方法:</p>
<pre><code class="language-swift">context.FillPath();
</code></pre>
<p>阴影效果由<code>SetShadow</code>方法定义:</p>
<pre><code class="language-swift">context.SetShadow(new SizeF(10f, 10f), 5f);
</code></pre>
<p>第一个参数,其类型为<code>SizeF</code>,定义了阴影的偏移量,而第二个参数定义了模糊量。</p>
<h2 id="还有更多-41">还有更多...</h2>
<p>当调用<code>SetShadow</code>方法时,所有添加到上下文中的对象都会显示阴影。要移除阴影,调用<code>SetShadowWithColor</code>方法,传递一个完全透明的颜色或为颜色参数传递<code>null</code>。</p>
<h3 id="透明颜色">透明颜色</h3>
<p>要用透明色填充形状,创建一个具有适当值的<code>CGColor</code>实例:</p>
<pre><code class="language-swift">context.SetFillColorWithColor(new CGColor(1f, 0f, 0f, 0.5f));
</code></pre>
<p>这将创建一个红色,其不透明度设置为 50%的颜色。</p>
<h2 id="参见-54">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>绘制线条和曲线</em></li>
</ul>
<h1 id="绘制文本">绘制文本</h1>
<p>在本食谱中,我们将学习如何使用轮廓绘制样式文本。</p>
<h2 id="准备工作-85">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>DrawTextApp</code>。将我们在之前的任务中创建的<code>DrawingView</code>类添加到项目中。</p>
<h2 id="如何做-17">如何做...</h2>
<ol>
<li>
<p>在<code>DrawingView</code>类中实现以下<code>Draw</code>方法的重写:</p>
<pre><code class="language-swift">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));
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。文本将在屏幕上显示。结果应类似于以下内容:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_05.jpg"></p>
<h2 id="它是如何工作的-70">它是如何工作的...</h2>
<p><code>NSString</code> 类包含一个非常有用的方法 <code>DrawString</code>,它将包含的文本绘制到当前上下文中。为了提供轮廓效果,我们调用 <code>SetTextDrawingMode</code> 方法:</p>
<pre><code class="language-swift">context.SetTextDrawingMode(CGTextDrawingMode.FillStroke);
</code></pre>
<p>我们传递了 <code>CGTextDrawingMode.FillStroke</code> 值。由于我们没有为上下文设置描边颜色,它默认为黑色。</p>
<p>最后,调用 <code>DrawString</code> 方法:</p>
<pre><code class="language-swift">drawText.DrawString(new PointF(10f, 100f), UIFont.FromName( "Verdana-Bold", 28f));
</code></pre>
<p>此方法有多个重载。我们在这里使用的是接受一个 <code>PointF</code> 结构的重载,它表示字符串在视图坐标系中的位置,以及一个 <code>UIFont</code> 实例,它表示文本将在屏幕上通过该字体渲染。</p>
<h2 id="还有更多-42">还有更多...</h2>
<p><code>CGContext</code> 类包含用于绘制文本的方法。我们首先需要调用 <code>SelectFont</code> 方法来分配字体:</p>
<pre><code class="language-swift">context.SelectFont("Verdana-Bold", 28f, CGTextEncoding.MacRoman);
</code></pre>
<p>然后我们调用 <code>ShowTextAtPoint</code> 方法来绘制文本:</p>
<pre><code class="language-swift">context.ShowTextAtPoint(10, 100, drawText.ToString());
</code></pre>
<p>这将给出以下结果:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_06.jpg"></p>
<p>文本将显示在正确的位置,但方向相反。为了纠正这一点,我们需要将一个变换矩阵设置到 <code>TextMatrix</code> 属性:</p>
<pre><code class="language-swift">context.TextMatrix = new CGAffineTransform(1, 0, 0, -1, 0, 0);
</code></pre>
<p>使用 <code>CGContext</code> 类的方法的最大优点是我们可以轻松地变换文本的外观。例如,通过应用一个稍微不同的变换矩阵,我们可以轻松地显示倾斜的文本:</p>
<pre><code class="language-swift">context.TextMatrix = new CGAffineTransform(1, 1, 0, -1, 0, 0);
</code></pre>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_07.jpg"></p>
<h3 id="绘制文本的大小">绘制文本的大小</h3>
<p><code>NSString</code> 类的 <code>DrawString</code> 方法返回文本的边界矩形的大小。然而,我们可以通过 <code>StringSize</code> 方法在绘制之前获取文本的大小:</p>
<pre><code class="language-swift">Console.WriteLine("Text size: {0}", drawText.StringSize(UIFont.FromName("Verdana-Bold", 28f)));
</code></pre>
<h2 id="参见-55">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>绘制线条和曲线</em></p>
</li>
<li>
<p><em>绘制形状</em></p>
</li>
</ul>
<h1 id="一个简单的绘图应用程序">一个简单的绘图应用程序</h1>
<p>在这个菜谱中,我们将使用我们学到的技术来创建一个绘图应用程序。</p>
<h2 id="准备工作-86">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>FingerDrawingApp</code>。再次,我们需要一个自定义视图。添加一个继承自 <code>UIView</code> 的类,并将其命名为 <code>CanvasView</code>。</p>
<h2 id="如何实现-9">如何实现...</h2>
<ol>
<li>
<p>使用以下代码实现 <code>CanvasView</code> 类:</p>
<pre><code class="language-swift">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);
}
}
}
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。</p>
</li>
<li>
<p>用手指触摸并拖动(或使用光标点击并拖动)开始绘画!</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_11_08.jpg"></p>
<h2 id="它是如何工作的-71">它是如何工作的...</h2>
<p>在这个任务中,我们将结合触摸事件和自定义绘制来创建一个简单的绘图应用程序。当用户触摸并在屏幕上移动手指时,我们保留触摸位置点的信息,并在 <code>Draw</code> 方法中使用这些信息来绘制线条。</p>
<p>在将触摸位置设置为类字段后,我们调用 <code>SetNeedsDisplay</code> 来强制调用 <code>Draw</code> 方法。<code>fingerDraw</code> 字段用于确定 <code>Draw</code> 方法是由屏幕上的触摸触发的,而不是在视图首次加载时由运行时触发的。</p>
<p>每次我们调用一个方法将某个东西绘制到图形上下文中时,该上下文中的先前绘图都会被清除。为了避免这种行为,我们使用<code>CGPath</code>对象。我们可以在<code>CGPath</code>中添加各种绘图对象,并通过将它们添加到图形上下文中来显示这些对象。因此,每次用户在屏幕上移动手指时,由触摸位置点定义的新线就会被添加到路径中,并且路径会在当前上下文中绘制。</p>
<p>注意,我们需要保留当前触摸位置和上一个位置的信息。这是因为<code>AddLineToPoint</code>方法接受一个点,该点定义了线的终点,假设路径中已经有一个点。每条线的起点是通过调用<code>MoveToPoint</code>,传递上一个触摸位置点来定义的。</p>
<p>通过在屏幕上滑动手指绘制的路径基本上由一系列连续的直线组成。然而,由于<code>TouchesMoved</code>方法在手指在屏幕上每次移动时都会被触发,因此结果是平滑的路径,它遵循手指的移动。</p>
<p>在将线添加到路径后,我们将其添加到上下文中并绘制它:</p>
<pre><code class="language-swift">context.AddPath(this.drawPath);
context.DrawPath(CGPathDrawingMode.Stroke);
</code></pre>
<h2 id="更多内容-38">更多内容...</h2>
<p>在本任务中引入了两个新的<code>CGContext</code>方法:<code>SetLineJoin</code>和<code>SetLineCap</code>。<code>SetLineJoin</code>方法设置每条线如何与前一条线连接,而<code>SetLineCap</code>设置线的端点外观。</p>
<p>它们接受的值解释如下:</p>
<ul>
<li>
<p><code>SetLineJoin</code></p>
<ul>
<li>
<p><code>CGLineJoin.Miter:</code> 以斜角连接两条线</p>
</li>
<li>
<p><code>CGLineJoin.Round:</code> 以圆角连接两条线</p>
</li>
<li>
<p><code>CGLineJoin.Bevel:</code> 以方形端点连接两条线</p>
</li>
</ul>
</li>
<li>
<p><code>SetLineCap</code></p>
<ul>
<li>
<p><code>CGLineCap.Butt:</code> 线将以端点上的方形边缘结束</p>
</li>
<li>
<p><code>CGLineCap.Round:</code> 线将以圆角结束,并扩展到端点之外</p>
</li>
<li>
<p><code>CGLineCap.Square:</code> 线将以扩展到端点之外的方形边缘结束</p>
</li>
</ul>
</li>
</ul>
<h3 id="清除绘图">清除绘图</h3>
<p>为了清除绘图,我们只需将<code>fingerDraw</code>变量设置为<code>false</code>并调用<code>SetNeedsDisplay</code>。这样,<code>Draw</code>方法将不会调用我们的自定义绘图代码,从而清除当前上下文。</p>
<h2 id="参见-56">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>绘制线条和曲线</em></p>
</li>
<li>
<p><em>绘制形状</em></p>
</li>
<li>
<p><em>绘制文本</em></p>
</li>
</ul>
<h1 id="创建图像上下文">创建图像上下文</h1>
<p>在本食谱中,我们将通过为用户提供保存创建的绘图的功能来扩展我们之前创建的手指绘图应用程序。</p>
<h2 id="准备工作-87">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>ImageContextApp</code>。将我们在上一个任务中创建的<code>CanvasView</code>类添加到项目中。</p>
<h2 id="如何操作-40">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加两个按钮。一个将用于保存图像,另一个用于清除当前绘图。</p>
</li>
<li>
<p>在<code>CanvasView</code>类中添加以下方法:</p>
<pre><code class="language-swift">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();
}
</code></pre>
</li>
<li>
<p>在<code>MainController</code>类中添加以下代码:</p>
<pre><code class="language-swift">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();
} ;
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>在画布上绘制一些东西,然后点击 <strong>保存绘图</strong> 按钮来保存你的绘图。</p>
</li>
<li>
<p>点击 <strong>清除</strong> 绘图按钮来清除画布。然后你可以检查模拟器的照片库以查看你的绘图。</p>
</li>
</ol>
<h2 id="工作原理-15">工作原理...</h2>
<p>使用 <code>UIGraphics</code> 类,我们可以创建一个图像上下文,通过这个上下文我们可以从 <code>UIImage</code> 对象中检索我们的绘图。</p>
<p>要创建图像上下文,在 <code>GetDrawingImage</code> 方法中我们调用 <code>BeginImageContext</code> 静态方法,传递我们想要图像上下文具有的大小:</p>
<pre><code class="language-swift">UIGraphics.BeginImageContext(this.Bounds.Size);
</code></pre>
<p>当前上下文现在是我们使用 <code>BeginImageContext</code> 调用创建的图像上下文。然后我们重复 <code>Draw</code> 方法中的代码,只是这次不需要向路径中添加新线条。我们只需将已有的路径添加到上下文中并绘制它。</p>
<p>添加路径后,我们通过调用 <code>GetImageFromCurrentContext</code> 方法获取上下文图像:</p>
<pre><code class="language-swift">toReturn = UIGraphics.GetImageFromCurrentImageContext();
</code></pre>
<p>最后,我们必须结束图像上下文块并返回 <code>UIImage</code> 对象:</p>
<pre><code class="language-swift">UIGraphics.EndImageContext();
return toReturn;
</code></pre>
<p>要从屏幕上清除绘图,我们只需将 <code>fingerDraw</code> 变量设置为 <code>false</code>,并在 <code>ClearDrawing</code> 方法中处理并准备我们的 <code>CGPath</code> 对象以供重新使用:</p>
<pre><code class="language-swift">this.fingerDraw = false;
this.drawPath.Dispose();
this.drawPath = new CGPath();
</code></pre>
<p>要立即在屏幕上反映清除操作,我们调用 <code>SetNeedsDisplay</code> 方法:</p>
<pre><code class="language-swift">this.SetNeedsDisplay();
</code></pre>
<h2 id="更多-9">更多...</h2>
<p>我们不能在 <code>Draw</code> 方法内部创建图像上下文。这是因为当我们调用 <code>BeginImageContext</code> 方法时,实际上创建了一个上下文,但视图的默认上下文仍然是当前上下文。因此,<code>GetImageFromCurrentImageContext</code> 方法会返回 <code>null</code>。</p>
<h3 id="在-uiimageview-上绘制">在 <code>UIImageView</code> 上绘制</h3>
<p>这里讨论的技术可以用来在自定义 <code>UIImageViews</code> 上绘制。要显示当手指在屏幕上滑动时的绘图,我们只需将其 <code>Image</code> 属性设置为从图像上下文中获取的图像。</p>
<h3 id="保存绘图的背景信息">保存绘图的背景信息</h3>
<p>你会注意到,尽管我们将 <code>CanvasView</code> 的背景设置为灰色,但保存的绘图却是白色背景。这是因为视图的背景颜色不包括在绘图内。要包括它,我们只需将与背景颜色相同的矩形绘制到图形上下文中。</p>
<h2 id="参见-57">参见</h2>
<p>在本章中:</p>
<ul>
<li>
<p><em>绘制线条和曲线</em></p>
</li>
<li>
<p><em>绘制形状</em></p>
</li>
<li>
<p><em>绘制文本</em></p>
</li>
<li>
<p><em>简单绘图</em></p>
</li>
</ul>
<h1 id="第十二章多任务">第十二章。多任务</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>检测应用程序状态</p>
</li>
<li>
<p>接收应用程序状态的通知</p>
</li>
<li>
<p>在后台运行代码</p>
</li>
<li>
<p>在后台播放音频</p>
</li>
<li>
<p>网络连接维护</p>
</li>
</ul>
<h1 id="简介-11">简介</h1>
<p>当 2007 年推出 iOS 平台时,为用户带来了许多令人兴奋的新功能,它彻底改变了移动设备的概念。</p>
<p>尽管当时它取得了巨大的成功,但它缺少了一些被认为是“基本”的功能。其中之一就是多任务处理;即同时运行多个进程的支持。实际上,该平台在内部支持多任务处理系统进程,但这对开发者来说并不可用。从 iOS 4 开始,苹果提供了对多任务处理的支持,尽管它与大多数开发者习惯的方式仍然有很大不同。</p>
<p>在本章中,我们将讨论如何利用平台的多任务功能。我们将了解在什么情况下可以使用这些功能,以及我们可以通过多任务为应用程序用户提供哪些功能。具体来说,我们将学习应用程序的状态及其运行时生命周期。通过一系列详细的示例项目,我们能够在应用程序后台执行代码,支持音频播放和 VoIP 连接维护。</p>
<h1 id="检测应用程序状态">检测应用程序状态</h1>
<p>在本配方中,我们将讨论如何检测应用程序从活动状态到非活动状态以及相反状态的变化,并相应地做出反应。</p>
<h2 id="准备就绪">准备就绪</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>AppStateApp</code>。在这个例子中不需要视图控制器。</p>
<h2 id="如何操作-41">如何操作...</h2>
<ol>
<li>
<p>在<code>AppDelegate</code>类中添加以下方法重写:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在模拟器或设备上编译并运行应用程序。</p>
</li>
<li>
<p>按下<strong>主页</strong>按钮以挂起应用程序,并观察 MonoDevelop 中的<strong>应用程序输出</strong>面板。</p>
</li>
</ol>
<h2 id="它是如何工作的-72">它是如何工作的...</h2>
<p><code>UIApplicationDelegate</code>类包含由运行时发出的特定通知触发的方法。这些方法是:</p>
<ul>
<li>
<p><code>OnActivated:</code> 当应用程序变为活动状态时,会调用此方法,例如,当解锁屏幕或应用程序启动时。</p>
</li>
<li>
<p><code>OnResignActivation:</code> 当应用程序即将变为非活动状态时,会调用此方法,例如,当屏幕锁定或显示多任务栏时。</p>
</li>
<li>
<p><code>DidEnterBackground:</code> 当应用程序进入后台时,会调用此方法,例如,当按下<strong>主页</strong>按钮。应用程序被挂起。</p>
</li>
<li>
<p><code>WillEnterForeground:</code> 当应用程序即将返回前台时,会调用此方法。</p>
</li>
</ul>
<p>注意,当应用程序移动到后台时,会调用<code>OnResignActivation</code>和<code>DidEnterBackground</code>方法。同样,当应用程序移动到前台时,会调用<code>WillEnterForeground</code>和<code>OnActivated</code>方法。</p>
<p>所有这些方法都包含一个参数,该参数包含应用程序的 <code>UIApplication</code> 实例。<code>UIApplication</code> 类包含一个属性 <code>ApplicationState</code>,它返回 <code>UIApplicationState</code> 属性中的应用程序状态值。这些值是:</p>
<ul>
<li>
<p><code>Active:</code> 此值表示应用程序处于活动状态</p>
</li>
<li>
<p><code>Inactive:</code> 此值表示应用程序处于非活动状态,例如,当显示通知警报时</p>
</li>
<li>
<p><code>Background:</code> 此值表示应用程序处于后台</p>
</li>
</ul>
<h2 id="还有更多-43">还有更多...</h2>
<p>多任务处理是 iOS 4+ 的一个功能,并且并非所有设备都支持多任务处理,即使它们运行在 iOS 4+ 上。对于 4 版本之前的版本,当按下 <strong>主页</strong> 按钮时,会调用 <code>WillTerminate</code> 方法:</p>
<pre><code class="language-swift">public override void WillTerminate (UIApplication application){
Console.WriteLine("App will terminate!");
}
</code></pre>
<p>然而,在某些情况下,iOS 会终止您的应用程序;例如,当发出内存警告而您的应用程序没有释放资源时。在这些情况下,也会调用 <code>WillTerminate</code> 方法。</p>
<h3 id="多任务处理支持">多任务处理支持</h3>
<p>要检查设备是否支持多任务处理,请检查 <code>UIDevice.CurrentDevice.IsMultitaskingSupported</code> 属性。</p>
<h3 id="正确使用">正确使用</h3>
<p>这些方法非常有用,因为它们允许我们在应用程序状态改变时保存向用户展示的当前数据。当应用程序过渡到非活动或后台状态时,每个方法都有一定的时间限制来执行,因此我们应该确保它们不执行长时间运行的操作,否则 iOS 会终止应用程序。</p>
<h1 id="接收应用程序状态的通知">接收应用程序状态的通知</h1>
<p>在本菜谱中,我们将讨论在 <code>UIApplicationDelegate</code> 实现范围之外接收应用程序状态变化的通知。</p>
<h2 id="准备工作-88">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>NotifyStatesApp</code>。向项目中添加一个带有控制器的视图,并将其命名为 <code>MainController</code>。</p>
<h2 id="如何操作-42">如何操作...</h2>
<ol>
<li>
<p>在 <code>MainController</code> 类中输入以下字段:</p>
<pre><code class="language-swift">private NSObject didEnterBgdObserver;
private NSObject willEnterFgdObserver;
</code></pre>
</li>
<li>
<p>创建以下方法:</p>
<pre><code class="language-swift">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 });
}
</code></pre>
</li>
<li>
<p>在 <code>ViewDidLoad</code> 覆盖中,调用 <code>AddAppStateObservers</code> 方法:</p>
<pre><code class="language-swift">this.AddAppStateObservers();
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>按下 <strong>主页</strong> 按钮,并观察 <strong>应用程序输出</strong> 面板中的输出。它应该类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_12_01.jpg"></p>
<h2 id="它是如何工作的-73">它是如何工作的...</h2>
<p>除了调用 <code>UIApplicationDelegate</code> 对象的应用程序状态方法外,iOS 还会发出我们可以接收的通知。这非常有用,因为在大多数情况下,我们需要在 <code>AppDelegate</code> 类的范围之外接收应用程序状态变化的通知。</p>
<p>要实现这一点,我们使用 <code>NSNotificationCenter:</code></p>
<pre><code class="language-swift">this.didEnterBgdObserver = NSNotificationCenter.DefaultCenter. AddObserver(UIApplication.DidEnterBackgroundNotification, delegate(NSNotification obj) {
Console.WriteLine("App entered background, app state: {0}", UIApplication.SharedApplication.ApplicationState);
} );
</code></pre>
<p>我们感兴趣的通知密钥通过 <code>UIApplication</code> 静态属性公开。此示例仅添加了背景和前台之间的转换通知观察者。</p>
<p>结果类似于前一个菜谱的示例,但仅在 <code>MainController</code> 加载后。</p>
<h2 id="还有更多-44">还有更多...</h2>
<p>要为应用程序激活或停用激活时添加通知观察者,我们分别使用 <code>UIApplication.DidBecomeActiveNotification</code> 和 <code>UIApplication.WillResignActiveNotification</code> 键。</p>
<h3 id="移除通知观察者">移除通知观察者</h3>
<p>当不再需要通知观察者时,在 <code>ViewDidUnload</code> 覆盖方法中调用 <code>RemoveAppStateObservers</code> 方法:</p>
<pre><code class="language-swift">this.RemoveAppStateObservers();
</code></pre>
<h2 id="参见-58">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>检测应用程序状态</em></li>
</ul>
<h1 id="在后台运行代码">在后台运行代码</h1>
<p>在本食谱中,我们将学习如何在后台执行代码,充分利用 iOS 的多任务功能。</p>
<h2 id="准备工作-89">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为 <code>BackgroundCodeApp</code>。在这个例子中不需要视图控制器。</p>
<h2 id="如何做到这一点-17">如何做到这一点...</h2>
<ol>
<li>
<p>在 <code>AppDelegate</code> 类中输入以下代码:</p>
<pre><code class="language-swift">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!");
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译和运行应用程序。</p>
</li>
<li>
<p>按下<strong>主页</strong>按钮使应用程序进入后台,并观察<strong>应用程序输出</strong>。</p>
</li>
<li>
<p>在后台任务完成之前(一分钟),可以通过在多任务栏中点击其图标或在其<strong>主页</strong>屏幕上的图标来将应用程序带到前台。</p>
</li>
</ol>
<h2 id="它是如何工作的-74">它是如何工作的...</h2>
<p>在前面的任务中,我们学习了如何得知应用程序从前台到后台以及相反的转换。</p>
<p>iOS 上的多任务处理并不完全像我们在其他平台上所习惯的那样。iOS 平台确保前台应用程序将拥有所有可用的资源(以及用户的)。为了实现这一点,当应用程序进入后台时,它会被操作系统挂起。当它被挂起时,它不会执行任何代码。</p>
<p>如果我们想要防止用户按下<strong>主页</strong>按钮时应用程序被挂起,我们可以请求后台时间。我们请求的时间限制为 600 秒(10 分钟),这对于我们可能在后台执行的大多数任务来说已经足够了(例如,保存 UI 状态,完成文件下载/上传,关闭任何打开的连接,等等)。</p>
<p>要请求后台时间,我们调用我们的 <code>UIApplication</code> 实例的 <code>BeginBackgroundTask</code> 方法:</p>
<pre><code class="language-swift">this.taskID = application.BeginBackgroundTask(delegate {
application.EndBackgroundTask(taskID);
this.taskID = 0;
} );
</code></pre>
<p>该方法接受一个类型为 <code>NSAction</code> 的参数,并返回一个整数,该整数对应于任务 ID。<code>NSAction</code> 参数表示在后台时间结束前将要执行的代码块。在这个代码块内部,我们必须调用 <code>EndBackgroundTask</code> 方法,传递已启动的任务 ID,这将通知运行时我们不再需要后台时间。每次调用 <code>BeginBackgroundTask</code> 都应该跟随一个调用 <code>EndBackgroundTask</code>。如果我们不调用此方法并且后台时间结束,应用程序将被终止。</p>
<p>在调用<code>BeginBackgroundTask</code>方法后,我们可以执行我们想要的代码。为了允许<code>DidEnterBackground</code>方法完成并避免阻塞主线程,我们只需将我们的代码封装到异步调用中,或者在一个单独的线程中。在这个例子中,我们使用<code>ThreadPool</code>中的线程。由于这个特定任务将在我们设定的超时时间之前完成,我们调用<code>EndBackgroundTask</code>方法来让系统知道工作已完成。我们传递给<code>BeginBackgroundTask</code>方法的代码块将不会执行,因为我们已经结束了任务。</p>
<p>然而,也有可能用户在后台任务仍在运行时将应用程序带到前台。为了覆盖这种情况,我们需要重写<code>WillEnterForeground</code>方法并适当地处理它。我们可以停止后台任务(通过调用<code>EndBackgroundTask</code>),或者向用户提供某种反馈,表明任务仍在运行。在这种情况下对我们的代码进行异步调用是最佳实践。如果我们的后台任务代码是同步的,当用户将应用程序带到前台且任务仍在运行时,应用程序将冻结,直到任务完成。</p>
<h2 id="更多-10">更多...</h2>
<p>要知道执行后台任务剩余多少时间,我们可以检查<code>BackgroundTimeRemaining</code>属性的值:</p>
<pre><code class="language-swift">Console.WriteLine("Remaining time: {0}", application.BackgroundTimeRemaining);
</code></pre>
<h3 id="后台代码的重要注意事项">后台代码的重要注意事项</h3>
<ul>
<li>
<p><strong>不要在应用程序后台更新 UI:</strong>这样做可能会导致您的应用程序被终止或崩溃。在应用程序后台发生的任何 UI 元素更新都将排队,在它返回前台时执行。这肯定会使得应用程序无响应。</p>
</li>
<li>
<p><strong>不要通知用户将您的应用程序带到前台,只是给任务更多时间:</strong>这样做肯定会使得您的应用程序在应用商店的审批过程中被拒绝。如果一个后台任务正在进行,而用户将应用程序带到前台,再次将应用程序移回后台基本上会重置后台时间。</p>
</li>
<li>
<p>在后台执行轻量级操作以避免运行时杀死您的应用程序。</p>
</li>
<li>
<p>避免使用外部资源(例如,通过资源库检索的资源)。</p>
</li>
</ul>
<h2 id="参见-59">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>检测应用程序状态</em></li>
</ul>
<h1 id="在后台播放音频">在后台播放音频</h1>
<p>在这个菜谱中,我们将学习如何防止应用程序挂起,以便允许音频播放。</p>
<h2 id="准备工作-90">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>BackgroundAudioApp</code>。在<code>MainController</code>的视图中添加一个按钮。</p>
<h2 id="如何做到-7">如何做到...</h2>
<ol>
<li>
<p>打开<code>Info.plist</code>文件,并添加键<code>UIBackgroundModes</code>。</p>
</li>
<li>
<p>在其下添加音频项。添加<code>MonoTouch.AVFoundation</code>命名空间。</p>
</li>
<li>
<p>在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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();
} ;
}
</code></pre>
</li>
<li>
<p>将声音文件添加到项目中,并将其<strong>构建操作</strong>设置为<strong>内容</strong>。本例使用一个名为<code>sound.m4a</code>的 20 秒长声音文件。</p>
</li>
<li>
<p>在设备上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>开始播放</strong>按钮,然后按<strong>主页</strong>按钮使应用程序进入后台。注意,声音仍在播放。</p>
</li>
</ol>
<h2 id="它是如何工作的-75">它是如何工作的...</h2>
<p>为了确保我们的应用程序在后台播放音频时能够工作,我们必须在<code>Info.plist</code>文件中的<code>UIBackgroundModes</code>键中设置音频项。</p>
<p>在这个例子中,我们使用<code>AVAudioPlayer</code>类来播放声音文件。然而,仅仅创建类的实例并调用其<code>Play</code>方法是不够的。我们必须为音频会话类别设置一个特定的类型:</p>
<pre><code class="language-swift">NSError error = null;
AVAudioSession.SharedInstance ().SetCategory (AVAudioSession.CategoryPlayback, out error);
</code></pre>
<p>静态方法<code>AVAudioSession.SharedInstance</code>返回当前的音频会话对象。音频会话类别设置为<code>AVAudioSession.CategoryPlayback</code>,这允许<code>AVAudioPlayer</code>在应用程序处于后台时播放声音。这个要求是针对<code>MonoTouch.AVFoundation</code>命名空间中的对象。</p>
<h2 id="还有更多-45">还有更多...</h2>
<p>可用的音频会话类别如下:</p>
<ul>
<li>
<p><code>CategoryAmbient:</code> 在这个类别中,当设备屏幕锁定或设备静音开关开启时,声音会被静音。来自外部资源(如 iPod 应用程序)的声音会与这个类别混合。</p>
</li>
<li>
<p><code>CategorySoloAmbient:</code> 这是默认类别。使用此类别,来自外部资源的声音会被静音。当设备屏幕锁定或设备静音开关开启时,声音会被静音。</p>
</li>
<li>
<p><code>CategoryPlayback:</code> 在这个类别中,当屏幕锁定或静音开关开启时,声音不会被静音。来自外部资源的声音会被静音,但如果将<code>MonoTouch.AudioToolbox.AudioSession.OverrideCategoryMixWithOthers</code>属性设置为<code>true</code>,则可以进行混合。</p>
</li>
<li>
<p><code>CategoryRecord:</code> 这个类别用于录音。所有音频播放都会被静音。即使屏幕锁定,录音也会继续。</p>
</li>
<li>
<p><code>CategoryPlayAndRecord:</code> 这个类别适用于需要录音和播放音频的应用程序。来自外部资源的声音会被静音,但如果将<code>MonoTouch.AudioToolbox.AudioSession.OverrideCategoryMixWithOthers</code>属性设置为<code>true</code>,则可以进行混合。当屏幕锁定或静音开关开启时,声音会继续播放。</p>
</li>
<li>
<p><code>CategoryAudioProcessing:</code> 这个类别专门用于处理音频。声音播放和录音被禁用。</p>
</li>
</ul>
<h3 id="音频的背景状态">音频的背景状态</h3>
<p>即使应用程序通过<code>Info.plist</code>文件配置为支持后台音频播放,当播放完成后,应用程序将被挂起。</p>
<h2 id="参见-60">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>网络连接维护</em></li>
</ul>
<p>在本书中:</p>
<p>第十章, 位置服务和地图:</p>
<ul>
<li><em>后台位置服务</em></li>
</ul>
<h1 id="网络连接维护">网络连接维护</h1>
<p>在本食谱中,我们将学习如何定期唤醒应用程序以执行网络连接检查。</p>
<h2 id="准备工作-91">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>NetCheckApp</code>。在这个示例中不需要视图控制器。</p>
<h2 id="如何操作-43">如何操作...</h2>
<ol>
<li>
<p>在<code>AppDelegate</code>类中添加以下<code>DidEnterBackground</code>重写方法:</p>
<pre><code class="language-swift">public override void DidEnterBackground (UIApplication application){
application.SetKeepAliveTimeout(610, delegate {
Console.WriteLine("App woken up for network connection maintenance!");
} );
}
</code></pre>
</li>
<li>
<p>在<code>Info.plist</code>文件中添加<code>UIBackgroundModes</code>键,并包含项目<code>voip</code>。</p>
</li>
</ol>
<h2 id="它是如何工作的-76">它是如何工作的...</h2>
<p>苹果提供了这项多任务功能,允许使用<strong>互联网协议语音</strong>(VoIP)通信的应用程序与适当的服务器进行周期性的网络连接检查。为了允许应用程序为此功能唤醒,请调用<code>UIApplication</code>类的<code>SetKeepAliveTimeout</code>方法:</p>
<pre><code class="language-swift">application.SetKeepAliveTimeout(610, delegate {
Console.WriteLine("App woken up for network connection maintenance!");
} );
</code></pre>
<p>第一个参数是应用程序将被唤醒的时间间隔(以秒为单位)。允许的最小间隔是 600 秒(10 分钟)。设置低于最小值的间隔将导致方法失败,并且应用程序将被挂起。第二个参数是在间隔即将结束时将要执行的处理程序。此处理程序只有 30 秒的时间执行。如果它需要超过 30 秒,则应用程序将被终止。</p>
<h2 id="更多内容-39">更多内容...</h2>
<p>在保持活动处理程序中可用于网络连接的对象是<code>NSInputStream, NSOutputStream</code>和<code>NSUrlRequest</code>。</p>
<h3 id="合并-uibackgroundmodes-键的项目">合并 UIBackgroundModes 键的项目</h3>
<p>应用程序可以使用<code>UIBackgroundModes</code>键的任何组合或所有可用项。然而,避免添加与预期功能不同的后台模式。在这种情况下,您的应用程序可能会被应用商店拒绝。</p>
<h2 id="相关内容-28">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>后台播放音频</em></li>
</ul>
<p>在本书中:</p>
<p>第十章, 位置服务和地图:</p>
<ul>
<li><em>后台位置服务</em></li>
</ul>
<h1 id="第十三章本地化">第十三章。本地化</h1>
<p>在本章中,我们将介绍:</p>
<ul>
<li>
<p>为不同语言创建应用程序</p>
</li>
<li>
<p>可本地化资源</p>
</li>
<li>
<p>区域格式化</p>
</li>
</ul>
<h1 id="简介-12">简介</h1>
<p>随着 iOS 平台和以应用程序商店形式出现的全球软件市场的发布,苹果公司使开发者更容易在全球范围内分发应用程序。</p>
<p>但是,全球用户甚至不会费心下载和使用以他们不理解的语言发布的应用程序。为了扩大其应用程序的用户基础,开发者必须对其进行本地化。本地化是将文本翻译成多种语言,提供针对多个区域的具体资源,从而创建面向不同文化受众的应用程序的过程。</p>
<p>在本章中,我们将讨论提供翻译文本的最佳实践,这些文本将根据每个用户的区域设置偏好显示。我们还将了解如何根据这些偏好提供资源(图像、视频)。最后,我们将使用常见的.NET 实践来格式化日期、货币和数字。</p>
<h1 id="为不同语言创建应用程序">为不同语言创建应用程序</h1>
<p>在本食谱中,我们将创建一个支持两种不同语言的应用程序。</p>
<h2 id="准备工作-92">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>MultipleLanguageApp</code>。</p>
<h2 id="如何操作-44">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加两个标签。</p>
</li>
<li>
<p>向项目添加两个文件夹。分别命名为<code>en.lproj</code>和<code>es.lproj</code>。</p>
</li>
<li>
<p>使用文本编辑器应用程序创建两个文本文件。在第一个文件中输入以下文本:</p>
<pre><code class="language-swift">// Localized output on MainController
"Have a nice day!" = "Have a nice day!";
</code></pre>
</li>
<li>
<p>将其保存为 en.lproj 文件夹中的 Localizable.strings。</p>
</li>
<li>
<p>在第二个文件中输入以下文本:</p>
<pre><code class="language-swift">// Localized output on MainController
"Have a nice day!" = "Tenga un buen día!";
</code></pre>
</li>
<li>
<p>这次将文件以相同的名称,Localizable.strings,保存在<code>es.lproj</code>文件夹中。将两个文件的<strong>构建操作</strong>设置为<strong>内容</strong>。</p>
<h3 id="注意-44">注意</h3>
<p><code>Localizable.strings</code>文件必须以<code>UTF8</code>或<code>UTF16</code>编码保存。</p>
</li>
<li>
<p>在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">public override void ViewWillAppear (bool animated){
base.ViewWillAppear (animated);
this.lblLocale.Text = string.Format("Locale: {0} - Language: {1}", NSLocale.CurrentLocale.LocaleIdentifier, NSLocale.PreferredLanguages);
string resourcePath = NSBundle.MainBundle.PathForResource( NSLocale.PreferredLanguages, "lproj");
NSBundle localeBundle = NSBundle.FromPath(resourcePath);
this.lblLocalizedOutput.Text = localeBundle.LocalizedString("Have a nice day!", "Localized output on MainController");
}
</code></pre>
</li>
<li>
<p>通过模拟器的<strong>设置</strong>应用程序,将语言设置为<strong>英语</strong>(如果尚未设置),然后运行应用程序。消息将以英语显示。尝试将模拟器的语言设置为<strong>西班牙语</strong>,然后再次运行应用程序。消息将以西班牙语显示。</p>
</li>
</ol>
<h2 id="它是如何工作的-77">它是如何工作的...</h2>
<p>为了使开发者更容易在应用程序中提供对多种语言的支持,iOS 从相应的语言文件夹中读取不同语言文本。在本应用程序中,我们支持英语和西班牙语。它们相应的文件夹分别是<code>en.lproj</code>和<code>es.lproj</code>。当我们调用<code>LocalizedString</code>方法时,它会查找并解析<code>Localizable.strings</code>文件以返回适当的文本。</p>
<p>字符串文件的 内容由一组带引号的键/值对定义,采用 C 语言风格语法,每个集合以分号结尾:</p>
<pre><code class="language-swift">// Localized output on MainController
"Have a nice day!" = "Have a nice day!";
</code></pre>
<p>如您所见,我们还可以提供注释以协助翻译文本的人的工作,即使我们自己翻译。</p>
<p><code>NSLocale.PreferredLanguages</code>静态属性返回一个包含用户首选语言标识符的字符串数组。这个数组中的第一个项目是当前选定的语言。如果选定的语言是<strong>英语</strong>,它将返回<code>en</code>;如果是<strong>西班牙语</strong>,它将返回<code>es</code>,依此类推。</p>
<p><code>NSBundle.PathForResource</code>方法返回我们传递给它的参数的应用程序包路径。我们使用这个路径来获取适当的<code>NSBundle</code>实例,根据选定的语言:</p>
<pre><code class="language-swift">string resourcePath = NSBundle.MainBundle.PathForResource( NSLocale.PreferredLanguages, "lproj");
NSBundle localeBundle = NSBundle.FromPath(resourcePath);
</code></pre>
<p>然后我们调用<code>LocalizedString</code>方法来显示适当的文本:</p>
<pre><code class="language-swift">this.lblLocalizedOutput.Text = localeBundle.LocalizedString("Have a nice day!", "Localized output on MainController");
</code></pre>
<p>第一个参数的目的是双重的。它不仅是查找以返回翻译文本的关键,而且在指定的本地化路径未找到时,它还将显示为文本。第二个参数是注释,或者我们想要提供给翻译者的任何指令。它不会显示并且基本上不会被使用。我们可以传递<code>null</code>给这个参数,并且不会发生错误。然而,始终包含注释或指令是明智的,因为它将有助于在翻译多个字符串时避免混淆。</p>
<h2 id="还有更多-46">还有更多...</h2>
<p>建议始终提供可以作为在<strong>英语</strong>中显示的回退文本的键,以防用户选定的语言不包括在我们的应用程序中。</p>
<p>然而,<code>LocalizedString</code>方法有多个重载。第二个重载接受三个参数。考虑以下示例:</p>
<pre><code class="language-swift">this.lblLocalizedOutput.Text = localeBundle.LocalizedString("Have a nice day!", "Have a nice day!", "Localizable");
</code></pre>
<p>第一个参数是查找的关键。第二个参数是回退值,以防指定的本地化路径未找到。第三个参数是包含字符串的文件的名称,不包括<code>.strings</code>扩展名。这个重载更有帮助,我们可以为我们的字符串使用不同的键,这有助于我们识别特定字符串在代码中的使用位置。例如,在这种情况下,我们可以在字符串文件中将键设置为<code>MainController.lblLocalizedOutput:</code></p>
<pre><code class="language-swift">// Localized output on MainController
"MainController.lblLocalizedOutput" = "Have a nice day!";
</code></pre>
<p>在我们的代码中使用它如下:</p>
<pre><code class="language-swift">this.lblLocalizedOutput.Text = localeBundle.LocalizedString( "MainController.lblLocalizedOutput", "Have a nice day!", "Localizable");
</code></pre>
<p>这个重载还帮助我们将字符串分成多个<code>.strings</code>文件,通过将相应的文件名作为参数#3 传递。</p>
<p>最后一个重载包含四个参数。前三个与第二个重载相同。第四个参数只是我们想要特定字符串拥有的注释。</p>
<h3 id="实际应用场景中的本地化">实际应用场景中的本地化</h3>
<p>在这个示例中,我们使用<code>PathForResource</code>方法获取当前区域设置包的实例。这是因为从<code>LocalizedString</code>方法返回的值被缓存了。在实际应用场景中,如果应用程序以特定语言下载,并且用户最有可能永远不会更改设备语言来使用它,只需调用<code>NSBundle.MainBundle.LocalizedString</code>就足够了。</p>
<h1 id="可本地化资源">可本地化资源</h1>
<p>可本地化资源是内容,例如图像、声音文件等,它针对特定区域。在本食谱中,我们将学习如何根据用户的本地化首选项加载和显示资源。</p>
<h2 id="准备工作-93">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>LocalizableResourcesApp</code>。在<code>MainController</code>的视图中添加一个标签和一个<code>UIImageView</code>。</p>
<h2 id="如何操作-45">如何操作...</h2>
<ol>
<li>
<p>在项目中添加两个用于<strong>英语</strong>和<strong>西班牙语</strong>区域的文件夹。在每个文件夹中添加一个具有相同文件名的图像。将它们的<strong>构建操作</strong>设置为<strong>内容</strong>。</p>
</li>
<li>
<p>在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">public override void ViewWillAppear (bool animated){
base.ViewWillAppear (animated);
this.lblLanguage.Text = NSLocale.PreferredLanguages;
this.imageView.Image = UIImage.FromFile(NSBundle. MainBundle.PathForResource("flag", "jpg"));
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序,在<strong>设置</strong>应用程序中选择<strong>英语</strong>语言。结果应该类似于以下截图:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_13_01.jpg"></p>
</li>
<li>
<p>现在,将模拟器的语言设置为<strong>西班牙语</strong>,并再次运行应用程序。应该显示西班牙国旗:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_13_02.jpg"></p>
<h2 id="它是如何工作的-78">它是如何工作的...</h2>
<p><code>PathForResource</code>方法会自动搜索适当的语言文件夹,并通过其参数加载指定的资源。在这个例子中,我们将方法的结果传递给<code>UIImage.FromFile</code>方法来加载图像,并将其分配给图像视图的<code>Image</code>属性。</p>
<h2 id="还有更多-47">还有更多...</h2>
<p>除了图像外,我们还可以使用<code>PathForResource</code>方法来加载视频、PDF 文件以及任何其他需要本地化的资源。</p>
<h3 id="更多关于可本地化资源的信息">更多关于可本地化资源的信息</h3>
<p>我们需要确保特定语言的文件夹中的资源存在。如果不存在,将会抛出异常。避免这种情况的一种方法是,在项目中添加一个通用的图像文件,并在每个语言文件夹中添加一个<code>Localizable.strings</code>文件,其中包含资源的路径:</p>
<pre><code class="language-swift">// US flag image
"flag_path"="en.lproj/flag.jpg";
</code></pre>
<p>为了加载适当的国旗,我们使用<code>LocalizedString</code>方法加载图像:</p>
<pre><code class="language-swift">this.Image = UIImage.FromFile(NSBundle.MainBundle.LocalizedString( "flag_path", "path/to/universal/image.jpg", "Localizable");
</code></pre>
<p>这样,如果找不到相应的语言文件夹,将加载图像<code>image.jpg</code>。</p>
<h2 id="相关内容-29">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>为不同语言创建应用程序</em></li>
</ul>
<h1 id="区域格式化">区域格式化</h1>
<p>区域格式化是指根据世界各地的不同区域显示各种信息,如货币、日期和时间等。在本食谱中,我们将讨论如何根据用户的区域格式设置显示格式化的数字和日期。</p>
<h2 id="准备工作-94">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>RegionalFormattingApp</code>。</p>
<h2 id="如何操作-46">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加五个标签。在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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);
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序,将区域格式设置为<strong>美国和西班牙 | 西班牙</strong>,在<strong>设置 | 通用 | 国际 | 区域格式</strong>下。具有两种不同区域格式的输出将类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_13_03.jpg"><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_13_04.jpg"></p>
<h2 id="它是如何工作的-79">它是如何工作的...</h2>
<p>要格式化日期、货币和数字,我们使用标准的 .NET 代码。对于日期和时间,<code>DateTime.ToLongDateString</code> 和 <code>DateTime.ToLongTimeString</code> 方法分别根据区域设置返回值。</p>
<p>对于货币和数字,我们使用 C# 数值字符串:</p>
<pre><code class="language-swift">this.lblCurrency.Text = string.Format("Currency: {0:c}", 250);
this.lblNumber.Text = string.Format("Number: {0:n}", 1350);
</code></pre>
<h2 id="还有更多-48">还有更多...</h2>
<p><code>System.Globalization</code> 命名空间在 MonoTouch 中受支持。要显示当前区域设置,请考虑以下代码行:</p>
<pre><code class="language-swift">Console.WriteLine(CultureInfo.CurrentCulture.Name);
</code></pre>
<p>注意,此代码与 <code>NSLocale.CurrentLocale.LocaleIdentifier</code> 之间有一个区别。前者使用破折号 (-),而后者在区域名称中使用下划线 (_)。</p>
<h1 id="第十四章部署">第十四章:部署</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>创建配置文件</p>
</li>
<li>
<p>创建一个临时的分发包</p>
</li>
<li>
<p>为 App Store 准备应用</p>
</li>
<li>
<p>提交到 App Store</p>
</li>
</ul>
<h1 id="简介-13">简介</h1>
<p>将应用程序部署到设备或 App Store 的过程被认为相当复杂。当然,这也是为了开发者的利益,因为需要各种证书和配置文件才能将应用程序包从开发 Mac 传输到设备。</p>
<p>在本章中,我们将详细介绍在开发计算机上准备和安装适当证书的所有必要步骤。我们还将学习如何创建配置文件,这将允许我们将应用程序部署到设备上,无论是我们的设备还是发送给测试人员安装到他们的设备上。</p>
<p>最后,我们将了解如何为 App Store 提交准备应用程序以及最终发布到 App Store 的流程。</p>
<h1 id="创建配置文件">创建配置文件</h1>
<p>在本食谱中,我们将逐步指导您创建和安装必要的证书和配置文件,这些证书和配置文件对于将应用程序部署到设备上是必需的。</p>
<h2 id="如何操作-47">如何操作...</h2>
<p>以下步骤将指导您创建应用程序的开发者证书和适当的配置文件。</p>
<p>我们将从开发者证书开始。</p>
<ol>
<li>
<p>登录到 iOS 开发者网站:<code>developer.apple.com/ios</code>。</p>
</li>
<li>
<p>前往 iOS 配置文件门户。</p>
</li>
<li>
<p>从左侧菜单中选择 <strong>Certificates</strong>。</p>
</li>
<li>
<p>如果这是您第一次在 Mac 上使用开发者证书,请下载并安装 <strong>WWDR 中间证书</strong>。您可以在 <strong>Development</strong> 选项卡下找到链接。</p>
</li>
<li>
<p>在 <strong>How To</strong> 选项卡下,您将找到创建证书签名请求的说明,这是颁发您的开发者证书和下载安装它的必要条件。</p>
</li>
<li>
<p>如果开发者证书已正确安装,您将在 MonoDevelop 项目的选项中选中它,在 <strong>Identity</strong> 下拉列表中的 <strong>iPhone Bundle Signing</strong> 节点下:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_01.jpg"></p>
<ul>
<li>您可以在 <strong>iOS Provisioning Portal</strong> 的 <strong>Certificates</strong> 选项下的 <strong>Development</strong> 选项卡中查看您的开发者证书。</li>
</ul>
<p>现在我们已经颁发并安装了我们的开发者证书,我们需要注册我们将用于调试的设备。</p>
<ol>
<li>
<p>点击左侧菜单中的 <strong>Devices</strong> 选项。</p>
</li>
<li>
<p>在 <strong>Manage</strong> 选项卡下,点击 <strong>Add Devices</strong> 按钮。</p>
</li>
<li>
<p>在 <strong>Device Name</strong> 字段中,输入一个可以识别特定设备的名称(例如,<code>我的 iPhone</code>)。</p>
</li>
<li>
<p>在<strong>设备 ID</strong>字段中,输入设备的<strong>唯一设备标识符</strong>(UDID)。您可以通过将设备连接到您的 Mac 并打开<strong>iTunes</strong>来找到设备的 UDID。在设备的<strong>摘要</strong>选项卡下,点击序列号将切换到 UDID。按<em>Command</em> + <em>C</em>将 UDID 复制到剪贴板。</p>
</li>
<li>
<p>点击加号(+)按钮,然后<strong>提交</strong>。为要添加的所有设备重复所有步骤。</p>
</li>
</ol>
<p>接下来,我们需要一个<strong>App ID</strong>。</p>
<ol>
<li>
<p>点击左侧菜单上的<strong>App IDs</strong>选项。</p>
</li>
<li>
<p>在<strong>管理</strong>选项卡下,为您的应用程序输入描述/名称。不要使用特殊字符和空格。</p>
</li>
<li>
<p>将<strong>Bundle Seed ID (App ID Prefix)</strong>选项保留为<strong>生成新</strong>。</p>
</li>
<li>
<p>输入<strong>包标识符</strong>。包标识符的最佳实践是遵循字段上方给出的示例和建议。<strong>包标识符</strong>很重要,因为在部署过程的至少一个步骤中您将需要它。</p>
</li>
<li>
<p>点击<strong>提交</strong>按钮以创建 App ID。</p>
</li>
</ol>
<p>接下来,是配置文件的时间。</p>
<ol>
<li>
<p>点击左侧菜单上的<strong>配置</strong>选项。</p>
</li>
<li>
<p>在<strong>开发</strong>选项卡下,点击<strong>新建配置文件</strong>按钮。</p>
</li>
<li>
<p>输入配置文件名称。您可以在此字段中输入任何字符。</p>
</li>
<li>
<p>在<strong>证书</strong>选项中,选择配置文件的开发者证书。如果您已成功创建您的开发者证书,您的姓名(或 iOS 开发者账户所属者的姓名)将显示在旁边的复选框旁边。勾选复选框。</p>
</li>
<li>
<p>在<strong>App ID</strong>选项中,选择您正在创建的配置文件的<strong>App ID</strong>。</p>
</li>
<li>
<p>在<strong>设备</strong>选项中,勾选将包含在配置文件中的设备(们)。您的应用程序只能安装在此处选择的设备上。</p>
</li>
<li>
<p>点击<strong>提交</strong>按钮以完成配置文件的创建。</p>
</li>
<li>
<p>如果列表中配置文件的状态为<strong>挂起</strong>,只需刷新页面。</p>
</li>
<li>
<p>点击您配置文件所在行的<strong>下载</strong>按钮以下载它。将下载一个扩展名为 .mobileprovision 的文件。</p>
</li>
<li>
<p>将您的设备连接到您的 Mac。如果已打开,请关闭<strong>iTunes</strong>。</p>
</li>
<li>
<p>双击您在<em>步骤 9</em>中下载的<code>.mobileprovision</code>文件。Xcode 将打开并在您的设备上安装配置文件。您可以通过在 Xcode 的<strong>组织者</strong>窗口中查看配置文件状态来检查此操作。要显示<strong>组织者</strong>窗口,按<em>Shift + Command + 2</em>,或从菜单中选择<strong>窗口 | 组织者</strong>。您可以通过在左侧选择<strong>配置</strong>选项来查看每个设备的配置文件:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_02.jpg"></p>
<p>带有红色<strong>X</strong>标记的配置文件表示它们已过期。</p>
<h2 id="它是如何工作的-80">它是如何工作的...</h2>
<p>本食谱中描述的过程将允许您在连接到您的 Mac 的设备上部署和调试您的应用程序。它不会允许您将应用程序分发给测试人员或 App Store。</p>
<p>开发者证书是允许编译将部署到设备上的应用的证书。它仅用于开发,并且一个开发者证书对应一个 iOS 开发者计划注册。在创建并安装到开发机器上之后,您无法再发行新的证书。然而,现有的开发者证书可以被转移到其他 Mac 上。</p>
<p>每个配置文件包含有关它可以安装到哪些设备的信息。注册到 iOS 开发者计划的 Apple 开发者可以添加最多 100 个设备并将它们包含在配置文件中。</p>
<p>App ID 是您应用的标识符。为您的每个应用创建一个 App ID。</p>
<p>配置文件是允许您的应用在设备上部署的电子签名。每个配置文件对应一个应用,并包含所有允许应用在包含在内的设备上执行以及 App ID 信息的适当权限。它也是区分开发或分发应用的关键。配置文件会附带一个过期时间。在撰写本文时,大约是一年。</p>
<h2 id="还有更多-49">还有更多...</h2>
<p>要在设备上编译和调试应用,请在 MonoDevelop 的<strong>项目选项</strong>下的<strong>iPhone 包签名</strong>节点中选择开发者证书和配置文件:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_03.jpg"></p>
<p>这必须为每个构建配置(调试、发布等)执行。</p>
<p>在<strong>iPhone 应用</strong>节点下,为您的应用设置<strong>显示名称、包标识符</strong>和<strong>包版本</strong>字段。如果您留空,MonoDevelop 将设置它们的默认值。具体来说,<strong>包标识符</strong>将被设置为包含在 App ID 中的那个。然而,如果您将<strong>包标识符</strong>设置为与 App ID 中声明的内容不同的内容,编译时将发生错误。</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_04.jpg"></p>
<h3 id="配置文件过期">配置文件过期</h3>
<p>当配置文件过期时,应用将无法在设备上工作。您可以续订现有的配置文件或创建一个新的配置文件并将其重新安装到设备上。</p>
<h2 id="参见-61">参见</h2>
<p>在本章中:</p>
<ul>
<li>创建临时分发包</li>
</ul>
<p>在本书中:</p>
<p>第一章,</p>
<ul>
<li><em>编译</em></li>
</ul>
<h1 id="创建临时分发包">创建临时分发包</h1>
<p>在这个菜谱中,我们将学习如何创建我们的应用包,这样我们就可以将其发送给测试人员,让他们在自己的设备上进行测试。</p>
<h2 id="准备工作-95">准备工作</h2>
<p>要创建一个临时的分发包,请确保您已经在 iOS 配置文件门户为您的应用创建了一个 App ID。</p>
<h2 id="如何操作-48">如何操作...</h2>
<p>创建临时配置文件的过程与创建开发分发配置文件的过程类似。以下步骤将指导您完成这个过程。</p>
<ol>
<li>
<p><strong>分发证书:</strong>为了将应用程序分发到未连接到您的 Mac 的各种设备,以及提交到应用商店,您需要一个分发证书进行安装。遵循之前任务中描述的创建开发者证书的相同步骤。不过,这次您需要在<strong>iOS 配置文件门户</strong>的<strong>证书</strong>菜单选项下的<strong>分发</strong>选项卡下进行操作。</p>
</li>
<li>
<p><strong>临时分发配置文件:</strong></p>
<ul>
<li>
<p>在<strong>iOS 配置文件门户</strong>中,转到<strong>配置 | 分发</strong>。</p>
</li>
<li>
<p>在<strong>分发方法</strong>字段中,选择<strong>临时</strong>。</p>
</li>
<li>
<p>为配置文件输入一个<strong>配置文件名称</strong>。为了将来参考,最好在名称中添加单词<code>AdHoc</code>,例如,<code>MyAppAdHoc</code>。</p>
</li>
<li>
<p><strong>分发证书</strong>将自动选中。</p>
</li>
<li>
<p>选择<strong>App ID</strong>。</p>
</li>
<li>
<p>选择配置文件中的设备。</p>
</li>
<li>
<p>点击<strong>提交</strong>以创建配置文件。</p>
</li>
<li>
<p>从显示的列表中下载配置文件。再次点击,你将得到一个 .mobileprovision 文件。双击它以允许 Xcode 安装它。此时不需要连接设备。不要删除文件;我们稍后会用到它。</p>
</li>
</ul>
</li>
<li>
<p><strong>创建临时构建:</strong></p>
<ul>
<li>
<p>在 MonoDevelop 中加载你的项目后,转到<strong>项目 | 解决方案选项</strong>。</p>
</li>
<li>
<p>在<strong>构建 | 配置</strong>下,通过点击<strong>添加</strong>创建一个新的配置。</p>
</li>
<li>
<p>为配置输入一个名称。这里使用的名称是 <code>MyAppDistribution</code>。</p>
</li>
<li>
<p>在<strong>平台</strong>选项中选择<strong>iPhone</strong>。以下截图显示了<strong>新配置</strong>对话框应该如何显示:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_05.jpg"></p>
</li>
<li>
<p>确保已勾选“为所有解决方案项创建配置”选项。</p>
</li>
<li>
<p>点击<strong>确定</strong>按钮。</p>
</li>
<li>
<p>将一个新的<strong>属性列表</strong>文件添加到项目中,并将其命名为<code>Entitlements</code>。<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_06.jpg"></p>
</li>
<li>
<p>MonoDevelop 将自动使用<strong>属性列表编辑器</strong>加载 <code>Entitlements.plist</code> 文件。添加键 <code>get-task-allow</code>,并将其<strong>类型</strong>设置为<strong>布尔值</strong>。将其值设置为<strong>否</strong>。</p>
</li>
<li>
<p>在项目选项的<strong>iPhone Bundle Signing</strong>节点下,选择之前创建的配置(MyAppDistribution)。将<strong>自定义权限</strong>字段设置为之前步骤中创建的 <code>Entitlements.plist</code> 文件。它可以通过点击字段旁边的<strong>浏览 ()</strong>按钮轻松找到。</p>
</li>
<li>
<p>在同一窗口中设置分发配置文件和适用于临时分发的适当配置文件。</p>
</li>
<li>
<p>将项目的当前配置设置为之前创建的分发配置(MyAppDistribution)。</p>
</li>
<li>
<p>构建项目。</p>
<p>应用程序现在已准备好进行临时分发!</p>
</li>
</ul>
</li>
<li>
<p><strong>分发给测试人员:</strong></p>
<ul>
<li>
<p>打开<strong>Finder</strong>,导航到项目中的 <code>bin</code> 文件夹。</p>
</li>
<li>
<p>打开<code>iPhone/MyAppDistribution</code>文件夹。通过右键单击并选择<strong>Compress "MyApp"</strong>(或应用的实际名称)使用 OS X 的默认压缩工具压缩应用程序包。应用程序包是一个显示以下图标的文件夹:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_07.jpg"></p>
</li>
<li>
<p>将压缩的应用程序包以及<code>.mobileprovision</code>文件发送给您的测试员。</p>
</li>
<li>
<p>为了安装应用,您的测试员需要从压缩归档中提取应用程序包。</p>
</li>
<li>
<p>提取后,将文件拖放到<strong>iTunes</strong>的<strong>Apps</strong>部分,并附带<code>.mobileprovision</code>文件,即可将其导入到<strong>iTunes Library</strong>。请注意,应用将在<strong>iTunes</strong>中显示默认图标。如果你没有为应用设置任何图标,这对于临时分发来说是正常的。</p>
</li>
<li>
<p>在<strong>iTunes</strong>中同步设备。</p>
</li>
<li>
<p>如果测试员在 Windows 机器上使用<strong>iTunes</strong>,请指示他们不要使用默认的解压缩工具,而应使用第三方应用程序。</p>
</li>
</ul>
</li>
</ol>
<h2 id="它是如何工作的-81">它是如何工作的...</h2>
<p>为了分发应用,我们需要一个分发证书。就像开发者证书一样,分发证书一旦创建,就可以在需要时转移到另一台 Mac 上。</p>
<p>临时分发配置文件的创建过程与创建开发配置文件的过程相同。唯一的区别是我们有选择分发类型的选项,可以是<em>App Store</em>或<em>Ad Hoc</em>。</p>
<p>我们创建的配置不是必需的,但它有助于我们更好地组织构建过程。它还使我们免于为每个构建设置不同的配置文件和设置。</p>
<p><code>Entitlements.plist</code>文件以及<code>get-task-allow</code>键用于防止应用在启动时尝试寻找调试器进行连接。</p>
<h2 id="还有更多-50">还有更多...</h2>
<p>MonoDevelop 提供了一个直接创建和压缩应用程序包的选项。在菜单中选择<strong>项目 | 压缩应用程序包...</strong>,然后选择输出位置和文件名,它将编译并压缩应用程序包。</p>
<h3 id="使用-itunes-同步临时应用程序包">使用 iTunes 同步临时应用程序包</h3>
<p>不同用户在他们的 iTunes 应用中设置了不同的设置。如果用户同步设备但无法在设备上找到应用,请确保在<strong>iTunes</strong>中选中设备的<strong>Apps</strong>标签页下的应用以进行同步。</p>
<h2 id="相关内容-30">相关内容</h2>
<p>在本章中:</p>
<ul>
<li><em>创建配置文件</em></li>
</ul>
<h1 id="准备应用提交到-app-store">准备应用提交到 App Store</h1>
<p>在这个菜谱中,我们将讨论为准备应用提交到 App Store 所需的重要步骤。</p>
<h2 id="准备工作-96">准备工作</h2>
<p>按照前一个菜谱中的步骤为您的应用创建 App Store 分发配置文件。</p>
<h2 id="如何操作-49">如何操作...</h2>
<p>准备应用提交到 App Store 的一个非常重要的步骤是确定应包含在您应用中的图像。</p>
<ul>
<li>
<p><strong>应用图标</strong>:这是将在用户设备上代表你的应用程序的图标。对于 iPhone 和 iPod Touch,为低分辨率屏幕准备 <code>57x57</code> 像素的图标,并为高分辨率屏幕(例如,iPhone 4 及更新版本)准备 <code>114x114</code> 像素的图标。对于 iPad,图标大小应为 <code>72x72</code> 像素。</p>
<p>将图标文件保存为 PNG 格式。你可以随意命名它们,但保持一致的命名方案是一个好习惯,例如,<code>Icon-57.png, Icon-114.png</code> 等等。</p>
<p>将图像文件添加到项目中。打开项目选项对话框,转到 <strong>iPhone Application</strong> 节点下的 <strong>Summary</strong> 选项卡中的 <strong>iPhone Icons</strong> 部分,如图所示。</p>
</li>
</ul>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_08.jpg"></p>
<ul>
<li>
<p>点击适当的按钮以找到图标文件并分配它。请注意,将打开的图像选择对话框仅查找项目中包含的文件。因此,请确保首先将你的图像添加到项目中。无需将它们的 <strong>Build Action</strong> 设置为 <strong>Content</strong>。点击 <strong>OK</strong> 按钮以保存对项目的更改。</p>
</li>
<li>
<p><strong>启动图像</strong>:启动图像是应用程序启动时首先显示的内容。为 iPhone 和 iPod Touch 应用程序准备至少两个维度的启动图像:<code>320x480</code> 像素用于低版本,<code>640x960</code> 像素用于高版本。分别将文件命名为 <code>Default.png</code> 和 <code>Default@2x.png</code>,并将它们添加到项目中。将它们的 <strong>Build Action</strong> 设置为 <strong>Content</strong>。</p>
<p>如果你的应用程序是通用型且因此可以被 iPad 用户下载,或者它是一个仅限 iPad 的应用程序,那么你应该为应用程序支持的每个方向提供启动图像。对于纵向版本,大小应为 768x1004 像素,对于横向版本,大小应为 1024x748 像素。对于支持纵向和横向右方向的应用程序,文件名分别应为 Default-Portrait.png 和 Default-LandscapeRight.png。</p>
</li>
<li>
<p><strong>最终设置</strong>:最后但同样重要的是,在 <strong>project</strong> 选项中的 <strong>iPhone Application</strong> 节点的 <strong>Summary</strong> 选项卡中填写适当的应用程序信息,如本章开头所述的 <em>Creating Profiles</em> 菜单中所述。构建项目并压缩应用程序包。你的应用程序已准备好提交!</p>
</li>
</ul>
<h2 id="它是如何工作的-82">它是如何工作的...</h2>
<p>应用程序图标非常重要。这是用户将在设备屏幕上看到并点击以启动您的应用程序的内容。尽管所有应用程序图标都显示为具有圆角和光泽效果的按钮,但您不应在图标中包含这些图形特征。这些图形特征在提交到应用商店时自动渲染。图标应该是完美的正方形。此外,始终为图标提供背景。不要使用透明度,因为图标上的任何透明度都将显示为黑色,可能会破坏您预期的图标外观。</p>
<p>当应用程序启动时,首先显示的是启动图像。如果启动时屏幕变黑,这意味着没有启动图像。根据苹果的<em>iOS 人机界面指南</em>(<code>developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/MobileHIG/Introduction/Introduction.html</code>),这个图像应该是应用程序完成启动过程并准备好接受输入时的第一个屏幕。它应该只包含第一个屏幕的静态内容,而不是可能改变的内容,例如本地化文本。例如,以下截图展示了 g-force 测量应用 GBox 的启动图像。</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_09.jpg"></p>
<p>以下截图显示了应用程序完全加载后的样子。</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_10.jpg"></p>
<h2 id="更多内容-40">更多内容...</h2>
<p>启动图像的存在是为了在应用程序加载时给用户一种响应感,避免空白屏幕。尽管<em>iOS 人机界面指南</em>正如其名所示,是一份指南,但遵循它们是一个好的实践。苹果建议避免在启动画面、"关于"信息和品牌中使用启动图像。</p>
<h3 id="图标上的灯光效果">图标上的灯光效果</h3>
<p>如果我们想要为图标提供自己的灯光效果,或者甚至不允许显示默认的 iOS 效果,我们可以在<code>Info.plist</code>文件中添加<code>UIPrerenderedIcon</code>键,并将其<strong>类型</strong>设置为<strong>布尔值</strong>并启用它。此设置防止在设备主屏幕上显示应用程序图标时创建光泽效果。</p>
<h2 id="参见-62">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>创建配置文件</em></li>
</ul>
<h1 id="提交到-app-store">提交到 App Store</h1>
<p>在这个菜谱中,我们将介绍提交应用程序到 App Store 所需的步骤。</p>
<h2 id="准备工作-97">准备工作</h2>
<p>对于这个任务,您需要准备好您的压缩分发应用程序包。</p>
<h2 id="如何操作-50">如何操作...</h2>
<p>按照以下步骤提交您的应用程序到 App Store。</p>
<ol>
<li>
<p><strong>截图</strong>:准备显示您应用程序各个方面的截图。对于 iPhone/iPod Touch 应用程序,肖像模式的尺寸应为<code>320x480</code>,横屏模式的尺寸应为<code>480x320</code>。对于 iPad 应用程序,截图的尺寸应为肖像模式的<code>768x1024</code>和横屏模式的<code>1024x768</code>。如果应用程序没有隐藏状态栏,最好将其包含在截图内。对于每个应用程序,我们可以在 App Store 上最多拥有五张截图。</p>
</li>
<li>
<p><strong>App Store 图标</strong>:准备代表应用在 App Store 上的图标。其尺寸必须是<code>512x512</code>像素,并且必须与应用图标相同。</p>
</li>
<li>
<p><strong>描述和关键词</strong>:准备描述您的应用程序的文本。尽量包括最重要的功能。记住,描述是用户在下载应用程序之前会阅读的内容,所以越吸引人越好。</p>
<p>准备有助于您的应用程序在搜索结果中排名靠前的关键词。</p>
<p>应用描述和关键词都是必需的。</p>
</li>
<li>
<p><strong>登录到 iTunes Connect</strong>:iTunes Connect 是管理提交应用程序(以及其他 App Store 相关内容)的开发者门户。使用您的 Apple 开发者 ID 登录到 iTunes Connect(<code>itunesconnect.apple.com</code> )。点击链接<strong>管理您的应用程序</strong>。然后,点击左上角的<strong>添加新应用</strong>按钮。按照步骤在门户上完成应用程序准备。完成后,请确保应用程序状态为<strong>等待上传</strong>。</p>
</li>
<li>
<p><strong>上传</strong>:在门户上创建新应用程序后,您可以使用<strong>应用程序加载器</strong>上传压缩的应用程序包。它默认安装在 Xcode 中,可以在路径<code>/Developer/Application/Utilities</code>下找到,或者通过<strong>Spotlight</strong>搜索。</p>
<p>当您启动<strong>应用程序加载器</strong>时,它将要求您使用您的<strong>Apple 开发者 ID</strong>登录。登录后,您将看到一个以下窗口:</p>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_11.jpg"></p>
</li>
<li>
<p>点击<strong>提交您的应用程序</strong>按钮,它将连接到<strong>iTunes Connect</strong>,找到您处于<strong>等待上传</strong>状态的应用程序,并将它们加载到列表框中:<img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_12.jpg"></p>
<ul>
<li>然后,您将看到您应用程序的摘要视图:</li>
</ul>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468_14_13.jpg"></p>
</li>
<li>
<p>点击<strong>选择...</strong>按钮,将弹出一个对话框,允许您选择压缩的应用程序包。选择后,继续上传。一切就绪!如果所有步骤都正确完成,应用程序将被上传,并将在 App Store 上等待审核发布。</p>
</li>
</ol>
<h2 id="它是如何工作的-83">它是如何工作的...</h2>
<p>应用截图和 App Store 图标非常重要。它们可以是<code>JPG</code>、<code>TIF</code>或<code>PNG</code>格式,使用 RGB 颜色,分辨率至少为 72 DPI。</p>
<p>但图片只有在用户已经在 App Store 中查看你的应用时才重要。关键词和描述是允许你的应用在搜索结果中排名更高的参数,并让用户决定应用是否值得下载。特别是关于关键词,要明智地选择。不要包含尽可能多的关键词;反映应用关键方面的关键词越少越好。</p>
<p>iTunes Connect 是管理应用、审查财务数据、应用下载的开发者门户,包括开发者需要签署的合同和协议。请确保你阅读并接受这些合同,否则你将无法继续应用准备流程。在这个过程中,你需要提供之前描述的应用必要信息,如果它是付费应用,还需要提供价格范围、它将在哪些国家可用,以及如果不想自动发布,还需要提供发布日期。</p>
<p>当一切设置正确且应用状态为<strong>等待上传</strong>时,你可以运行<strong>应用加载器</strong>来上传它。iOS 和 iOS SDK 版本每次更新时,各种组件或流程都会发生变化。请确保你的 iOS SDK 版本是最新的。</p>
<h2 id="还有更多-51">还有更多...</h2>
<p>在应用准备过程中某个阶段,你将需要输入一个<strong>库存单位</strong>(SKU)编号。这个编号是每个产品或服务的唯一标识符。它可以是你想要的任何数字,但请保持一个特定的模式来跟踪标识符,例如,当你开发额外的应用时。</p>
<h2 id="参见-63">参见</h2>
<p>在本章中:</p>
<ul>
<li><em>为 App Store 准备应用</em></li>
</ul>
<h1 id="第十五章ios-5-特性">第十五章:iOS 5 特性</h1>
<p>在本章中,我们将涵盖:</p>
<ul>
<li>
<p>重新生成翻页效果</p>
</li>
<li>
<p>视图样式</p>
</li>
<li>
<p>Twitter 集成</p>
</li>
<li>
<p>与分割键盘一起工作</p>
</li>
</ul>
<h1 id="简介-14">简介</h1>
<p>iOS 的第五个主要版本带来了 200 多项新特性。在本章中,我们将只关注其中的一些,这些特性大多与增强用户体验有关。</p>
<p>具体来说,我们将创建一个项目,显示内容分为页面,用户可以使用新引入的 UIPageViewController 类像在普通书籍中一样导航这些页面。</p>
<p>我们将讨论 UIAppearance 类,它允许我们以更灵活和简单的方式对我们的应用程序中的控件进行样式设计。在今天的设备中,社交分享也不可或缺,因此我们将创建一个项目,允许用户使用 Twitter,使用 TWTweetComposeViewController。</p>
<p>在本章的最后一个小节中,我们将使用 iPad 的新分割键盘功能,学习如何根据屏幕上虚拟键盘的位置调整内容。</p>
<h1 id="重新生成翻页效果">重新生成翻页效果</h1>
<p>在这个菜谱中,我们将创建一个应用,使用<code>UIPageViewController</code>类来显示类似书籍的内容。</p>
<h2 id="准备工作-98">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的 iPhone 项目,并将其命名为<code>BookApp</code>。除了<code>MainController</code>外,再添加另一个控制器,并将其命名为<code>Page</code>。根据您的喜好配置<code>Page</code>控制器的外观。在这个菜谱的源代码中,它包含一个<code>UIImageView</code>和一个<code>UILabel</code>。</p>
<h2 id="如何操作-51">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>类中输入以下代码:</p>
<pre><code class="language-swift">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);
}
}
</code></pre>
</li>
<li>
<p>在<code>Page</code>类中添加一个属性,并修改其构造函数,如下面的代码所示:</p>
<pre><code class="language-swift">public Page (int pageIndex) : base ("Page", null){
this.PageIndex = pageIndex;
}
public int PageIndex{
get;
private set;
}
</code></pre>
</li>
<li>
<p>最后,在<code>ViewDidLoad</code>方法中配置将在<code>Page</code>中显示的内容:</p>
<pre><code class="language-swift">this.imageView.Image = UIImage.FromFile(string.Format( "images/{0}.jpg", this.PageIndex + 1));
this.lblPageNum.Text = string.Format("Page {0}", this.PageIndex + 1);
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>在模拟器的屏幕区域上点击并拖动以更改页面。结果应类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_15_01.jpg"></p>
<h2 id="它是如何工作的-84">它是如何工作的...</h2>
<p>随着 iOS 5 的推出,<code>UIPageViewController</code>类成为许多开发者的期望组件。它允许我们通过类似真实书籍的效果导航内容,就像在苹果的 iBooks 应用中一样。</p>
<p>我们使用以下行进行初始化:</p>
<pre><code class="language-swift">this.pageViewController = new UIPageViewController( UIPageViewControllerTransitionStyle.PageCurl, UIPageViewControllerNavigationOrientation.Horizontal, UIPageViewControllerSpineLocation.Min);
</code></pre>
<p>构造函数的第一个参数确定效果的类型。目前唯一可用的值是<code>PageCurl</code>。第二个参数确定效果的方向。<code>Horizontal</code>是类似于书籍的效果的值,而<code>Vertical</code>是类似于笔记本的效果的值,其中页面在顶部绑定。第三个参数确定书籍绑定的位置。<code>Min</code>表示绑定在屏幕的一侧;在这种情况下,在左侧。</p>
<p>初始化页面控制器后,我们需要通过调用其<code>SetViewControllers</code>方法来设置其第一个页面:</p>
<pre><code class="language-swift">this.pageViewController.SetViewControllers(new UIViewController[] { firstPage }, UIPageViewControllerNavigationDirection.Forward, false, s => { });
</code></pre>
<p>其第一个参数是一个<code>UIViewController</code>对象的数组。我们可以为这个参数设置一个或两个控制器,具体取决于设备方向。例如,如果应用程序支持横屏方向,我们可能希望同时显示两个页面。第二个参数基本上决定了包含页面的导航方向。<code>Forward</code>表示如果我们从右向左在屏幕上滑动,将加载下一页,而<code>Reverse</code>表示对于相同的滑动,将加载上一页。最后一个参数是<code>UICompletionHandler</code>类型,代表在控制器被添加后要执行的处理器。在这个例子中,我们不需要它,所以我们只传递一个空的 lambda。</p>
<p>接下来,我们需要为我们的“书籍”其余页面提供数据源。MonoTouch 再次为我们简化了事情,提供了两个非常有用的属性供我们使用:<code>GetNextViewController</code>和<code>GetPreviousViewController</code>。这两个属性仅仅代表了如果我们要为页面控制器创建一个<code>Delegate</code>对象时需要重写的回调方法。除了它们的名称外,这两个方法的签名是相同的:</p>
<pre><code class="language-swift">UIViewController GetNextViewController(UIPageViewController pageController, UIViewController referenceViewController);
UIViewController GetPreviousViewController(UIPageViewController pageController, UIViewController referenceViewController);
</code></pre>
<p>第一个参数给出了页面控制器,而第二个参数给出了在调用此方法时屏幕上当前显示的控制器。</p>
<p>在这些方法的实现中,我们只需返回应该加载的下一个控制器,或者当前控制器之前的一个。如果我们不希望激活效果,我们只需返回<code>null</code>。</p>
<p>最后但同样重要的是,我们设置了页面控制器的视图大小,并将其添加到父视图中,以便它将被显示:</p>
<pre><code class="language-swift">this.pageViewController.View.Frame = this.View.Bounds;
this.View.AddSubview(this.pageViewController.View);
</code></pre>
<h2 id="还有更多-52">还有更多...</h2>
<p>如果我们希望我们的应用程序支持横屏方向,我们首先必须在<code>MainController</code>类中实现<code>ShouldAutoRotateToInterfaceOrientation</code>方法,并从它返回我们希望支持的横屏方向。其次,我们必须为<code>UIPageViewController</code>类的<code>SetViewControllers</code>方法提供两个视图控制器。</p>
<h3 id="双面页面">双面页面</h3>
<p>如您在菜谱的截图中所注意到的,当我们翻页时,其内容在页面的背面以相反的方式显示,就像我们通过真实书籍的一页看过去一样。我们有选项通过将<code>UIPageViewController.DoubleSided</code>属性设置为<code>true</code>来创建双面页面。</p>
<h1 id="视图样式">视图样式</h1>
<p>在这个菜谱中,我们将发现如何轻松地在我们应用程序中设置按钮样式。</p>
<h2 id="准备工作-99">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>StyleButtonsApp</code>。添加<code>MainController</code>和另一个名为<code>ModalController</code>的控制器。</p>
<h2 id="如何做到这一点-18">如何做到这一点...</h2>
<ol>
<li>
<p>在每个控制器上添加一个按钮。在<code>MainController</code>类中,实现<code>ViewDidLoad</code>方法,代码如下:</p>
<pre><code class="language-swift">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);
} ;
}
</code></pre>
</li>
<li>
<p>实现<code>ModalController</code>类的<code>ViewDidLoad</code>方法,以便在按钮被点击时关闭它:</p>
<pre><code class="language-swift">this.buttonDismiss.TouchUpInside += delegate(object sender, EventArgs e) {
this.DismissModalViewControllerAnimated(true);
} ;
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击<strong>显示</strong>按钮以显示模态控制器。注意,<code>MainController</code>和<code>ModalController</code>的按钮具有相同的背景颜色和文字颜色。</p>
</li>
</ol>
<h2 id="它是如何工作的-85">它是如何工作的...</h2>
<p>为了使我们的应用程序中的所有按钮看起来相同,我们使用<code>UIButton</code>类的<code>Appearance</code>静态属性。此属性返回继承自<code>UIAppearance</code>类的对象,这是将 Objective-C 的<code>UIAppearance</code>协议反映到 MonoTouch 中的类。</p>
<p>这样,我们为所有支持它的视图都有一个<code>Appearance</code>静态属性,根据我们想要样式的视图进行强类型化。对于<code>UIButton</code>,<code>Appearance</code>属性返回一个<code>UIButtonAppearance</code>对象。在设置好这个对象中的值后,应用程序中所有<code>UIButton</code>实例将共享相同的样式。</p>
<p>因此,在这个例子中,如高亮代码所示,我们设置了我们希望所有按钮都具有的背景颜色和文字颜色,运行时将为我们处理其余部分。</p>
<h2 id="还有更多-53">还有更多...</h2>
<p>在我们的应用程序中全局样式化控件是一个非常不错的功能,但有人可能会同意它有点限制。如果我们只想样式化特定的<code>UIButtons</code>而不是所有按钮怎么办?考虑以下代码:</p>
<pre><code class="language-swift">UIButton.UIButtonAppearance buttonStyle = UIButton.AppearanceWhenContainedIn(typeof(ModalController));
buttonStyle.BackgroundColor = UIColor.Red;
</code></pre>
<p><code>AppearanceWhenContainedIn</code>方法返回相应的<code>UIAppearance</code>对象,在这种情况下是<code>UIButtonAppearance</code>,并接受一个类型为<code>System.Type</code>的可变数量的参数(params <code>Type[] containers)</code>)。此代码将仅样式化包含在<code>ModalController</code>对象中的<code>UIButton</code>实例。</p>
<p>尽管该方法参数的数量是可变的,但我们传递的<code>Type</code>对象序列决定了其行为。例如,以下调用将只在我们将<code>ModalController</code>包含在<code>MainController</code>中时,将我们设置的样式应用到<code>ModalController</code>中包含的<code>UIButton</code>实例。</p>
<pre><code class="language-swift">UIButton.AppearanceWhenContainedIn(typeof(MainController), typeof(ModalController));
</code></pre>
<h3 id="特定属性">特定属性</h3>
<p>继承自<code>UIView</code>的每个类都继承自<code>UIAppearance</code>类。然而,并非每个类的所有属性都支持它。例如,通过<code>UIButtonAppearance</code>对象,我们可以设置应用程序中每个<code>UIButton</code>的背景颜色,但不能设置标题。</p>
<h1 id="twitter-集成">Twitter 集成</h1>
<p>在这个菜谱中,我们将创建一个实现 Twitter 分享的应用程序,允许用户发送推文。</p>
<h2 id="准备工作-100">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的项目,并将其命名为<code>TweetApp</code>。将<code>MainController</code>添加到项目中。</p>
<h2 id="如何操作-52">如何操作...</h2>
<ol>
<li>
<p>在<code>MainController</code>的视图中添加一个按钮,并在类中输入<code>MonoTouch.Twitter</code>命名空间。接下来,输入以下代码:</p>
<pre><code class="language-swift">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!");
}
} ;
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。如果模拟器上尚未设置 Twitter 账户,可以通过<code>Settings</code>应用程序轻松配置。</p>
</li>
<li>
<p>点击按钮以显示 Twitter 控制器。结果应该类似于以下截图:</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_15_02.jpg"></p>
<h2 id="它是如何工作的-86">它是如何工作的...</h2>
<p>iOS 提供了 <code>TWTweetComposeViewController</code> 类,它提供了共享功能。此控制器与用于从设备相册共享照片的原生界面相同。就像类似的本地控制器一样,我们只能在它呈现之前设置其内容。我们无法在它向用户显示后对其进行修改,用户负责是否发送或丢弃它。</p>
<p>我们可以通过读取 <code>CanSendTweet</code> 静态属性来确定用户是否在设备上配置了 Twitter 账户:</p>
<pre><code class="language-swift">if (TWTweetComposeViewController.CanSendTweet)
</code></pre>
<p>如果我们在设备上未设置账户的情况下呈现控制器,将显示一个原生警报,用户可以选择在继续之前配置账户。</p>
<p>然后,我们初始化控制器,并使用 <code>SetInitialText</code> 方法设置要填充的文本,如果需要的话:</p>
<pre><code class="language-swift">this.tweetController = new TWTweetComposeViewController();
this.tweetController.SetInitialText("Tweet from MonoTouch!");
</code></pre>
<p>我们还可以通过 <code>AddUrl</code> 和 <code>AddImage</code> 方法分别添加推文中的 URL 或图片。</p>
<p>为了获取用户是否发送或取消推文的反馈,我们调用 <code>SetCompletionHandler</code> 方法,传递要调用的回调:</p>
<pre><code class="language-swift">this.tweetController.SetCompletionHandler(delegate(TWTweetComposeView ControllerResult tweetResult) {
</code></pre>
<p>此回调接受枚举类型 <code>TWTweetComposeViewControllerResult</code> 的一个参数,它可以包含两个值中的任意一个 <code>Done</code> 或 <code>Cancelled</code>。</p>
<p>最后但同样重要的是,我们应该在回调中关闭控制器。</p>
<h3 id="注意-45">注意</h3>
<p>手动关闭 <code>TWTweetComposeViewController</code> 不是必需的。然而,如果没有手动关闭它,已经注意到尽管在用户点击 <strong>Send</strong> 时控制器被关闭,但需要两次点击 <strong>Cancel</strong> 按钮才能关闭它。</p>
<h2 id="还有更多-54">还有更多...</h2>
<p>除了发送推文外,<code>MonoTouch.Twitter</code> 命名空间还封装了 <code>TWRequest</code> 类,允许我们通过 Twitter API URL 读取 Twitter 信息,例如用户的推文时间线。通过这种方式接收到的数据是以 JSON 对象的形式,正确读取它们是我们的责任。</p>
<h3 id="支持横幅方向">支持横幅方向</h3>
<p><code>TWTweetComposeViewController</code> 支持横幅方向。要启用它,我们只需覆盖呈现它的控制器中的 <code>ShouldAutoRotateToInterfaceOrientation</code> 方法。</p>
<h1 id="与分割键盘一起工作">与分割键盘一起工作</h1>
<p>在这个菜谱中,我们将创建一个能够感知虚拟键盘位置变化的应用程序,以便相应地调整我们的内容。</p>
<h2 id="准备工作-101">准备工作</h2>
<p>在 MonoDevelop 中创建一个新的 iPad 项目,并将其命名为 <code>SplitKeyboardApp</code>。将 <code>MainController</code> 添加到项目中。</p>
<h2 id="如何做到这一点-19">如何做到这一点...</h2>
<ol>
<li>
<p>添加一个 <code>UITextField</code>,并将其放置在 <code>MainController</code> 的中心位置。调整文本框的大小,使其宽度扩展到屏幕的另一侧。在 <code>MainController</code> 类中添加以下代码:</p>
<pre><code class="language-swift">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();
}
}
}
}
</code></pre>
</li>
<li>
<p>在模拟器上编译并运行应用程序。</p>
</li>
<li>
<p>点击文本框以显示键盘。如果之前未在 iPad 模拟器中使用过键盘,它将默认处于正常状态,即在底部并合并。</p>
</li>
<li>
<p>点击并拖动屏幕右下角的<strong>隐藏键盘</strong>键来移动键盘,使其分裂,并让它停留在文本字段上方。</p>
</li>
</ol>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_15_03.jpg"></p>
<ul>
<li>观察文本字段在键盘上方动画。结果应该类似于以下截图:</li>
</ul>
<p><img src="https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios-dev-mntch-cb/img/1468EXP_15_04.jpg"></p>
<h2 id="它是如何工作的-87">它是如何工作的...</h2>
<p>要检测分裂键盘的位置,我们首先需要为<code>UIKeyboardDidChangeFrameNotification</code>键添加一个观察者:</p>
<pre><code class="language-swift">this.kbdFrameChangedObserver = NSNotificationCenter. DefaultCenter.AddObserver(new NSString( "UIKeyboardDidChangeFrameNotification"), this.KeyboardFrameChanged);
</code></pre>
<p>在<code>KeyboardFrameChanged</code>回调内部,我们从<code>UserInfo</code>字典中获取<code>FrameEndUserInfoKey</code>键的值。这个值,作为一个<code>NSObject</code>返回,实际上是一个包含键盘框架的<code>NSValue</code>对象。我们从它中读取<code>RectangleFValue</code>属性,以获取包含键盘框架值的<code>RectangleF</code>对象:</p>
<pre><code class="language-swift">if (ntf.UserInfo.TryGetValue(UIKeyboard.FrameEndUserInfoKey, out frameEndObj)){
RectangleF keyboardFrame = (frameEndObj as NSValue).RectangleFValue;
}
</code></pre>
<p>其余的代码将文本字段移动到键盘上方。请随意更改它!</p>
<h2 id="还有更多-55">还有更多...</h2>
<p><code>NSNotification</code>类的<code>UserInfo</code>属性返回一个<code>NSDictionary</code>对象,其中包含有关键盘的各种信息。为了枚举它包含的键,简单的<code>foreach</code>即可:</p>
<pre><code class="language-swift">foreach (NSString eachItem in ntf.UserInfo.Keys){
Console.WriteLine("Key: {0}", eachItem);
}
</code></pre>
<h3 id="移动键盘有问题">移动键盘有问题?</h3>
<p>隐藏键盘键在我们长按它时会出现一个小的“上下文菜单”。这个菜单提供了停靠和合并(或相反操作)键盘的选项。为了将键盘移动到我们想要的位置,我们必须在点击键的瞬间就开始拖动。</p><br><br>
来源:https://www.cnblogs.com/apachecn/p/19134788
頁:
[1]