Xamarin-IOS-移动应用开发-全-
Xamarin IOS 移动应用开发(全)
原文:
zh.annas-archive.org/md5/df0d9ce04a9c53c7449329b9b3e852fc译者:飞龙
前言
欢迎来到这本书!希望这本书的封面内容能帮助你开始制作自己的 iOS 应用程序;不仅如此,它还将帮助你开始编写可以轻松迁移到 Android 和 Windows 8 手机和平板电脑的代码。朋友们,请系好你们的帽子!
本书涵盖的内容
第一章, 为 Android 和 iOS 安装 Xamarin 产品系列,解释了如何设置您的 PC 或 Mac 以开发 iOS 设备的应用程序。
第二章, 用户界面,处理创建用户界面及其关键组件。
第三章, 视图和布局,解释了创建用户界面不仅仅是把按钮放在屏幕上;你还需要从正确应用类型开始。
第四章, 控制器,介绍了两种最常用的导航和视图形式的基本知识。iOS 使用 MVC 系统(模型、视图、控制器)。我们已经有了前两个,现在让我们看看控制器。
第五章, UI 控件,解释了控件不仅仅是按钮和文本框;我们真的可以大显身手,看看这些家伙的外观。
第六章, 事件,解释了如果没有事件,你的 iOS 设备将不过是一块塑料。iOS 事件丰富,它们都在这里。
第七章, 手势,涵盖了 iOS 大肆宣传的操作,如捏、扫动和屏幕上的移动。这些操作被称为手势,它们非常简单易用!
第八章, 线程,处理 iOS 作为一个多线程系统。这些线程的交互方式决定了应用程序的行为。
第九章, 线程任务,处理任务调度以及异步任务。它解释了 Android 如何与这些任务交互。
第十章, 动画,深入探讨了动画,因为它是任何应用程序的重要组成部分。
第十一章, 处理数据,解释了在 C#和 iOS 中处理和操作大量复杂数据竟然如此简单。
第十二章, 外围设备,解释了如何编写代码以利用手机上的通话和短信功能以及使用 GPS 系统。
第十三章,用户偏好,解释了如何存储内置系统的设置以及创建自己的跨平台设置代码。存储您的设置是任何应用程序的重要部分。
第十四章,测试和发布,处理了在您完成开发后如何测试和发布应用程序。
您需要这本书的内容
一台安装了 Xcode 并拥有 Xamarin.iOS 副本的 Mac(运行 OS.X Lion 或 Mountain Lion)。如果您使用的是 PC,则需要运行 Windows 7。您需要在网络上某个地方有一个 Mac 来部署和使用 Xcode。
一部 iPhone 或 iPad 也很有用。
这本书面向的对象
这本书是为那些已经使用 C#进行编码的人准备的;这是一个基本假设。您不需要为移动设备编写过任何东西或使用过 Objective C 进行编码。如果您对为 iOS 开发代码感兴趣,那么您就找到了正确的位置。
惯例
在这本书中,您将找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词如下所示:“UIImageView没有附加事件,所以如果您需要一个可以点击的图像,它们可以被种植到UIButton类中。”
代码块设置如下:
var r = new UIButton();
r.Frame = new RectangleF(0, 0, 100f, 100f);// button 100 x 100 at 0, 0
var i = new UIImageView(new RectangleF(15f, 2f, 70f, 70f));
i.Image = UIImage.FromFile("path/toimage.png")Scale(new SizeF(70f, 70f));
var l = new UILabel(new RectangleF(2f, 78f, 96f, 20f));
l.Text = "Hello world";
r.AddSubview(i);
r.AddSubview(l);
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“类需要在连接器中定义为输出和动作(选择事件为值更改)。”

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


小贴士
小贴士和技巧如下显示。

读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中获得最大收益的标题非常重要。
要发送一般反馈,请简单地发送电子邮件到<feedback@packtpub.com>,并在邮件主题中提及书名。
如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 图书的骄傲拥有者,我们有一些东西可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从www.PacktPub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.PacktPub.com/support并注册,以便将文件直接通过电子邮件发送给您。
勘误
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。这样做可以帮助其他读者避免挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的勘误部分下的现有勘误列表中。您可以通过从www.packtpub.com/support选择您的标题来查看任何现有勘误。
盗版
互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何我们作品的非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过<copyright@packtpub.com>与我们联系,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。
问答
如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>联系我们,我们将尽力解决。
第一章. 安装 Xamarin 产品系列以支持 Android 和 iOS
Xamarin 产品系列涵盖了 iOS、OS X 和 Android 开发。这使您能够在不支持其的设备上通过受尊敬且成熟的 Mono 框架进行.NET框架开发。
在本章中,我们将涵盖以下主题:
-
安装 Xamarin.iOS、Xamarin.Android 和 Xamarin Studio
-
设置 Windows 机器以开发 iOS 应用程序
安装 Xamarin.iOS 和 Xamarin.Android
在 Windows 和 OS X 上安装 Xamarin 的过程非常相似且简单。在下载之前,您需要确保您的计算机满足以下最低要求:
| Windows | Mac |
|---|
|
-
Windows 7 或 Windows 8
-
Visual Studio 2010 或 2012
|
-
OS X Lion 或 Mountain Lion
-
Xcode v4.6 或更高版本
|
对于两者,一般规则是您拥有的内存越多越好。您还需要有一个活跃的网络连接。
下载软件
该网站能够检测您正在使用的操作系统,并在您选择下载时,将为您下载适合您操作系统的正确版本。
在您被允许下载评估副本之前,您需要向 Xamarin 提供一些基本信息。评估副本将允许您开发并部署应用程序 30 天,之后您将需要购买副本。如果您已将任何应用程序发布到 Apple Store 或 Google Play,它们将不再工作。
在 PC 上,双击XamarinInstaller.exe文件(Windows 8 用户应作为管理员运行此文件)。对于 Mac 用户,双击安装程序。
安装软件
根据您的网络连接,这个过程可能需要长达一个小时,因为每个软件包都会依次下载并安装。安装过程是自动的,除了Xcode之外,所有软件运行所需的组件都会被安装。检查您是否已安装 Xcode 在 Mac 上的简单方法是:点击“应用程序”文件夹,然后在底部附近寻找图标。如果找不到,请前往 App Store 并输入Xcode。下载是免费的。除非您有理由更改默认设置,否则每次出现选项时,请点击下一步接受默认设置选项。

启用 Visual Studio 以构建和运行 iOS 应用程序
为了让 Windows 能够创建、构建和部署 iOS 应用程序,它必须连接到网络上的某个 Mac。该 Mac 还必须安装 Xamarin.iOS。这是一个两步过程。
在 Mac 上
-
点击苹果图标并选择系统偏好设置。
![在 Mac 上]()
-
然后您需要选择安全与隐私首选项选项。
![在 Mac 上]()
-
然后需要关闭防火墙。虽然这通常不是一个好主意,但大多数内部网络在任何路由器上都有一个足够好的防火墙。如果你不满意这个设置,请保留设置——你将无法在 Windows 下运行或为 iOS 开发。
在 PC 上
当 Xamarin.iOS for Visual Studio 安装时,它还会安装一个名为Bonjour的小型监听服务。当你尝试创建 iOS 应用程序时,Bonjour 将尝试自动找到你的网络上的 Mac。这可能失败,如果发生这种情况,你将看到以下屏幕截图:

你不需要担心错误。点击关闭。你将看到一个窗口,允许你输入你的 Mac 的 IP 地址。

在此示例中,我网络上的 Mac 的 IP 地址显示在上一个屏幕截图中。当你点击确定时,Bonjour 将尝试连接到 Mac。如果成功,你将看到以下屏幕截图:

一旦你在屏幕上看到前面屏幕截图所示的窗口,你就快完成了。下一步是点击主机机(如前面屏幕截图所示)并点击连接。完成后,你就在 PC 上设置了 iOS 开发环境。
所有开发工具(如模拟器——模拟的 iOS 设备)都可以在 iPhone 或 iPad 系列中的任何一个上。
如果 Bonjour 服务无法自动确定 Mac,你可能需要手动设置服务。这可以非常容易地完成。
假设你正在 Windows 7 上,以下步骤将帮助你设置 Bonjour 服务:
-
点击开始按钮并选择控制面板。
-
在控制面板上,点击管理工具,然后选择服务。你将看到一个窗口,如下面的屏幕截图所示:
![在 PC 上]()
-
确保状态为已启动,启动类型为自动。(这将在 Windows 重新启动时启动 Bonjour 服务。)
![注意]()
注意
当使用 PC 开发 iOS 应用程序时,确保 PC 和 Mac 都运行相同的 Xamarin.iOS 版本——如果它们不一致,你需要在不一致的设备上安装较新的 Xamarin.iOS 版本。
![注意]()
安装 Android 开发的额外代码
这在 iOS 和 PC 上有所不同,但最终效果是相同的。
当 Xamarin.Android 在任一平台上安装时,编译器和 Android 软件开发工具包(SDK)的最小集合将被安装。这将让你开始开发,但不会让你针对一系列设备。因此,安装其他版本的 Android 操作系统的 SDK 非常重要。这是通过Android SDK 管理器完成的。

要在 PC 上访问SDK 管理器,请选择开始,然后在所有程序菜单中,有一个名为Android SDK 工具的菜单选项,其中包含Android SDK 管理器。选择SDK 管理器,您将看到一个新窗口,允许您选择您想要的 SDK。
对于 iOS 用户
在 Mac 上的安装过程中,会安装Xamarin IDE(集成开发环境)——Xamarin Studio。它在许多方面与 Visual Studio 相似,并且执行非常相似的任务。

要在 Xamarin Studio 中访问 SDK 管理器,请选择工具,然后选择打开 Android SDK 管理器。
在这两种情况下,您都会看到 SDK 管理器中的以下截图。最简单的方法是选择所有 SDK,然后点击安装包...按钮开始安装过程。根据您的网络连接速度,这个过程可能需要一段时间。

摘要
就这样——您已经在 Mac 和 PC 上设置好了,可以创建令人惊叹的 Android 和 iOS 应用程序。您的开发环境已经设置好。然而,在这本书的其余部分,我将专注于 iOS 应用程序的开发,并将 Android 留给配套书籍。
第二章. 用户界面
用户界面是设备与用户之间主要的通信方式。设计和外观是区分优秀应用程序和卓越应用程序的关键。
在本章中,我们将介绍用户界面的一些基本功能:
-
画布
-
MonoTouch.Dialog
-
将外部视图集成到您的用户界面中
-
颜色
-
标签
-
图片
使用 Xcode 创建用户界面
除了 MonoTouch.Dialog 之外,任何 iOS 应用程序的用户界面都是使用 Xcode 创建的。
在考虑如何创建用户界面以及任何可用小部件的位置时,您需要从在画布上放置毛绒布的角度思考。您可以在毛绒布的任何地方放置任何 iOS 小部件。
要创建简单的用户界面,创建一个新的 iOS 应用程序。点击文件,然后新建。您将看到一个如下所示的窗口:

点击单视图应用程序,在名称字段中输入文件名,完成后点击确定。Xamarin.iOS 将创建一个包含所有文件夹和文件的目录,以帮助您开始。取消选中为解决方案创建目录复选框仍然会创建应用程序,但文件将不会保存在目录结构中,而是保存在位置路径指向的地方。应用程序仍然可以编辑和修改,但文件丢失或被覆盖的可能性很大(例如,如果您在多个项目中工作,每个项目都会创建一个 AppDelegate.cs 文件。当创建新版本时,它将覆盖旧版本。)
一旦设置好应用程序结构,您将看到一个新文本编辑窗口,如下所示:

在解决方案资源管理器的底部,有三个文件需要特别注意:
-
testappViewController.cs:这是创建应用程序 C# 代码的文件
-
testappViewController.designer.cs:这是一个由 Xamarin.iOS 创建的设计师文件,基于使用 Xcode 创建的用户界面
-
testappViewController_iPad.xib 和 testappViewController_iPhone.xib:任何以
.xib扩展名的文件都是 Xcode 设计师文件![注意]()
小贴士
要启动 Xcode,只需双击
.xib文件。要编辑.xib文件,您必须根据您希望编辑的视图控制器用户界面双击 testappViewController_iPad.xib 或 testappViewController_iPhone.xib 文件。![注意]()
Xcode 是一个非常简单的设计师,但在这个简单性中隐藏着非常强大的软件。启动时,您将看到以下屏幕:

Xcode 设计器是一个非常复杂的软件。然而,由于有书籍专门介绍如何最大限度地利用它,以及由于篇幅限制,我将在此仅讨论最小化内容。
要添加小部件,只需选择并将其拖放到主视图中。您几乎可以将任何小部件拖放到任何位置。然而,您的应用程序对此一无所知,因为它需要连接。连接小部件很简单。前面的截图显示了一个名为“连接器”的按钮。点击此图标,将在属性框架的左侧出现另一个框架。要连接小部件,请点击Ctrl并将其拖动到连接器框架中。

从这一点开始,您必须决定按钮的类型:出口、动作或出口集合。每个都大不相同,它们的名称有时会令人困惑。
通常认为出口按钮用于显示信息而不是接受事件。然而,动作,比如点击事件,是在出口中处理的。如果您将按钮作为出口连接,它被视为一个接口。它对所有可用的修改器和事件都开放。出口集合是一组出口。动作就是那样——它是一个与该对象链接的特定动作(或事件)。
然而,它们之间有一个重大区别。如果您将按钮连接为出口,则必须专门添加事件。如果您对按钮不做任何操作,应用程序将在您的设备上运行,但按钮将不会做任何事情。如果您将按钮连接为动作,则必须在应用程序运行之前编写动作代码。不这样做会导致应用程序崩溃。
事件将在后面的章节中处理。
屏幕原点和尺寸
所有屏幕和视图都从左上角的0,0开始。要获取屏幕尺寸(记住,这将因 iPhone 和 iPad 的不同版本而异),请考虑以下代码行:
var window = new UIWindow(UIScreen.MainScreen.Bounds);
float ScreenX = window.Screen.CurrentMode.Size.Width;
float ScreenY = window.Screen.CurrentMode.Size.Height;
MonoTouch.Dialog (MT.D)
iOS 屏幕通常以列表形式包含大量数据(想想 Facebook 或 Twitter 的外观)。在 iOS 上,这些是通过UITableView构建的。这是 UI 中非常灵活的部分,但编码起来可能很棘手。为了减轻UITableView的问题,Xamarin 创建了MonoTouch.Dialog类。MT.D的好处是,设计界面不需要 Xcode,因此它可以在 Windows 和 Mac 上一样简单地创建。
MonoTouch.Dialog视图非常简单创建,并且基于三层系统进行设计:
-
元素: 这些包含诸如
on/off布尔开关、字符串、图像以及您通常在用户界面中看到的其他任何内容。 -
部分: 这些包含任意数量的元素。
-
根元素: 这些元素包含部分内容。
MT.D类必须至少有一个根元素。
有许多不同类型的元素。在以下示例中,构建了一个简单的用户界面(此代码是在您要求 Xamarin Studio 创建MT.D类时自动生成的):
public partial class Login : DialogViewController{
public Login() : base (UITableViewStyle.Grouped, null){
Root = new RootElement(“Login”){
new Section (“First Section”){
new StringElement (“Hello”, () => {
new UIAlertView (“Hola”, “Thanks for tapping!”, null,“Continue”).Show();
}),
new EntryElement(“Name”, “Enter your name”, string.Empty)
},
new Section (“Second Section”){
},
};
}
}
编译后,代码生成以下结果(第一个视图是初始显示;第二个视图是在点击Hello条目时显示):

图像 A
点击后会得到以下图像:

图像 B
键盘不必是标准的 QWERTY 键盘。它可以是一个专门用于电子邮件地址、电话号码或仅数字的键盘。这是使用UIKeyboardType定义的。
更改键盘类型
考虑以下代码行:
var entryExample = new EntryElement(“Caption”,“Type a number here”, string.Empty);
当前面的代码行附加到 MT.D 类视图时,点击元素将弹出一个标准字母数字键盘。对于标准输入,这很好;然而,在这个例子中,需要不同类型的键盘。为此,可以使用以下任一方法:
entryElement.KeyboardType = UIKeyboardType.DecimalPad
entryElement.KeyboardType = UIKeyboardType.NumberPad
然而,这些键盘有一个问题,那就是关闭它们。对于标准字母数字键盘,可以通过以下方式在键盘本身添加一个回车键:
entryElement.ReturnKeyType = UIReturnKeyType.Done
类似于数字键盘,即使添加了ReturnKeyType,也没有回车键。有三种方法可以解决这个问题:
-
ShouldReturn -
ResignFirstResponder -
向键盘添加一个带有关闭按钮的工具栏
使用 ShouldReturn
这是一个简单的方法来使用,但它依赖于键盘上有回车键:
entryElement.ShouldReturn += delegate {
adminPhone.ResignFirstResponder(true); // animated
};
使用 ResignFirstResponder
除非在设计师或代码中定义,否则FirstResponder方法就是首先点击的控制。当点击其他东西(比如另一个EntryElement,但这也适用于任何其他控制)时,需要将其变为FirstResponder控制。通过发出ResignFirstResponder方法,关闭该控制的键盘。点击新的控制应该发出BecomeFirstResponder控制,并且其键盘出现(假设与控制相关联的键盘)。
向键盘添加工具栏
工具栏实际上不是键盘的一部分,但可以附加到键盘上以提供额外的或缺失的功能。对于MT.D类,添加键盘的方式与标准UITextField或UITextView控件不同。
对于 MT.D
在这里,需要一个EntryElement类的子类来实现InputAccessoryView方法。虽然可以通过从 Root 构造函数查看TableView来找到正在使用的单元格,但InputAccessoryView是从这里的一个只读参数,因此不能设置。
子类的示例如下:
public class ToolbarKeyboardEntryElement : EntryElement{
private UITextField textField;
public ToolbarKeyboardEntryElement(string caption,
string placeholder, string value) :
base(caption, placeholder, value) {
}
protected override UITextField CreateTextField(System.Drawing.RectangleF frame) {
textField = base.CreateTextField(frame);
UIToolbar toolHigh = new UIToolbar() {
BarStyle = UIBarStyle.Black, Translucent = true
};
toolHigh.SizeToFit();
UIBarButtonItem doneHigh = new UIBarButtonItem(“Done”,UIBarButtonItemStyle.Done, (ss, ea) => {
textField.ResignFirstResponder();
}
);
toolHigh.SetItems(new UIBarButtonItem[] { doneHigh }, true);
textField.InputAccessoryView = toolHigh;
return textField;
}
private NSString key = new NSString(“CustomEntryElement”);
protected override NSString CellKey {
get {
return key;
}
}
}
}
主代码中的EntryElement类需要修改以读取。
var entry = new ToolbarKeyboardEntryElement(“Caption”,“Enter a number”, string.Empty);

小贴士
对于这个例子,我没有包括密码参数。

对于标准的 UITextField
在这里,工具栏(如前一小节所述)被创建,但没有动作表,然后通过以下方式添加到 UITextField 方法中:
var txtField = new UITextField();
txtField.InputAccessoryView = toolBar;
图像 B(MonoTouch.Dialog (MT.D)部分)显示了 UIAlertView 控件。这是一个可定制的警告框,通常用于信息(例如,错误、在慢速过程中停止用户担心,或需要用户选择时)。
MT.D 的真正美在于它消除了与 UITableView 控件相关的乏味。它为开发者提供了从 UITableView 控件中所需的大多数功能,而无需麻烦。此外,如果您需要特殊的东西(例如标准界面按钮),这些可以通过简单地创建元素类型的子类来实现。
MT.D 类支持的元素类型如下:
| Element | 使用 | 使用注意事项 |
|---|---|---|
ActivityElement |
用于显示正在发生的事情(它是一个旋转器) | |
BadgeElement |
带文本的图像 | |
BaseBooleanImageElement |
布尔的基础类型,不能直接使用(抽象类) | |
BooleanElement |
简单的 开/关 开关 |
|
BooleanImageElement |
简单的 开/关 开关,允许显示两种不同的图像 |
|
BoolElement |
不能直接使用(抽象类) | |
CheckboxElement |
选择时在字符串旁边打勾 | |
DateElement |
显示日期选择器 | 这是一个两部分的元素。第一部分看起来像标准的 StringElement 元素,但在右侧值的旁边有一个 > 符号。点击时,会显示一个 UIDatePicker 元素。选定的值在值元素中返回。 |
DateTimeElement |
显示日期/时间选择器 | 实质上与 DateElement 相同,但包括时间。时间可以设置为 12 小时或 24 小时制 |
Element |
基础元素 | |
EntryElement |
允许数据输入 | 构造函数中的可选第四个参数允许将输入用于密码(将密码设置为 true)。构造函数接受三个字符串:标题、占位符和值。占位符和值可以是字符串或 Empty,但标题必须有值。 |
FloatElement |
滑块栏 | 值为浮点数 |
HtmlElement |
带有 HTML 视图的标题 | 这是一个交叉元素,因为发起者只是一个 Element,但点击时显示一个 UIWebView 元素。 |
ImageElement |
生成图像 | |
ImageStringElement |
生成带有字符串的图像 | |
JsonElement |
允许从本地或远程 URL 加载内容 | |
LoadMoreElement |
允许用户向屏幕上的列表添加更多项目 | |
MessageElement |
将其视为在 Twitter 上找到的消息类型 | |
MultilineElement |
允许显示多行文本 | 不能被样式化 |
OwnerDrawnElement |
不直接使用 | 必须子类化。必须重写 Height 和 Draw 方法 |
RadioElement |
允许从多个选项中选择单个选项的无线电元素 | 需要在元素中指定一个无线电组 |
RootElement |
根元素 | |
StringElement |
左侧有一个简单的标题,右侧有一个值 | 此元素也可以用作按钮,通过提供匿名代理作为第二个参数(如上例所示) |
StyledMultilineElement |
与 MultilineElement 基本相同,但可以自定义样式 |
|
StyledStringElement |
允许使用内置样式(如颜色、字体和大小)和自定义格式来显示字符串 | |
TimeElement |
显示时间选择器 | 与 DateElement 相同,但用于时间。 |
UIViewElement |
可显示的 UIView |
使用 Xcode 设计 UIView。 |
在 MT.D 上创建自己的选择器
我决定使用 UIPickerView 并将其与顶部的 UIToolBar 结合,然后将其整合到 UIActionSheet 中来演示这一点。
首先,我们需要两样东西:一个事件来挂钩,以及模型(包含 UIPickerView 所需的信息)。然后就是将这两者连接起来。
-
首先是事件:
public class PickerChangedEventArgs : EventArgs { public string SelectedValue { get; set; } }实际上,这可以返回任何东西,不仅仅是字符串。但出于我的目的,我会将其保持为字符串。
-
接下来是模型:
public class PickerModel : UIPickerViewModel { private Ilist<string> myValues; public event EventHandler<PickerChangedEventArgs>PickerChanged; public PickerModel(IList<string> values) { myValues = values;} public override int GetComponentCount(UIPickerView picker) { return 1; } public override int GetRowsInComponent(UIPickerView picker, int component) { return myValues.Count; } public override string GetTitle(UIPickerView picker,int row, int component) { return myValues[row]; } public override float GetRowHeight(UIPickerView picker, int component) { return 40f; } public override void Selected(UIPickerView picker,int row, int component) { if (PickerChanged != null) { PickerChanged(this, new PickerChangedEventArgs {SelectedValue = myValues[row] }); } } }这不是什么火箭科学——唯一重要的是父类的
Selected()方法被重写以返回所选行的值;其他所有内容都覆盖了默认类设置。 -
为了将其连接到主
MT.D类,使用EntryElement:EntryElement myElement = null; -
还需要一个
UIActionSheet元素:UIActionSheet action = new UIActionSheet(); -
然后我们创建
UIPickerView:List<string> data = new List<string>() {“Hello”,“This is a”, “test”}; Ilist<string> iData = data; var myPickerViewModel = new PickerModel(iData); var MyPickerView = new UIPickerView() { Model = myPickerViewModel, ShowSelectionIndicator = true, Hidden = false, AutosizeSubviews = true, }; myPickerView.Frame = new RectangleF(0, 100, 320, 162); // 320 = screen x size for an iPhone 4 -
接下来,创建
UIToolbar和UIBarButtonItemvar toolBar = new UIToolbar() { BarStyle = UIBarStyle.Black, Translucent = true, }; toolBar.SizeToFit(); var doneButton = new UIBarButtonItem(“Done”,UIBarButtonItemStyle.Done, (s, e) => { action.DismissWithClickedButtonIndex(0, true);myElement.ResignFirstResponder(true); }); toolBar.SetItems(new UIBarButtonItem[] { doneButton },true); myPickerViewModel.PickerChanged += (object sender,PickerChangedEventArgs e) => { myElement.Value = e.SelectedValue; }; -
创建
EntryElement对象:myElement = new EntryElement(“Hello”, string.Empty,string.Empty); -
现在只需要使用
EntryStarted事件来调用选择器:myElement.EntryStarted += (object ss, EventArgs ee) => { action.Style = UIActionSheetStyle.BlackTranslucent; action.ShowInView(View); action.AddSubview(toolBar); action.AddSubview(myPickerView); action.Frame = new RectangleF(0, 100, 320, 500); myPickerView.Frame = new RectangleF(action.Frame.X,action.Frame.Y – 25, action.Frame.Width, 216); };当所有代码都编写完成后,结果如下:
![在 MT.D 上创建自己的选择器]()
-
元素的子类化同样简单。子类
EntryElement允许输入特定数量的字符:public class MaxNumberEntryElement : EntryElement { private UITextField textField; public int MaxLength { get; set; } public MaxNumberEntryElement(string caption,string placeholder, string value, int maxLength) :base(caption, placeholder, value) { MaxLength = maxLength; } protected override UITextField CreateTextField(System.Drawing.RectangleF frame) { textField = base.CreateTextField(frame); textField.ShouldChangeCharacters = (UITextField t,NSRange range, string replacementText) => { int newLength = t.Text.Length + replacementText.Length- range.Length; return (newLength <= MaxLength); }; return textField; } private NSString key = new NSString(“CustomEntryElement”); protected override NSString CellKey { get { return key; } } }当然,你仍然可以使用
UITableView和UITableViewCell。
UITableView 和 UITableViewCell
UITableView 方法是 iPhone 的主力。大多数,如果不是所有,的列表数据都是通过 TableView 和 TableViewCell 方法显示的。如果你是 Facebook、Twitter、标准 iPhone 文本消息应用或 iPhone 上的任何形式配置的用户,你将使用这两个组件——这让你了解到它们的使用程度。
这个主题非常庞大,将在第四章控制器中进一步讨论。
颜色、按钮和标签
UILabel是将文本放置在屏幕上的最简单方法。与其使用相关联的操作范围非常有限(例如,你不能将其用作可点击的对象)。可以设置标签的颜色和文本,也可以设置格式。例如,考虑以下代码行:
label.Text = “text”; // sets the text label to be “text”
label.TextColor = UIColor.Blue; // sets the text color to be blue
label.BackgroundColor = UIColor.FromRGB(255,255,200);
// sets the background to be yellow
label.TextAlignment = UITextAlignment.Center; // centres the text in the label
UILabel方法有五个构造函数,其中两个非常有用:
-
UILabel()UILabel(new RectangleF(x_pos, y_pos, width, height))
第二个构造函数可以使用Frame属性来复制,如下所示:
-
UILabel lbl = new UILabel();lbl.Frame = new RectangleF(x_pos, y_pos, width, height);
使用UILabel的一个主要问题是确保边界框足够大。有一种方法可以解决这个问题:
-
将标签的大小做得比所需的大得多。
-
计算字符串的长度,并使用
Frame属性来改变标签的大小。 -
减小字符串的字体大小以确保其能够适应。
这两种方法都有其优点和缺点。第一种是文本总是能够适应,但前提是字体大小是系统默认值。第二种是你会始终拥有正确的大小边界框,但你需要自己计算大小,这会花费时间。
确保你有正确的大小边界框
这假设已经使用 Xcode 创建了一个标签。在这个例子中,标签的宽度是 96(足够写“更多文本”):
string test2 = “More text to fit and boy, does it fit!”;
lblTestLabel.AdjustsFontSizeToFitWidth = true;
lblTestLabel.Text = test2;
UIColor
iOS 自带一系列预设的颜色(如红色、绿色、蓝色、黑色和白色)。你也可以使用UIColor.FromRGB[A]以及FromHSB[A](其中HSB代表色调、饱和度和亮度,而[A]是透明度通道)来创建自己的颜色。后者可以看作是颜色的透明度。颜色也可以从图案(这很有用,因为它可以根据图像创建颜色,然后可以用作画笔来绘制图像)、CoreImage和CoreGraphicsColor(CI和CGColor)以及FromWhiteAlpha(基于当前颜色空间的灰度颜色)中设置。
使用CIColor和CGColor需要做更多的工作,但允许在颜色上有更大的灵活性。
最简单易用的方法是FromRGB。这允许输入的值以字节、整数和浮点数的形式输入。但在这里需要注意的是,浮点数的范围是从0到1而不是从0到255,所以82, 184, 33的值将是0.32、0.72和0.13(即 82/255、184/255 和 33/255)。
UIButton
一个按钮不仅仅是一个按钮;它可以应用一系列有趣的效果(如添加图形、渐变色、文本和图形)。假设在 Xcode 中创建了一个按钮(btn),我们希望给它应用渐变色。
var gradient = new CAGradientLayer();
gradient.Colors = new MonoTouch.CoreGraphics.CGColor[] {
UIColor.FromRGB(115, 181, 216).CGColor,
UIColor.FromRGB(35, 101, 136).CGColor
};
gradient.Locations = new NSNumber[] { .5f, 1f };
gradient.Frame = btn.Layer.Bounds;
btn.Layer.AddSublayer(gradient);
btn.Layer.MasksToBounds = true;
CAGradientLayer来自CoreAnimation命名空间。
添加图像也是相当简单的,但重要的是要记住,当在按钮上放置任何东西时,你必须将那个按钮视为一个新的视图,其原点设置在按钮的左上角。还要记住,按钮可以有前景和背景图像。
通常,背景图像会覆盖整个按钮。
btn.SetBackgroundImage(UIImage.FromFile(“Path/ToImage.png”),UIControlState.Normal);
这里的第二个参数(UIControlState)是按钮(或控件)所处的状态。Normal 表示未选中状态。当按钮被按下时,状态变为 Highlighted,当释放时,状态再次变为 Normal。这意味着你可以根据按钮的状态显示不同的图片。
前景图像通常不会覆盖整个按钮,而会是一个特定的尺寸。例如,假设按钮是 92 x 92。为了填充按钮的大部分区域,每边 4 的间距会很好;这使得尺寸为 84 x 84(左右间距,以及高度和宽度)。为按钮创建此图像是一个两步过程:创建图像并添加图像。这次,ImageView 首先被使用,然后通过 SetImage 方法传递:
UIImageView btnImage = new UIImageView (new RectangleF(new PointF(4, 4), new SizeF(84, 84)));
btnImage.Image = UIImage.FromFile (“Path/ToImage.png”).Scale (new SizeF(84, 84));
btn.SetImage(btnImage.Image, UIControlState.Normal);
另一个选择是将 ImageView 作为子视图添加到按钮中。
btn.AddSubview(btnImage);
UIButton 也可以直接为其分配颜色。
btn.BackgroundColor = UIColor.Gray;
按钮上还有一个默认的文本,称为 Title。与任何文本元素一样,这也可以设置:
btn.SetTitle(“Some text”, UIControlState.Normal);
一个更有趣的效果是在按钮上同时显示文本和图形。考虑放置的最简单方法是以下这样:
设 a 和 b 为图像的左上角和右下角的位置(在我们之前的例子中,这将是 4, 4)。为了方便,右侧也有相同的间距。
设 c 为从顶部(a + “图像高度”+ “一些间距”)的偏移量。
然而,这里的技巧是确保底部有足够的间距,以免看起来杂乱。
添加图像是一个两步或三步的过程。
-
如果设置了
Title,则清除它(这可以在设计器中实现)。 -
添加图片(参见上一个示例)。
-
创建并添加一个
UILabel。// step 1 if (!string.IsNullOrEmpty(btn.CurrentTitle)) btnTitle.SetTitle(string.Empty, UIControlState.Normal); // step 3 UILabel myLabel = new UILabel(new RectangleF(4, 78),new SizeF(84,10)); myLabel.Text = “some text”; btn.AddSubview(myLabel);
UIControlStates
(如前所述)有几种 UIControlStates:Application、Disabled、Highlighted、Normal、Reserved 和 Selected。对于大多数日常考虑,最常用的是 Disabled、Highlighted 和 Normal。
如果 Enabled 属性为 false,按钮处于 Disabled 状态。唯一的问题是这是使用系统默认值判断按钮是否禁用的唯一方式。可能更好的做法是在禁用状态下设置背景颜色以及文本。
按钮不一定是圆形的。有四个预定义的按钮(ContactsAdd、DetailDisclosure [即 > 箭头]、InfoDark 和 InfoLight [带有深色或浅色背景的信息图标])。还有一个自定义的 UIButtonType 类型。默认情况下,它不会为按钮添加边框,但允许创建有趣的按钮,其中 .png 文件可以是按钮的形状。所以如果你想有一个八边形的按钮,你将需要一个八边形的 .png 文件,然后编写以下代码:
btn.ButtonType = UIButtonType.Custom;
btn.SetBackgroundImage(UIImage.FromFile(“octagon.png”,UIControlState.Normal));
btn.SetBackgroundImage(UIImage.FromFile(“octagon-selected.png”,UIControlState.Highlighted));
摘要
如您从这次匆匆一瞥中可以看到,iOS 为您提供了大量丰富且多样化的对象,这些对象可以在 UI 中使用。在后续章节中,我们将看到这些对象如何被扩展,以及如何最大限度地利用它们。
第三章:视图和布局
在 iPhone 和 iPad 的层面上,视图可以被认为是你所看到的,但你看到的视图类型(在一定程度上)取决于你创建应用时选择的应用类型。
在本章中,我们将涵盖以下主题:
-
项目及其布局类型
-
确保你的设计适合所有 iOS 设备
-
UI 控件
项目类型选择
当你第一次决定创建 iOS 应用时,你会看到以下截图所示的视图:

应用类型及其视图类型
项目类型需要一些解释,以下表格中已提供:
| 项目类型 | 视图类型 |
|---|---|
| 主-详情 | 基于表格的布局。 |
| 单视图 | 最简单的视图形式。名称并不意味着你的应用只有一个页面,而是指视图不会通过(因此视图 1 可能有跳转到视图 2 的标签页。视图 2 可能是MT.D)。 |
| 标签视图 | 一种标准视图,屏幕底部有多个持久标签。视图会变化,但标签保持不变。 |
| OpenGL | 快速、响应式,用于高分辨率游戏。这里不会涉及,因为它超出了本书的范围。 |
在oleb.net/blog/2013/05/xcode-project-templates-difference/可以找到对项目类型的更完整解释。
iOS 布局
在处理 iPhone(和 iPad)用户界面的布局时,必须考虑许多因素;其中最重要的是屏幕的物理尺寸。虽然使用 Xcode 创建用户界面很容易,但与 Android 设备不同,iOS 并不真正自动调整大小。UI 部分是由于 iOS 中布局的创建方式。
画布模型
当我成长的时候,我在学校得到了一个“毛绒布”套件。对于那些不知道这是什么的人来说,毛绒布套件由一块大的背景布组成,你可以在上面粘贴其他布料;这使你能够制作出很多图片。为 iOS 设计也是如此。你可以将用户界面的任何部分拖放到主视图中并留下它。这就是 iOS 拥有其丰富性的部分原因;设计师需要创造,而不是对放置的位置有严格的规则。对于应用来说,UI 元素在屏幕上都有绝对的位置,而不是相对于其他对象。然而,这里有一个问题。
如果你增加屏幕尺寸,这些位置保持不变。所以,在 iPhone 4 上看起来不错的东西在 3G 上可能缺失部分,在 iPhone 5 上可能有间隙——不要开始考虑 iPad——因为视图通常位于屏幕中间,通常是挤压在一起的。
如何避免这些问题中的某些
最简单的方法是,当使用 Xcode 设计你的 UI 时,你需要在 UI 视图中勾选使用自动布局复选框。这会为你完成移动操作。

然而,这里的问题是您需要在每个视图中设置此使用自动布局,并且它也不支持 iPhone 3GS。然而,3GS 现在已经非常旧了,可能不值得费心去自动缩放。3GS 支持 iOS 6,但只能通过黑客手段。iOS 5 正接近其生命的终结(在撰写本文时)。
视图和视图控制器
它们听起来很相似,但它们并不相同。将视图控制器和视图视为网页的最简单方法是。一个典型的网页是从服务器提供的信息。内容可能是动态创建的(例如,从数据库查询),但对于用户来说,它只是数据。这将被视为视图。它上面有东西,但没有真正的用户交互。
视图控制器更接近于使用 ASP.net 或其他形式的语言构建的网页(例如 PHP)。该网站有一个按钮。按钮有一个事件,然后将其反馈到按钮背后的代码(称为处理代码)。使用 Xcode 创建的视图是网页,带有连接的源文件是服务器。
如前一章所述,UIViewController上的对象通过点击控件(以及按住Ctrl键)并将小部件拖到连接器入口窗口来与控件的代码(称为处理代码)连接。
其他视图
除了视图选择之外,还有许多其他视图可用,如下表所示:
| 视图名称 | 描述 |
|---|---|
| 活动指示器 视图 | 它是一个模态指示器,显示正在发生某事(活动)。这可能是指网页的加载或地图的渲染。 |
| 进度 视图 | 它显示了活动的进度——给用户一个更好的想法,了解某件事将花费多长时间。 |
| 集合 视图 | 它显示一组单元格(CollectionViewCells)。每个单元格都可以被定义。 |
| 可重用集合视图 | 可重用集合的工作方式如下:假设你有一组单元格,为了论证,它们占据了屏幕。在一个标准集合中,当单元格离开屏幕时,它们仍然被保留在集合中。虽然这使它们返回时的渲染更快,但它们会占用内存空间。可重用集合存储单元格的指针,并在返回屏幕时刷新——然后集合被重用。 |
| 表格视图 | 它将在第四章中介绍,控制器。 |
| 图片视图 | 可以将其视为一个图片视图。它没有点击事件,显示图片。然而,它可以在其中播放动画。 |
| 文本视图 | 它显示多行文本。可以是只读的,也可以是读写。 |
| 网页视图 | 它是一个用于渲染 HTML 的视图。HTML 文件可以存储在手机上或远程。 |
| 地图视图 | 它显示带有各种选项的地图。地图将在本书的后面部分介绍。 |
| 滚动视图 | 它是一个设计用来在屏幕上容纳比屏幕尺寸实际允许的更多内容的视图。 |
| 选择视图 | 它是一个用户可定义的选择器。 |
| 广告横幅视图 | 它是用于应用内广告的广告横幅栏。 |
| GLKit 视图 | 它用于 OpenGL-ES 渲染。 |
我将在本书的其他地方处理这里未涉及到的视图。与所有视图一样,这些视图也需要拖放到 Xcode 中的视图中,然后将其链接到主代码。
活动指示器和进度视图
UIActivityIndicatorView 类是一个实现起来非常简单的视图。
var aiActivity = new UIActivityIndicatorView()
{
ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray,HidesWhenStopped = true,
};
// start the indicator
aiActivity.StartAnimating();
// stop the indicator
aiActivity.StopAnimating();
UIProgressView 类稍微复杂一些,但仍然相当简单。它通过线程系统来跟踪指示器。让我们先设置一个:
var pvActivity = new UIProgressView()
{
BackgroundColor = UIColor.Red,Style = UIProgressViewStyle.Bar,
};
pvActivity.SetProgress(0, true);
接下来是构建线程例程。NSAutoreleasePool 类用作一个临时内存块,一旦大括号内的代码执行完毕,就会释放它。在以下代码中,它允许访问 InvokeOnMainThread 方法:
private void myTestRoutine()
{
int n = 5;
for (int i = 0; I < n; ++i)
{
Thread.Sleep(1000);
using (var pool = new NSAutoreleasePool())
{
InvokeOnMainThread(delegate
{
pvActivity.Progress = (float)(i + 1) / n;
});
}
}
}
最后,将其链接到进度视图:
Thread t = new Thread(myTestRoutine);
t.Start();
UIImageView
一个 UIImageView 类可以从 UIImage 中引入图像,而 UIImage 又可以从多个地方引入图像:
-
FromFile:在应用程序结构中存储的文件(例如,如果应用程序有一个名为Graphics的目录,则FromFile将指向Graphics|image.png)。 -
FromImage:从CoreImage文件加载。 -
FromResource:从Resources目录加载。这些是从应用程序内部嵌入的。 -
FromBundle:从主应用程序包中加载一个图像并将其缓存。 -
LoadFromData:从应用程序内部创建的图像。
要将文件加载到 UIImageView:
var myImage = new UIImageView()
{
ContentMode = UIViewContentMode.ScaleAspectFit;
Image = UIImage.FromFile("Graphics/helloXamarin.png"),
Frame = new RectangleF(new PointF(20, 20), new SizeF(100, 100)),
};
UIImageView 类也可以显示动画。进行此操作的主要前提是你应该有一系列要动画化的图像。在我的例子中,我有一个拖拉机的六张图片。车轮是唯一移动的部分。

要开始动画,使用以下代码:
UIImageView animation = new UIImageView()
{
Frame = new RectangleF(new PointF(20, 20), newSizeF(100,100)),
AnimationDuration = 0.5,
AnimationRepeatCount = 0,
Center = new PointF(animation.Center.X + 115,animation.Center.Y + 65),
};
animation.AnimationImages = new UIImage[]
{
UIImage.FromFile("Graphics/track-1.png"),UIImage.FromFile("Graphics/track-2.png"),UIImage.FromFile("Graphics/track-3.png"),UIImage.FromFile("Graphics/track-4.png"),UIImage.FromFile("Graphics/track-5.png"),UIImage.FromFile("Graphics/track-6.png")};
animation.StartAnimating();
要停止动画,使用以下命令:
animation.StopAnimating();

提示
动画在 第十章 动画 中介绍。

UICollectionView
你最可能看到的简单集合视图是一个图片库。将集合视图视为可以扩展的网格视图。每个集合视图由三个不同的项目组成;单元格、辅助视图(数据驱动视图)和装饰视图。
单元格
每个 UICollectionView 类将包含 UICollectionViewCells。
这些单元格有一个主要内容视图(您可以看到的东西,无论是图片还是应用内部导出的数据),围绕内容视图的是两个背景视图之一:正常或选中。如果内容部分不小于背景,则背景将不可见。
辅助视图
这些是展示与 UICollectionView 的每个分区相关的信息的视图。它们是数据驱动的。单元格来自数据源时,辅助视图展示该分区的数据(例如,主视图可能是书的封面,辅助视图可能是目录)。
装饰视图
这些不是生成数据,它们纯粹是为了美观目的。
数据源
UICollectionView 类通过 UICollectionViewDataSource 类获取其数据。这个类提供信息,例如单元格(从 GetCell 获取),辅助视图(从 GetViewForSupplementaryElement 获取),分区数量(从 NumberOfSections 获取,如果没有实现则为 1),以及每个分区的项目数量(从 GetItemsCount 获取)。
单元重用
UICollectionView 类将只调用数据源来获取屏幕上项目的单元格。不在屏幕上的项目将被放入队列以供重用。
UIWebView
UIWebView 类将您的设备有效地转换成一个具有 JavaScript 功能以及常规网络功能的网络浏览器,例如,当扩展到 UITextField 时,可以后退、前进以及在文本字段中输入 URL。
加载网页很简单,如下面的代码所示:
var web = new UIWebView();
NSUrl url = new NSUrl("http://www.bbc.co.uk");
web.LoadRequest(new NSUrlRequest(url));
在网页加载中,有一些因素需要记住。第一个因素是它通常是一个异步任务;换句话说,某些部分在完成之前就已经完成,并且任务完成之前应用程序流程可能会返回主线程。第二个因素是速度。现在我不会关注第二个因素。
-
为了克服异步任务引起的问题,可以使用以下多个事件:
-
LoadStarted -
LoadFinished -
LoadError -
布尔值
IsLoadingIsLoading布尔值是一个标志,可以在任何时刻检查以确定是否正在加载(true)或已完成加载(false)。例如:
web.LoadStarted += delegate { // start ActivityIndicator here, stops anything else happening }; web.LoadFinished += delegate { // stop ActivityIndicator }; web.LoadError += delegate { UIAlertView error message };
-
-
为了前进和后退,可以使用以下方法:
-
web.GoBack() -
web.GoForward()
这些方法有一个简单的布尔测试
-
web.CanGoBack -
web.CanGoForward
-
-
为了刷新网页,可以使用以下方法:
web.Reload(); -
还可以包括缩放支持和页面适配
web.ScalesPageToFit = true;
地图视图

注意
这些是 iOS 地图,而不是 Google 地图(苹果从 iOS 6 开始从使用 Google 地图转向使用自己的地图)。

在 iOS 中,地图需要使用 CoreLocations 和 MapKit。映射和位置服务将在第十二章外围设备中处理,外围设备。
UIScrollView
有时候屏幕上会显示过多的信息(例如,如果你正在动态生成内容或创建某种形式的绘图应用程序)。在这种情况下,可以使用UIScrollView来确保用户可以看到所有内容。
视图(当与PageEnabled = true;结合使用时)通过计算页面大小来工作。
假设滚动视图已经在 Xcode 中创建(并命名为scrollView),代码如下:
private List<UIView> viewPages = new List<UIView>();
private int numPages = 4;
private float pad = 10, height =400, width = 300;
public override void ViewDidLoad()
{
scrollView.Frame = View.Frame;
scrollView.PagingEnabled = true;
scrollview.ContentSize = new SizeF(numPages * width + pad +
2 * pad * (numPages – 1), View.Frame.Height);
我们现在有一个有效的页面系统。问题是追踪用户在哪个页面。这可以通过UIPageControl来处理。在先前的例子中,跟踪将如下进行:
private UIPageControl pageNumber;
在ViewDidLoad()方法中,跟踪将如下进行:
scrollView.Scrolled += delegate {
pageNumber.CurrentPage = (int)Math.Round(scrollView.X / width);
};
AdBannerView
你在许多不同的应用程序中都见过这些。这些是顶部广告栏,可以宣传从汽车到快餐等各种商品,通常针对应用程序类型(例如,如果你设计一个提供汽车性能统计的应用程序,广告横幅通常会获得汽车杂志、汽车游戏等广告)。这仅仅是一种为应用程序开发者生成收入的方式。广告支持是AdSupport命名空间的一部分。
考虑这些视图的一种简单方式是将其视为一种带有多个关键事件的网页视图:
-
AdLoaded: 在广告加载之前,视图不会显示。这使得用户体验不那么侵扰。 -
FailedToReceive: 广告下载失败。
实现包含多个视图控制器(View Controllers)的视图
实现一个包含多个视图控制器(View Controller)的视图足够简单。比如说我们有两个视图。一个占据屏幕顶部 130 像素的空间;另一个高度为 250 像素。通过将第二个视图作为子视图添加到第一个视图上来实现。
secondView v2 = new secondView();
v2.Frame = new RectangleF(new PointF(0, 130), newSizeF(320, 250));
View.AddSubview(v2);
来自两个视图控制器的所有事件仍然会像通常一样工作(比如说第二个视图是一个网页视图,而第一个有一些按钮,按钮仍然会响应触摸事件,网页视图仍然会响应网络事件)。
然而,当视图 1(父视图)想要对视图 2(子视图)的事件做出反应时,问题就出现了。这也不是那么困难。实际上,在处理第二章中的MT.D类时,用户界面,父视图通过在另一个类中重写Selected事件并使用代理(adelegate)来处理SubView事件(如下面的代码示例所示)。
public class PickerChangedEventArgs : EventArgs
{
public string SelectedValue { get; set; }
}
在处理UIPickerViewModel的类中,必须重写Selected方法以支持PickerChangedEventArgs,如下面的代码所示:
public override void Selected(UIPickerView picker, int row,int component)
{
if (PickerChanged != null)
{
PickerChanged(this, new PickerChangedEventArgs {
SelectedValue = myValues[row] });
}
}
最后,在主应用程序中,调用UIPickerView类的代码需要在新的事件上触发。
myPickerViewModel.PickerChanged += (object sender,PickerChangedEventArgs e) =>
{
myElement.Value = e.SelectedValue;
};
摘要
在拥有所有这些视图和视图类型的情况下,iPhone 能够适应性地显示各种形式的数据,尽管其复杂,但这也并不奇怪;实际上,它是一个足够简单的系统,可以用来编码。
在下一章中,我们将更深入地探讨控制器。
第四章。控制器
在 iOS 应用程序开发中,必须有一些形式的控制器,因为 iOS 应用程序开发框架实现了模型-视图-控制器(MVC)设计模式。iOS 中的控制器分为两类之一:表格或非表格。虽然我在讨论MonoTouch.Dialog时提到了TableView方法,但我实际上并没有给出很多细节,考虑到它们在 iOS 中的重要性,这至少是相当不诚实的!
在本章中,我们将涵盖以下主题:
-
UITableView 和 UITableViewCell
-
UINavigationController
-
UITabBar
-
UIPageControl
-
GLKit 视图控制器
UITableView 和 UITableViewCell
TableView 方法是 iOS 中的主要工作马。如果你考虑像 Facebook、电子邮件、消息、Twitter 以及许多其他应用程序,它们通过基于列表的界面来显示数据。它可能包含不同的属性(如图像、标题文本、日期和时间、颜色以及一系列其他小部件),但最终,它是一个数据表。
当设置表格时,它可以采用两种格式:纯文本和分组。分组视图在以下图像中展示:

纯文本视图可以如下查看:

分组方法更适合用户名/密码或设置风格的信息,而纯文本视图最适合纯列表信息(如城市名称、化学元素或推文)。
由于表格需要数据来传播视图,因此通常还会定义一个代理(或源)类。
前一截图中的每个元素(如Brea、Burlingame和Canoga Park)都使用了UITableViewCell。数据可以在这些单元格中输入或编辑。
创建只读表格
在 Xcode 中,我创建了一个表格,并在其中创建另一个表格并添加了一些英格兰超级联赛球队。最简单的方法是通过列表来实现。
private List<string> premTeams;
public override void ViewDidLoad() {
base.ViewDidLoad();
premTeams = new List<string>(){"Liverpool","Everton","Arsenal","Swansea","Cardiff","Newcastle"};
tvTableView.Source = new myViewSource(premTeams);
}
private class myViewSource : UITableViewSource {
private List<string>dupPremTeams;
public myViewSource(List<string>prems) {
dupPremTeams = prems;
}
public override int RowsInSection(UITableView table,int section) {
return dupPremTeams.Count;
}
public override UITableViewCell GetCell(UITableView tableView,NSIndexPath index) {
UITableViewCell theCell = new UITableViewCell();
theCell.TextLabel.Text = dupPremTeams[index.Row];
return theCell;
}
}
这段简单的代码产生了以下图像。代码所做的只是从 Xcode 中创建的视图显示数据。这是表格最简单的形式:

利物浦已被选中(以蓝色显示),但我们并没有对它做任何事情。UITableView类的美妙之处在于其背后的力量。当一个单元格被选中时,它可以用来导航到其他地方,在其中输入数据,或者只是简单地在一个选中的单元格值旁边放置一个勾选标记!
要使代码能够对单元格的选择做出反应,必须在myViewSource中添加另一个重写,即:
public override void RowSelected(UITableView tv,NSIndexPath index)
{}
在 RowSelected 之上,UITableView 控件提供了一个 DetailDisclosureButton——此按钮允许在单元格内执行多个操作。(比如说,我们有一个元素周期表,旁边有图片和书图标:点击名称会显示原子详情和元素的图片,而书图标会显示历史。)要在 GetCell 方法中启用此功能,请考虑以下代码行:
theCell.Accessory = UITableViewAccessory.DetailDisclosureButton;
可用的其他 UITableViewAccessory 选项包括 Checkmark(在表格中的单选或多选)、Disclosure(一个灰色箭头,表示触摸单元格会导致导航)和 DetailDisclosure(一个白色箭头)。UITableViewAccessory 还有一个 None 选项,它有一个放置位置但没有内容。
UITableViewCell
默认情况下,TableViewCell 看起来很简洁。与 iOS 中的所有内容一样,单元格可以通过多种方式修改。单元格始终以 UITableViewCellStyle.Default 开始。这支持一个带有可选左侧图片的 TextLabel 组件。其他样式包括:
UITableViewCellStyle |
它的作用 |
|---|---|
Subtitle |
提供两个左对齐字段:TextLabel 和 DetailTextLabel。可以可选地将图片添加到这两个字段的左侧。DetailTextLabel 为灰色,字体大小比 TextLabel 小。 |
Value1 |
在这个例子中,TextLabel 是右对齐且为蓝色。DetailTextLabel 是黑色且左对齐。没有可选的图片可用。 |
Value2 |
在这个例子中,TextLabel 是左对齐且为黑色。DetailTextLabel 是右对齐且为蓝色。没有可选的图片可用。 |
表格中的可重用单元格
与 CollectionView 类似,TableView 方法也重用单元格。重用的目的是为了防止应用程序耗尽内存并减慢速度。为此,默认情况下,任何一次只显示 10 个单元格(尽管这个数字可能因单元格和 UITableView 的高度而有所不同)。
为了启用这种重用,我示例中的单元格需要使用不同的重载之一进行实例化。
UITableViewCell theCell = new UITableViewCell(UITableViewStyle.Default, "reuseID");
在前面的代码中,reuseID 是表格用来识别要重用单元格的标识符。
为了完全展示这一点,我扩展了我们原始示例中的团队列表,并修改了 GetCell 方法。所有其他代码保持不变。
premTeams=newList<string>() {"Arsenal", "Aston Villa","Cardiff City", "Chelsea","Crystal Palace", "Everton","Fulham", "Hull City", "Liverpool", "Man City", "Man United","Newcastle", "Norwich", "Southampton", "Stoke City","Sunderland", "Swansea", "Tottenham", "WBA", "West Ham"
};
private class myViewSource : UITableViewSource {
public override UITableViewCell GetCell(UITableView tableView,NSIndexPath index) {
UITableViewCell theCell = tableView.DequeueReusableCell("reuseID");
if (theCell == null) {
theCell = new UITableViewCell(UITableViewCellStyle.Default, "reuseID");
// nothing to reuse, so create a cell}
theCell.TextLabel.Text = dupPremTeams[index.Row];
return theCell;
}
}
部分(Sections)和行(Rows)
使用 UITableView 可以简化分组数据的处理,因为它们使用 Section 和 Row 系统来识别单元格(这些是 NSIndexPath 的两个参数)。
TableView 上的索引
通过重写 SectionIndexTitles,可以在表格的右侧添加一个索引表。点击索引表中的项目将使表格跳转到该值。
public override string[] SectionIndexTitles(UITableView tableView)
{
// add the index labels you want [letters are good]
return sectionTitles.ToArray();
}
在 Xcode 中或外部自定义 UITableViewCell 组件。说实话,最简单的方法是在 Xcode 中进行。将单元格拖动到空白视图中,并添加您想要其中的小部件。
使用 UITableView 进行导航
UINavigationController提供了带有表格的导航。简单来说,NavigationController允许你通过屏幕顶部的标准返回按钮在表视图之间移动。可以使用 Xcode 或纯代码设置导航控制器。
在代码中
在AppDelegate.cs文件中设置NavigationController。
UIWindow window = new UIWindow(UIScreen.MainScreen.Bounds);
var viewController = new mainController();
UINavigationController rootNavigationController =new UINavigationController();
rootNavigationController.PushViewController(viewController,false);
window.RootViewController = rootNavigationController;
window.MakeKeyAndVisible();
这将导航栏放置在屏幕顶部。当显示新视图时,返回按钮变得可见。可以使用以下代码行隐藏返回按钮:
NavigationItem.HidesBackButton = true;
可以使用以下方法隐藏NavigationController:
NavigationController.NavigationBarHidden = true;
这里的问题是,当你向前到一个视图并返回时,栏将不再隐藏。必须调用的代码是ViewWillAppear方法。
使用 Xcode
在 Xcode 中使用NavigationController有几种方法。然而,它们可能很难使用。

导航控制器(图像 B)创建一个视图,其中控制器栏位于顶部。它有一个大优点,即既有导航部分,也有用于加载视图控制器区域(图像 A)。要使用它,只需创建一个视图控制器,就像你通常做的那样。在RootViewController源中,以下代码将其他ViewController加载到RootViewController中:
var myViewController = new MyViewController();
myViewController = "My View";
// sets the title and gives a back button
NavigationController.PushViewController(myViewController, true);
每个导航控制器都需要一个带有可选导航控制器按钮的栏。

标题栏也可以放置到典型视图中,并以标准方式连接。导航项并不像它看起来那样。从项的外观来看,人们可能会认为按钮可以用于类似返回按钮、菜单按钮或可以放置在标题栏上的按钮。并不是这样!为此,需要直接在标题栏上添加BarButtonItem,并将导航项连接到栏,如下所示:

将Navigation Item(图像 D)拖到栏上,使其位于视图图标(图像 C)下方。将Bar Button Item拖到视图上已经存在的标题栏上。这将为你提供一个带有左侧按钮的栏(假设你将其放置在左侧)。下一步是将Navigation Item连接到栏上的按钮。为此,按下Shift按钮并将Navigation Item拖到Bar Button Item上。完成此操作后,你将看到一个菜单,显示该项代表的内容,如下所示:

选择backBarButtonItem将得到返回按钮。如果你选择leftBarButtonItem或rightBarButtonItem,这些将提供可用于其他目的的按钮(例如地址簿的图标或菜单)。要检查是否已建立连接,可以在右侧的输出连接上确认:

要在代码中访问backBarButtonItem,可以使用以下类似的方法:
NavigationItem.BackBarButtonItem.Clicked += delegate {
NavigationController.PopViewControllerAnimated(true);
// sends back to the previous view on the stack
};
使用 UITableView 进行导航
到目前为止,我将假设视图已经有一个有效的表,其中包含一些数据。UITableView 的一个关键点是用户可以选择一个表格单元格,并且该单元格可以被移动到另一个视图中。
对于以下代码行:
RowSelected(UITableView tableView, NSIndexPath indexPath)
要启动一个新的 ViewController,我们需要做以下事情:
var newController = new myNextTableViewController(ref_to_data,rows[indexPath.Row]);
newController.Title = rows[indexPath.Row];
parent_controller.NavigationController.PushViewController(myController, true);
tableView.DeselectRow(indexPath, true);
这将启动一个新的视图,当选择返回按钮时,视图重新出现。backBarButtonItem 标题将显示启动新视图的视图的标题。DeselectRow 从视图中移除蓝色选择颜色。如果按下返回按钮时没有调用它,选择仍然会显示为选中状态。
返回到 RootView
要返回到视图的顶层,使用以下方法:
NavigationController.PopToRootViewController(true);
标签栏
另一种简单的导航方法是通过 UITabBar。通常,这些条形位于屏幕底部,但没有任何阻止视图在屏幕顶部也拥有 UITabBar 的东西。

将标签栏放置在视图中与将任何其他视图放置在屏幕上相同;将其拖到屏幕上。默认情况下,TabBar 中添加了两个 TabBarItem 项目。您可以通过将 Tab Bar Item 小部件拖到条上添加更多。
每个 TabBarItem 都有一个 Title 和 Image 属性可以设置;然而,这些按钮必须在 Xcode 中设置为 Custom 类型。
两个 Space Bar Button Item 组件允许在视图之间添加按钮项,并保持它们之间有空间。Fixed 之间有有限的空间,而 Flexible 允许添加更多按钮,但保持它们等距。
TabBar 组件与其他导航机制不同。虽然 TabBarItem 当然可以启动一个新的视图,但更常见的是始终使用相同的视图,但新的视图控制器被加载到导航栏(或屏幕顶部的任何内容)和底部 TabBarItem 之间的空白框架中。
如果一个包含 TabBar 的视图正在动态生成视图并在屏幕上显示,TabBarItem 组件也是很好的。
在代码中处理标签栏
在您的代码中,需要处理事件的是 Tab Bar 而不是 Tab Bar Item。Tab Bar Item 组件仍然需要连接,因此它们可以在主控制器源中处理。
tabBar.ItemSelected += TabBarSelected;
确定哪个 Tab Bar Item 被选中的可能最简单的方法是在每个项目上设置 Tag 属性(无论是在 Xcode 中还是在控制器源中)。在 TabBarSelected 中,可以找到正确的 TabBarItem。
private void TabBarSelected(object s, EventArgs e)
{
UITabBar item = (UITabBar)s;
UITabBarItem item = tb.SelectedItem;
switch(item.Tag)
{
// do something;
}
}
页面控制
UIPageControl 方法是一个方便的机制,用于标记我们在单个视图中有多页。它通常与 UIScrollView 一起使用,以显示 UIScrollView 中的页面索引。

它们也可以用于导航。当你点击页面左侧或右侧时,会抛出一个事件以实现页面移动。点的数量表示页数。这个数字可以在代码中随时更改。
GLKit
GLKit视图使应用程序能够使用嵌入式系统的 OpenGL(OpenGL ES)。动画和图形不在此书的范围之内,但有许多 Xamarin.iOS 示例可用,可以演示如何使用 OpenGL ES。
摘要
本章已向你介绍了两种最常用的导航和视图形式的基础知识。虽然UITableView无疑非常强大,但当你有能力使用更简单的MonoTouch.Dialog类时,它就显得有些繁琐。
第五章。UI 控件
用以引用埃德蒙·布莱克阿德的话:“没有控件的 UI 就像没有笔尖的铅笔——毫无意义。”
控件的种类虽然不是很大,但在你可以用它们做什么方面非常灵活。所有控件都可以在 Xcode 中创建,或者在代码中动态创建。
在本章中,我们将涵盖以下主题:
-
控件和部件
-
控件选择
-
控件定制
-
控件参考(Android 和 iOS 交叉参考)
控件和部件
当我们提到小部件时,我将其与 UI 控件互换使用。在旧术语中,小部件代表 WInDow gadGET。iOS 设备上的屏幕被归类为窗口,因此将屏幕上的任何内容称为小部件是合理的。
UI 控件
UI 控件直接从 Xcode 中可用。对于在代码中创建,控件名称前缀为 UI,并删除所有/任何空格(除了 圆角矩形按钮,它只是 UIButton)。固定空间栏按钮项和弹性空间栏按钮项通过 UIBarButtonItem 访问。

控件选择
在上一张截图中所显示的大多数控件对于它们的功能来说都很明显,而且足够简单,只需添加并连接到 UI 即可。然而,有一些控件需要与其他控件区别对待,最值得注意的是 UIButton 和 UIStepper。UIButton 类在它能做什么方面非常灵活,而 UIStepper 则真正需要作为一个 动作 而不是 出口 来使用。这意味着它们必须以不同的方式处理。
UIButton
UIImageView 没有附加事件。因此,如果你需要一个可以点击的图像,它们可以被放置在 UIButton 类上。
var r = new UIButton();
r.ImageView = UIImage.FromFile("path/toimage.png");
完全可以在按钮上添加文本以及图像,只需将 UILabel 添加到按钮上,但这是一个两步的过程。重要的是要记住,当将任何内容放置在另一个视图上时,必须考虑父视图的大小,如下面的代码所示:
var r = new UIButton();
r.Frame = new RectangleF(0, 0, 100f, 100f);// button 100x100 at 0, 0
var i = new UIImageView(new RectangleF(15f, 2f, 70f, 70f));
i.Image = UIImage.FromFile("path/toimage.png")Scale(new SizeF(70f, 70f));
var l = new UILabel(new RectangleF(2f, 78f, 96f, 20f));
l.Text = "Hello world";
r.AddSubview(i);
r.AddSubview(l);
你可以向按钮添加任意多的图像和标签。按钮也可以添加背景图像,如下面的代码所示:
r.SetBackgroundImage(UIImage.FromFile("path/toimage.png"),UIControlState.Normal);
或者,背景可以添加渐变填充,如下面的代码所示:
var gradient = new CAGradientLayer();
gradient.Colors = new MonoTouch.CoreGraphics.CGColor[]
{
UIColor.FromRGB(115, 181, 216).CGColor,
UIColor.FromRGB(35, 101, 136).CGColor
};
gradient.Locations = new NSNumber[]
{
.5f,
1f
};
gradient.Frame = r.Layer.Bounds;
r.Layer.AddSublayer(gradient);
r.Layer.MasksToBounds = true;
假设,我们之前添加的 UILabel 类对于即将到来的文本来说太小了(它不再是Hello world,而是“我爱喝热咖啡”),那么标签内的字体大小需要改变。调整大小是通过创建一个边界框(textSize),并通过设置字体大小使其适应边界框的高度——而不是宽度——来实现的。
RectangleF textSize = new RectangleF(i.X, i.Y, i.Width, i.Height);
l.Font = UIFont.SystemFontOfSize(textSize.Height);
l.Text = "I love drinking hot coffee";
按钮不必是圆形的——也可以应用自定义形状。让我们看看以下代码中的圆形按钮:
r.Frame = CGPath.EllipseFromRect(new RectangleF(135f, 180f, 40f, 40f));
//width and height should be same value
r.ClipsToBounds = true;
r.Layer.CornerRadius = 20;//half of the width
r.Layer.BorderColor = UIColor.Red.CGColor;
r.Layer.BorderWidth = 2.0f;
为了使这可行,必须包含 Monotouch.CoreGraphics 命名空间。
还有其他类型的按钮可供选择(ContactAdd、Custom、DetailDisclosure、InfoDark、InfoLight、RoundedRect 和 System)。虽然 RoundedRect 是最常用的形式,但其他类型可以在 Xcode 中创建,也可以在您的应用程序代码中创建。
var myButton = new UIButton(UIButtonType.Custom);
以下代码创建了一个类型为 Custom 的按钮。其他类型的按钮外观如下所示:

从左到右(系统、自定义、详细披露、信息浅色、信息深色、联系添加)
UIStepper
UISteppers 类需要在连接器中定义为输出和动作(选择 Event 为 Value Changed)。以下截图显示了典型的动作:

public override void ViewDidLoad()
{
base.ViewDidLoad();
uiStepper.MinimumValue = 0;
uiStepper.MaximumValue = 10;
uiStepper.AutoRepeat = true;
lblCounter.Text = uiStepper.Value.ToString();
}
partial void stepper(NSObject sender)
{
UIStepper step = (UIStepper)sender;
lblCounter.Text = step.Value.ToString();
}
当点击 Stepper 控件时,会调用 partial void 方法,该方法更新计数器,如前述代码所示。如果您只有一个而没有另一个,当应用程序到达具有 UIStepper 类的视图控制器时,应用程序将崩溃。
其他控件
以下表格将为您提供每个控件的功能和任何使用控件时的注意事项的指示性列表:
| 控件 | 用于 | 注意事项 |
|---|---|---|
Label |
简单标签 | 不响应用户触摸事件 |
SegmentedControl |
单个视图中多个按钮。常用于 MapViews 中的不同类型地图 |
|
TextField |
文本输入 | 为密码设置 SecureTextEntry = true |
Slider |
选择音量或颜色 | 使用浮点值 |
PageControl |
显示 scrollview 区域中有多少 页面 的简单方法 |
|
DatePicker |
日期选择器 | 也可以用于时间 |
NavigationBar |
屏幕顶部的导航栏 | 需要一个导航项 |
NavigationItem |
导航栏的导航项 | 需要添加导航控制器以在 Xcode 中定义项类型 |
SearchBar |
搜索栏 | 需要定义动作和输出 |
ToolBar |
用于添加按钮的栏,作为工具 | 用于事件,在处理程序中使用 Item 为 BarButtonItem。 |
BarButtonItem |
用于工具栏的按钮。按钮上已包含图像和标签 | 不要从该事件中捕获;使用工具栏 |
Fixed/FlexibleBarButtonItem |
在工具栏按钮之间提供空间 | |
TabBar |
用于 tab 视图 | 使用 Toolbar 控制器来定义哪个 nib 文件,按下标签时将调用 |
TabBarItem |
用于 TabBar 的按钮,类似于 BarButtonIt em |
比较 Android 和 iOS UI 控件
以下表格是一个比较列表,旨在为希望移植到或从 Android 移植的人提供帮助:
| Android | iOS | Android 响应事件 | iOS 响应事件 |
|---|---|---|---|
Button |
UIButton |
是 | 是 |
Text |
UILabel |
是 | 否 |
ListView |
TableView |
是 | 是 |
TableViewCell |
是 | ||
CheckBox |
Switch |
是 | 是 |
CheckedTextView |
带有 UILabel 的 Switch |
是 | 是 |
ProgressBar |
ActivityIndicatorView |
是 | 否 |
RadioButton |
MTD.RadioElement |
是 | 是 |
RadioGroup |
MTD.RadioGroup |
是 | 否 |
SeekBar |
Slider |
是 | 是 |
TextView (多种类型) |
TextField |
是 | 是 |
FrameLayout, LinearLayout, RelativeLayout, TableLayout |
是 | ||
GridView |
ComponentView |
是 | 是 |
ScrollView |
Scrollview |
是 | 是 |
SlidingDrawer |
ActionBar |
是 | |
TabHost |
TabBar |
否 | 是 |
TabWidget |
TabBarItem |
是 | 否 |
WebView |
Webview |
是 | 是 |
Gallery |
ImageGallery |
是 | 是 |
ImageButton |
Button |
是 | 是 |
ImageView |
ImageView |
是 | 否 |
MediaController |
AudioViewController |
是 | 是 |
VideoView |
VideoViewController |
是 | 是 |
DatePicker |
DatePicker |
是 | 是 |
TimePicker |
DatePicker |
是 | 是 |
DialerFilter |
UIKeyboard |
是 | 是 |
GestureOverlayView |
Gestures (约) |
是 | 是 |
SurfaceView |
View |
是 | 是 |
TwoLineListItem |
MTD.MultipleLineElement |
是 | 是 |
View |
View |
是 | 是 |
Zoom 按钮 |
Stepper |
是 | 是 |
Zoom 控件 |
SegmentedControl |
是 | 是 |
尽管 Android 上有其他在 iOS 上没有,反之亦然,但这些是最常见的。
摘要
在 iOS 设备上,有丰富的 UI 控件可以玩转,并且几乎可以定制任何东西,难怪人们喜欢使用它们;它们可以看起来很棒!
第六章:事件
事件是任何 iOS 应用程序的一个基本方面。实际上,没有它们,你的手机将像一块无用的塑料一样静静地待在那里!每当发生某事时,都会引发一个事件。对于开发者来说,事件就是一切。
在本章中,我们将讨论以下主题:
-
处理事件
-
事件引用
-
控制事件引用
处理事件
事件处理使用以下类型之一:
-
代表
-
处理程序
代表
你可以将代表事件视为内联事件。它们可以是匿名的,或者可以像以下这样使用事件:
-
当代表是匿名的,代码如下:
var uiButton = new UIButton(); uiButton.TouchUpInside += delegate {…}; -
当代表使用事件时,代码如下:
var uiButton = new UIButton(); uiButton.TouchUpInside +=(object sender, EventArgs e) => {…};
如果按钮没有需要发送者或事件的任何内容,可以使用匿名事件。
Xamarin.iOS 在未使用处理程序时将所有事件委托给使用EventArgs e。
将事件附加到多个控件
为了方便起见,我将假设一个视图控制器有四个按钮。为每个按钮分配它自己的事件处理程序没有问题,但如果一个事件可以处理多个事件,那么这将是内存的浪费。然而,出于理智的考虑,应该限制这种情况,以便TouchUpInside事件一起处理,而不是在接受TouchUpInside事件的视图(或视图控制器)上处理所有事件。
一个简单的解决方案是为所有按钮使用单个事件。然而,问题在于如何在事件处理程序本身中识别和操作正确的按钮。
可能最简单的方法是使用数字设置Tag属性,如下面的代码所示:
private void setup()
{
UIButton btn1 = new UIButton()
{
// set up the properties
Tag = 1,
};
UIButton btn2 = new UIButton()
{
// set up the properties
Tag = 2,
};
UIButton btn3 = new UIButton()
{
// set up the properties
Tag = 3,
};
btn1.TouchUpInside += HandleButtonPressedEvent;
btn2.TouchUpInside += HandleButtonPressedEvent;
btn3.TouchUpInside += HandleButtonPressedEvent;
}
private void HandleButtonPressedEvent(object sender, EventArgs e)
{
UIButton theButton = (UIButton)sender;
switch(theButton.Tag)
{
// do what is needed
}
}
同步与异步事件处理
事件有两种流程方法:同步和异步。它们之间的区别与理解它们的用途一样重要。为了理解这些区别,你需要想象两个人在散步。
同步行走
两个人一起走,来到路上的一盏交通灯前;他们都停下来,当交通灯变绿时,他们一起出发。散步结束时,他们仍然在一起,坐在阳光下喝酒。
异步行走
两个人一起出发去酒吧,但当他们来到交通灯前时,其中一个人停下来等待信号,而另一个人过马路。一旦信号允许安全过马路,停下来的人过马路并继续前进。刚刚过马路的人因为乱穿马路被警察拦下。无法保证这对夫妇会同时到达酒吧;这取决于其他使他们减速的因素。
在编程环境中
考虑一个简单的信使应用程序,它由两部分组成:获取消息和显示它们。同步事件是用于调用地址视图控制器。在这里,点击按钮可以被视为两个人一起散步并停下来。
异步部分是消息的下载;在这里,慢的部分是从服务器获取消息。如果这是通过同步事件执行的,那么将请求消息,而线程会在从服务器获取返回请求时冻结。当这个过程中发生时,UI 被创建,但没有数据如何创建 UI?简而言之,这是不可能的,应用会崩溃。由于它是异步执行的,UI 的创建直到服务器发送了所有内容才会发生。当发生这种情况时,下一个阶段可以执行。
例如,考虑以下代码(这段代码直接取自 Xamarin ZXing 组件,并通过创意共享许可证发布):
buttonCustomScan.TouchUpInside += async (sender, e) =>
{
//Create an instance of our custom overlay
customOverlay = new CustomOverlayView();
//Wireup the buttons from our custom overlay
customOverlay.ButtonTorch.TouchUpInside += delegate {
scanner.ToggleTorch();
};
customOverlay.ButtonCancel.TouchUpInside += delegate
{
scanner.Cancel();
};
//Tell our scanner to use our custom overlay
scanner.UseCustomOverlay = true;
scanner.CustomOverlay = customOverlay;
var result = await scanner.Scan();
HandleScanResult(result);
};
代码的重要部分是这一行 var result = await scanner.Scan();,直到它返回,HandleScanResult 方法将不会被调用。在 async 处理器内部的 TouchUpInside 事件是同步的,一旦点击,它们要么终止处理器,要么启动另一段代码——它们都立即执行。
由于 Mono 3 对其的支持,Xamarin.iOS(以及 Xamarin.Android)支持异步调用。
事件和控件参考
并非每个小部件默认都附加有事件(尽管你可以编写事件并将其添加到小部件中),例如,UIImageView 没有任何附加的事件。
iOS 非常重视其触摸系统,这在分配给触摸的事件数量上得到了体现。除非小部件没有附加事件,否则以下表格适用于所有小部件:
| 事件名称 | 执行动作及时机 |
|---|---|
TouchCancel |
当取消控制器的当前触摸时被调用(任何处理 TouchCancel 事件的控制器)。 |
TouchDown |
这是小部件上的触摸事件。 |
TouchDownRepeat |
这是一个重复的触摸事件。要触发此事件,UITouch 的 tapCount 值必须大于 1。 |
TouchDragEnter |
当手指被拖入控制范围时被调用。 |
TouchDragExit |
当手指被拖出控制范围时被调用。 |
TouchDragInside |
当手指被拖入控制范围时被调用。 |
TouchDragOutside |
当手指被拖到控制范围外时被调用。 |
TouchUpInside |
当手指在控制范围内时被调用。 |
TouchUpOutside |
当手指在控制范围外时被调用。 |
其他重要的控制事件
下表中列出的事件可以在 iOS 中的许多控件上找到:
| 事件名称 | 执行动作及时机 |
|---|---|
ValueChanged |
当控制器的值发生变化时(例如,UITextField 的文本被更改),允许访问控制器上的更改值。 |
EditingDidBegin |
当在UITextField类中开始编辑会话时触发。 |
EditingChanged |
当UITextField类的值发生变化时发出。 |
EditingDidEnd |
当通过离开边界结束UITextField类的编辑会话时触发。 |
EditingDidEndOnExit |
当通过触摸结束UITextField类的编辑会话时触发。 |
AllTouchEvents |
它拦截所有触摸事件。 |
AllEditingEvents |
它拦截所有编辑事件。 |
AllEvents |
它拦截所有事件(包括系统事件)。 |
TouchesBegan、Moved、Ended和Cancelled以及手势识别器事件将在下一章中介绍。
AVAudioPlayer 和 AVRecordClass
播放和录制音频和视频是 iOS 体验的关键功能。以下表格列出了您需要挂钩以实现流畅播放和录制的事件:
| 事件名称 | 功能及触发时间 |
|---|---|
BeginInterruption |
当音频播放器被中断(例如,接听电话)时触发。 |
DecodeError |
当文件无法解码时触发。 |
EndInterruption |
当中断完成时触发。 |
FinishedPlaying |
当文件播放完毕时触发。 |
AVAudioSession
音频会话是播放音频或视频的物理行为。许多事件可以中断正在播放的视频或音频。以下表格列出了您需要了解的事件:
| 事件名称 | 功能及触发时间 |
|---|---|
BeginInterruption |
当音频会话被中断时触发。 |
CategoryChanged |
当音频会话类别发生变化时触发。 |
EndInterruption |
当音频会话的中断完成时触发。 |
InputAvailabilityChanged |
当设备上音频输入的可用性发生变化时触发。 |
InputChannelsChanged |
当输入通道数量发生变化时触发。 |
OutputChannelsChanged |
当输出通道数量发生变化时触发。 |
SampleRateChanged |
当采样率改变时触发。 |
ABAddressBook
iOS 中的地址簿可以从地址簿功能内部和外部进行修改。
| 事件名称 | 功能及触发时间 |
|---|---|
ExternalChange |
当地址簿外部(例如,更改地址簿的用户应用程序)更改地址簿内部的内容时触发。 |
ABNewPersonViewController
与所有视图一样,视图控制器用于放置 UI。地址簿NewPersonViewController也不例外。
| 事件名称 | 功能及触发时间 |
|---|---|
NewPersonComplete |
当用户点击保存或取消按钮时调用。如果点击保存,则新联系人将被保存到地址簿数据库中。 |
ABPeoplePickerNavigationController
ABPeoplePickerNavigationController的行为与普通导航控制器相同,但增加了一些功能,如下表所示:
| 事件名称 | 执行的操作及时间 |
|---|---|
Cancelled |
当点击取消按钮时发送。 |
PerformAction |
当在人员选择器上选择一个对象时触发。 |
SelectPerson |
当选择一个人时触发。 |
ABPersonViewController
人员视图控制器显示所选人员,并附加一个事件。
| 事件名称 | 执行的操作及时间 |
|---|---|
PerformDefaultAction |
当用户在“人员视图控制器”中选择一个人的属性值时发送。 |
ABUnknownPersonViewController
未知人员视图 控制器在人员被接受到ABAddressBook类之前显示人员信息。其事件列在以下表中:
| 事件名称 | 执行的操作及时间 |
|---|---|
PerformDefaultAction |
当用户在“人员视图控制器”中选择一个人的属性值时发送。 |
PersonCreated |
当用户点击保存或取消按钮时调用。如果点击保存,则新联系人将保存到地址簿数据库中。 |
AudioConverter
AudioConverter类用于转换音频格式。
| 事件名称 | 执行的操作及时间 |
|---|---|
InputData |
当通过端口提供数据作为输入时触发。 |
AudioSession
AudioSession类类似于AVAudioSession类,但它仅用于音频。
| 事件名称 | 执行的操作及时间 |
|---|---|
AudioRouteChanged |
当输出路由更改时触发(例如,扬声器到耳机)。 |
Interrupted |
当音频会话被中断时触发。 |
Resumed |
当音频会话的中断完成时触发。 |
InputAudioQueue
当音频被输入到设备时使用输入队列。
| 事件名称 | 执行的操作及时间 |
|---|---|
InputCompleted |
当输入完成时触发。 |
OutputAudioQueue
输出队列用于音频输出。
| 事件名称 | 执行的操作及时间 |
|---|---|
OutputCompleted |
当音频输出完成时触发。 |
AUGraph 和 AudioUnit
AUGraph和AudioUnit共享此事件,对两者都有相同的效果。
| 事件名称 | 执行的操作及时间 |
|---|---|
RenderCallback |
在音频图渲染期间使用的回调。 |
AudioConverter
回调事件用于将对象从事件回调到对象。
| 事件名称 | 执行的操作及时间 |
|---|---|
EncoderCallback |
为转换编码器创建的回调。 |
CAAnimation
动画有两个关键事件;开始和结束。
| 事件名称 | 执行的操作及时间 |
|---|---|
AnimationStarted |
当动画开始时调用。 |
AnimationEnded |
当动画结束时被调用。 |
CBCentralManager
The CoreBluetoothCentralManager 是处理添加或移除蓝牙设备的类。处理事件已在以下表中列出:
| 事件名称 | 它做什么以及何时发生 |
|---|---|
ConnectedPeripheral |
当连接成功建立时被触发。 |
DisconnectedPeripheral |
当连接成功断开时被触发。 |
DiscoveredPeripheral |
当发现连接时被触发。 |
FailedToConnectPeripheral |
当连接外围设备失败时被触发。 |
RetrievedConnectedPeripheral |
在检索到已连接的外围信息后被触发。 |
RetrievedPeripherals |
在请求所有存储的外围列表之后被触发。 |
UpdatedState |
在连接状态改变后调用。 |
CBPeripheral
每个蓝牙设备都附有特性和描述符。事件已在以下表中列出:
| 事件名称 | 它做什么以及何时发生 |
|---|---|
DiscoverCharacteristic |
发现外围服务的特性。 |
DiscoveredDescriptor |
发现特性的描述符。 |
DiscoveredIncludedService |
它发现外围指定的包含服务。 |
DiscoveredService |
在发现外围指定的服务并完成连接后调用。 |
InvalidatedService |
当外围服务改变时被触发。 |
RssiUpdated |
当外围连接到 CoreBluetooth 中心管理器时,其当前 RSSI 值被更新时被触发。 |
UpdatedCharacteristicValues |
在更新特性的值之后被触发。 |
UpdatedName |
当外围名称改变时被触发。 |
UpdatedNotificationState |
当外围通知状态改变时被触发。 |
UpdatedValue |
当外围特性描述符的值改变时被触发。 |
WroteCharacteristicValue |
它在写入特性的值之后被触发。 |
WroteDescriptorValue |
在写入描述符的值之后被触发。 |
CBPeripheralManager
The CBPeripheralManager 类管理连接到蓝牙管理器的外围设备。
| 事件名称 | 它做什么以及何时发生 |
|---|---|
AdvertisingStarted |
当外围开始广播其存在时被触发。 |
CharacteristicSubscribed |
当远程设备订阅特性的值时被触发。 |
CharacteristicUnsubscribed |
当远程设备取消订阅特性的值时被触发。 |
ReadRequestReceived |
在接收到读取请求后被触发。 |
ReadyToUpdateSubscribers |
当本地外围准备好发送特性更新值时被调用。 |
ServiceAdded |
添加服务后,会触发此事件。 |
StateUpdated |
当外围设备状态更新后,会触发此事件。 |
WriteRequestsReceived |
接收到写请求后,会触发此事件。 |
CFSocket
CoreFoundation 套接字覆盖了到远程套接字的连接(通常是网络连接)。
| 事件名称 | 功能及时间 |
|---|---|
AcceptEvent |
当 CoreFoundation 设置为接受来自套接字的事件时,会调用此方法。 |
ConnectEvent |
当客户端连接到它所调用的远程套接字时,会触发此事件。 |
CFStream
类似于 IOStream,CFStream 处理来自和到套接字的数据。
| 事件名称 | 功能及时间 |
|---|---|
CanAcceptBytesEvent |
当流中有信息可供写入时,会触发此事件。 |
ClosedEvent |
当流上的关闭操作完成时,会触发此事件。 |
ErrorEvent |
当流中发生错误时,会触发此事件。 |
HasBytesAvailableEvent |
当流中有信息可供读取时,会触发此事件。 |
OpenCompletedEvent |
当流上的打开操作完成时,会触发此事件。 |
CLLocationManager
The CoreLocation LocationManager 类是 iOS 设备上位置管理器的控制类。
| 事件名称 | 功能及时间 |
|---|---|
AuthorizationChanged |
当用户允许或阻止使用 CoreLocation 函数时,会调用此方法。 |
DeferredUpdatesFinished |
当延迟更新时间结束时,会触发此事件。 |
DidStartMonitoringForRegion |
通知代理正在监控一个新的区域。 |
Failed |
当 CLLocationManager 无法启动时,会触发此事件。 |
LocationUpdatesPaused |
它暂停更新位置。 |
LocationUpdatesResumed |
它重新启动暂停的位置更新。 |
LocationsUpdated |
它更新 GPS 位置。 |
MonitoringFailed |
当 CLLocationManager 无法监控时,会触发此事件(通常是由于与卫星没有通信)。 |
RegionEntered |
当进入一个区域时,会调用此方法。 |
RegionLeft |
当离开一个区域时,会调用此方法。 |
UpdatedHeading |
当航向更新时,会调用此方法。 |
UpdatedLocation |
当位置更新时,会调用此方法。 |
MidiClient
在 iPhone 上可能不太有用(尽管在 iPad 上有用),iOS 具有丰富的 MIDI 层。
| 事件名称 | 功能及时间 |
|---|---|
IOError |
当 MIDI 客户端遇到输入/输出错误时,会触发此事件。 |
ObjectAdded |
当添加 MIDI 设备时,会调用此方法。 |
ObjectRemoved |
当移除 MIDI 设备时,会调用此方法。 |
PropertyChanged |
当设备的 MIDI 属性更改时,会调用此方法。 |
SerialPortOwnerChanged |
当串行端口所有者更改时,会调用此方法(实际上是在设备切换时调用)。 |
SetupChanged |
当客户端设置更改时,会触发此事件。 |
ThruConnectionsChanged |
当 MIDI 菊花链连接改变时被触发。 |
MidiEndpoint 和 MidiPort
这两个类都有相同名称的事件,并且在两个类中都执行完全相同的操作!
| 事件名称 | 功能及时间 |
|---|---|
MessageReceived |
当 MIDI 子系统从设备接收到消息时被触发。 |
Monotouch.Dialog
MonoTouch.Dialog是一个功能极其强大的类,它极大地简化了在您的应用中创建和使用UITableViews的麻烦。
BadgeElement, BaseBooleanImageElement, GlassButton, LoadMoreElement, MessageElement, 和 StringElement
这些类都包含Tapped事件。它在选项被点击时执行。请注意,它发送一个NSAction事件而不是典型的对象发送器/EventArgs e组合,这意味着你不能为类的多个实例使用相同的处理程序。
| 事件名称 | 功能及时间 |
|---|---|
Tapped |
当元素被点击时触发。 |
BoolElement
这是一个简单的开/关元素。
| 事件名称 | 功能及时间 |
|---|---|
ValueChanged |
当布尔值改变时触发。 |
DateTimeElement
当调用时,会生成一个标准的日期时间选择器视图。
| 事件名称 | 功能及时间 |
|---|---|
DateSelected |
当日期被选中时被调用。 |
DialogViewController
DialogViewController是放置MT.D的视图控制器。
| 事件名称 | 功能及时间 |
|---|---|
OnSelection |
当 DVC 内的对象被选中时被调用。 |
RefreshRequested |
当调用MT.D刷新时被调用。 |
SearchTextChanged |
当搜索文本字段文本改变时被调用。 |
ViewAppearing |
当MT.D视图正在创建时被触发。 |
ViewDisappearing |
当MT.D视图正在销毁时被触发。 |
EntryElement
一个条目元素允许在MT.D单元格中持有的UITextField进行数据输入。
| 事件名称 | 功能及时间 |
|---|---|
Changed |
当EntryElement内容被改变时被调用。 |
ShouldReturn |
询问文本字段是否应该处理返回按钮。 |
StyledStringElement
StyledStringElement类与StringElement类相同,不同之处在于你可以为其添加样式。
| 事件名称 | 功能及时间 |
|---|---|
AccessoryTapped |
当元素被点击时触发。 |
EKCalendarChooser
EventKit的CalendarChooser类允许用户访问日历。
| 事件名称 | 功能及时间 |
|---|---|
Cancelled |
当用户取消选择时被调用。 |
Finished |
当用户选择完成时被调用。 |
SelectionChanged |
当选定的日期改变时被调用。 |
EKEventEditViewController 和 EKEventViewController
这两个 EventKit 视图控制器都有相同名称的事件,对两个控制器都有相同的效果。
| 事件名称 | 功能及时间 |
|---|---|
Completed |
当控制完成其操作时发出。 |
EAAccessory
外部配件类处理不属于手机或蓝牙的任何配件。
| 事件名称 | 功能及时间 |
|---|---|
Disconnected |
当外部配件断开连接时发出。 |
NS 类
NS 代表 NextStep。它们是一系列类,在史蒂夫·乔布斯从荒野归来后,苹果收购 NeXT 时成为了一部分传承。虽然与它们关联的事件并不多,但它们至关重要。许多 NS 类是 Xamarin.iOS 与 Objective-C 底层之间的绑定所必需的。
NSCache
NSCache 是一个内部缓存系统,用于许多不同的任务。
| 事件名称 | 功能及时间 |
|---|---|
WillEvictObject |
当对象即将从缓存中移除时调用。 |
NSKeyedArchiver
KeyedArchiver 类使用密钥编码数据。
| 事件名称 | 功能及时间 |
|---|---|
EncodedObject |
当对象被编码时调用。 |
Finished |
编码完成后触发。 |
Finishing |
当编码即将完成时触发。 |
ReplacingObject |
通知代理,给定对象将被另一个对象替换。 |
NSKeyedUnarchiver
KeyedUnarchiver 类执行 KeyedArchiver 类的逆操作。
| 事件名称 | 功能及时间 |
|---|---|
Finished |
当解码完成时触发。 |
Finishing |
当解码即将完成时触发。 |
ReplacingObject |
通知代理,给定对象将被另一个对象替换。 |
NSNetService
这是用于网络服务的类。
| 事件名称 | 功能及时间 |
|---|---|
AddressResolved |
当地址已解析时发出。 |
PublishFeature |
通知服务功能已成功发布。 |
Published |
通知服务已成功发布。 |
ResolveFailed |
当地址无法解析时发出。 |
Stopped |
通知请求发布或解析已停止。 |
UpdatedTxtRecordData |
通知服务的一个 TXT 记录已被更新。 |
WillPublish |
通知网络已准备好发布服务。 |
WillResolve |
通知网络已准备好解析服务 |
NSNetServiceBrowser
NetServiceBrowser 类用于连接外部世界。
| 事件名称 | 功能及时间 |
|---|---|
DomainRemoved |
当域名消失或不再可用时通知。 |
FoundDomain |
当发送者找到域名时触发。 |
FoundService |
当发送者找到服务时触发。 |
NotSearched |
当搜索未成功时触发。 |
SearchStarted |
当搜索开始时,会触发此事件。 |
SearchStopped |
当搜索停止时,会触发此事件。 |
SearchRemoved |
当搜索从浏览器中移除时,会触发此事件。 |
NSStream
这与标准的 .NET Stream 类似。
| 事件名称 | 它的作用和触发时间 |
|---|---|
OnEvent |
当给定流上发生给定事件时,会调用此事件。 |
GLKView
GL KitView 是用于 OpenGL 图形的视图。
| 事件名称 | 它的作用和触发时间 |
|---|---|
DrawInRect |
在给定的矩形内绘制视图的内容。 |
GK 类
游戏是任何用户体验的重要组成部分。如果你只想让手机成为一部手机,那没问题,但如果你有玩愤怒刺猬或足球经理游戏的能力,为什么不利用它们呢?
GKAchievementViewController, GKFriendRequestComposeViewController, 和 GKLeaderboardViewController
这三个类具有相同名称的事件。
| 事件名称 | 它的作用和触发时间 |
|---|---|
DidFinish |
当视图被关闭时,会调用此事件。 |
GKGameCenterViewController
此视图控制器是主要游戏视图控制器。
| 事件名称 | 它的作用和触发时间 |
|---|---|
Finished |
当玩家停止与视图控制器交互时,会调用此事件。 |
GKMatch
GKMatch 类处理玩家的连接。
| 事件名称 | 它的作用和触发时间 |
|---|---|
DataReceived |
当从玩家接收到数据时,会触发此事件。 |
Failed |
当匹配无法连接到其他玩家时,会调用此事件。 |
StateChanged |
当玩家连接或断开与匹配的连接时,会触发此事件。 |
GKMatchmakerViewController
MatchMaker 视图控制器处理设备之间的匹配。
| 事件名称 | 它的作用和触发时间 |
|---|---|
DidFailWithError |
当视图控制器遇到无法恢复的错误时,会触发此事件。 |
DidFindMatch |
当找到对等方匹配时,会触发此事件。 |
DidFindPlayers |
当找到托管匹配时,会调用此事件。 |
ReceivedAcceptFromHostedPlayer |
当玩家接受托管匹配的邀请时,会触发此事件。 |
WasCancelled |
当用户取消匹配请求时,会触发此事件。 |
GKSession
GKSession 类可以是设备本地或外部的。
| 事件名称 | 它的作用和触发时间 |
|---|---|
ConnectionFailed |
尝试连接到另一个对等方失败时,会触发此事件。 |
ConnectionRequest |
尝试连接到另一个对等方时,会触发此事件。 |
Failed |
当会话中发生严重错误时,会发送此事件。 |
PeerChanged |
当对等方状态改变时,会收到此事件。 |
ReceiveData |
当从对等方接收到数据时,会触发此事件。 |
MKMapView
MapKit 的 MapView 类处理地图的创建和显示,以及将指针添加到视图上(如用户或兴趣点)。
| 事件名称 | 它的作用和触发时间 |
|---|---|
CalloutAccessoryControlTapped |
当用户点击注释视图的附件之一(如按钮)时触发。 |
ChangedDragState |
当注释视图的状态之一发生变化时触发。 |
DidAddAnnotationViews |
当一个或多个注释视图被添加到地图上时调用。 |
DidAddOverlayViews |
当一个或多个覆盖视图被添加到地图上时调用。 |
DidChangeUserTrackingModel |
当用户跟踪模式更改时触发。 |
DidDeselectAnnotationView |
当取消选择注释视图时调用。 |
DidFailToLocateUser |
当尝试查找用户位置失败时触发。 |
DidSelectAnnotationView |
当选择其中一个注释时调用。 |
DidStopLocatingUser |
当定位用户服务停止时调用。 |
DidUpdateUserLocation |
当用户位置更新时触发。 |
LoadingMapFailed |
当地图加载失败时调用(通常由没有 GPS 连接引起)。 |
MapLoaded |
当地图加载完成时触发。 |
RegionChanged |
当区域发生变化时触发。 |
RegionWillChange |
当区域正在变化时触发。 |
WillStartLoadingMap |
当地图即将开始加载时触发。 |
WillStartLocatingUser |
当用户即将被定位时触发。 |
MPMediaPickerController
这些事件附加到 MediaPickerController 视图控制器。
| 事件名称 | 执行的操作及何时发生 |
|---|---|
DidCancel |
当用户点击取消按钮时调用。 |
ItemsPicked |
当用户选择了一组媒体项目时调用。 |
MFMailComposeViewController 和 MFMessageComposeViewController
MFMailComposeViewController 和 MFMessageComposeViewController 都有此事件。它用于编写电子邮件或消息。
| 事件名称 | 执行的操作及何时发生 |
|---|---|
Finished |
当组合完成时发出。 |
PKAddPassesViewController
passkit 视图控制器用于存储密码。
| 事件名称 | 执行的操作及何时发生 |
|---|---|
Finished |
在添加通行证视图控制器完成后触发。 |
QLPreviewController
QuickLook 预览 控制器允许快速查看文件。
| 事件名称 | 执行的操作及何时发生 |
|---|---|
DidDismiss |
在预览控制器关闭后调用。 |
WillDismiss |
在预览控制器关闭之前调用。 |
SK 类
StoreKit 类处理应用商店和从应用商店的在线购买。
SKProductsRequest
此类处理从应用商店请求产品。
| 事件名称 | 执行的操作及何时发生 |
|---|---|
ReceivedResponse |
当应用商店对产品请求做出响应时调用。 |
RequestFailed |
当请求应用商店失败时触发。 |
RequestFinished |
当产品请求关闭时触发。 |
SKRequest
此类用于处理对商店的请求。
| 事件名称 | 它做什么以及何时做 |
|---|---|
RequestFailed |
当请求应用商店失败时触发。 |
RequestFinished |
当产品请求关闭时触发。 |
SKStoreProductViewController
StoreProductViewController是用于应用商店内容的主要视图控制器。
| 事件名称 | 它做什么以及何时做 |
|---|---|
Finished |
当产品请求关闭时触发。 |
UIClasses
这些类专门处理用户界面事件,没有它们几乎什么也做不了。
UIAccelerometer
加速计检测设备的移动。
| 事件名称 | 它做什么以及何时做 |
|---|---|
Acceleration |
当检测到移动时触发。 |
UIActionSheet 和 UIAlertView
这两个类在两者中都共享这些命名事件,并且具有相同的效果。
| 事件名称 | 它做什么以及何时做 |
|---|---|
Cancelled |
当点击取消按钮时触发。 |
Clicked |
当控件被选中时触发。 |
Dismissed |
当ActionSheet或AlertView关闭时触发。 |
Presented |
当ActionSheet或AlertView显示时触发。 |
WillDismiss |
当ActionSheet或AlertView即将关闭时触发。 |
WillPresent |
当ActionSheet或AlertView即将显示时触发。 |
UIButtonBarItem
按钮栏项必须连接到ButtonBar才能工作。
| 事件名称 | 它做什么以及何时做 |
|---|---|
Clicked |
当项目被点击时调用。 |
UIImagePickerController
此类用于从相册中选择图片。
| 事件名称 | 它做什么以及何时做 |
|---|---|
Cancelled |
当选择器被取消时触发。 |
DidShowViewController |
当视图控制器已显示时触发。 |
FinishedPickingImage |
当用户完成选择图片时触发。 |
FinishedPickingMedia |
当用户完成媒体选择时触发。 |
WillShowViewController |
在视图控制器显示之前触发。 |
UIPageViewController
此类用作“虚拟”页面计数器的一种形式。
| 事件名称 | 它做什么以及何时做 |
|---|---|
DidFinishAnimating |
页面滚动过渡完成后触发。 |
WillTransition |
在页面滚动过渡开始之前触发。 |
UIPopoverController
此类仅在 iPad 上可用。
| 事件名称 | 它做什么以及何时做 |
|---|---|
DidDismiss |
当控制器被关闭时触发。 |
UIPrintInteractionController
用于从设备打印。
| 事件名称 | 它做什么以及何时做 |
|---|---|
DidDismiss |
当控制器被关闭时触发。 |
DidFinishJob |
当打印机完成作业时触发。 |
DidPresentPrinterOptions |
当打印机选项被显示时触发。 |
WillDismissPrinterOptions |
当打印机选项被关闭时触发。 |
WillPresentPrinterOptions |
当打印机选项即将显示时触发。 |
WillStartJob |
当打印作业即将开始时触发。 |
UIScrollView
滚动视图允许在页面上的内容更多,如果没有它,这些内容将无法适应页面。
| 事件名称 | 它的作用和触发时间 |
|---|---|
DecelerationEnded |
当滚动减速停止时调用。 |
DecelerationStarted |
当滚动减速开始时调用。 |
DidZoom |
缩放发生后调用一次。 |
DraggingEnded |
当拖动停止(手指从手机上移开)时调用。 |
DraggingStarted |
当开始拖动(手指在手机上拖动)时调用。 |
ScrollAnimationEnded |
当滚动完成后调用。 |
Scrolled |
当滚动完成后调用。 |
ScrolledToTop |
当完成滚动到视图顶部时触发。 |
WillEndDragging |
当用户即将结束拖动时触发。 |
ZoomingEnded |
当缩放完成后触发。 |
ZoomingStarted |
当开始缩放时触发。 |
UISearchBar
这是一个在应用内或设备上查找信息的搜索方法。
| 事件名称 | 它的作用和触发时间 |
|---|---|
BookmarkButtonClicked |
当点击书签按钮时调用。 |
CancelButtonClicked |
当点击取消按钮时调用。 |
ListButtonClicked |
当点击列表按钮时调用。 |
OnEditingStarted |
当UITextField视图边界被进入时调用。 |
OnEditingStopped |
当UITextField视图边界被离开时调用。 |
SearchButtonClicked |
当点击搜索按钮时调用。 |
SelectedScopeButtonIndexChanged |
当范围按钮选择改变时调用。 |
TextChanged |
当UITextField文本被更改时调用。 |
UISplitViewController
分割视图允许将视图分割成部分(例如,当在按钮栏上按下菜单按钮时,左侧会出现菜单)。
| 事件名称 | 它的作用和触发时间 |
|---|---|
WillHideViewController |
在隐藏视图控制器之前触发。 |
WillPresentViewController |
在呈现视图控制器之前触发。 |
WillShowViewController |
在显示视图控制器之前触发。 |
UITabBar
可以使用标签导航的简单方法与TabBarController控制器一起使用。
| 事件名称 | 它的作用和触发时间 |
|---|---|
DidBeginCustomizingItems |
在显示自定义模态视图后触发。 |
DidEndCustomizingItems |
在自定义模态视图被关闭后触发。 |
ItemSelected |
当标签栏项目被选择时被调用。 |
WillBeginCustomizingItems |
在自定义模态视图被关闭前被触发。 |
WillEndCustomizingItems |
在自定义模态视图被关闭前被触发。 |
UITabBarController
This is a convenient method of controlling the NIBs called when a tab bar item is clicked.
| 事件名称 | 功能和时机 |
|---|---|
FinishedCustomizingViewController |
当标签栏自定义表单被关闭时被触发。 |
OnCustomizingViewController |
当自定义开始时被触发。 |
OnEndCustomizingViewController |
当自定义结束时被触发。 |
ViewControllerSelected |
当用户在标签栏上选择一个项目时被调用。 |
UITextField
The UITextField class is a simple, editable textbox.
| 事件名称 | 功能和时机 |
|---|---|
Ended |
当TextField的编辑结束时被触发。 |
Started |
当TextField的内容被编辑时立即被触发。 |
UITextView
The UITextView class displays text. It also inherits from the ScrollView view to enable more text than within the frame.
| 事件名称 | 功能和时机 |
|---|---|
Changed |
当文本发生变化时被触发。 |
DecelerationEnded |
当滚动减速停止时被调用。 |
DecelerationStarted |
当滚动减速开始时被调用。 |
DidZoom |
当缩放发生时被调用一次。 |
DraggingEnded |
当拖动停止(手指从手机上移开)时被调用。 |
DraggingStarted |
当拖动开始(手指在手机上拖动)时被调用。 |
Ended |
在滚动完成后被调用。 |
ScrollAnimationEnded |
当滚动完成时被调用。 |
Scrolled |
当文本视图被滚动时被触发。 |
ScrolledToTop |
当视图滚动到顶部完成时被触发。 |
SelectionChanged |
当文本视图中的文本选择发生变化时被触发。 |
Started |
当滚动开始时被触发。 |
WillEndDragging |
当用户即将结束拖动时被触发。 |
ZoomingEnded |
当缩放完成时被触发。 |
ZoomingStarted |
当缩放开始时被触发。 |
UIView
The UIView is a generic view that can be added to any View Controller.
| 事件名称 | 功能和时机 |
|---|---|
AnimationWillEnd |
当动画即将结束时被触发。 |
AnimationWillStart |
当动画即将开始时被触发。 |
UIWebView
用于显示 HTML 的视图(无论是来自网站还是由应用生成)。
| 事件名称 | 功能和时机 |
|---|---|
LoadError |
当 URL 加载失败时被触发。 |
LoadFinished |
当 URL 加载完成时被触发。 |
LoadStarted |
当 URL 加载开始时被触发。 |
广告类
通常,应用程序内的广告是开发者生成收入的一种便捷方法(被称为点击通过)。
AdBannerView
AdBannerView 视图是包含广告的视图。
| 事件名称 | 执行的操作和时机 |
|---|---|
ActionFinished |
当横幅视图完成覆盖 UI 的动作执行时被触发。 |
AdLoaded |
当广告加载时被触发。 |
FailedToReceiveAd |
当广告检索失败时被触发(通常是网络连接错误)。 |
WillLoad |
当广告即将加载时被触发。 |
AdInterstitialAd
这些是全屏广告。它们的行为与AdBannerView相同。
| 事件名称 | 执行的操作和时机 |
|---|---|
ActionFinished |
当横幅视图完成覆盖 UI 的动作执行时被触发。 |
AdLoaded |
当广告加载时被触发。 |
AdUnloaded |
在全屏广告处理其内容后调用。 |
FailedToReceiveAd |
当广告检索失败时被触发(通常是网络连接错误)。 |
WillLoad |
当广告即将加载时被触发。 |
OpenTK
OpenTK 是一个在移动和桌面应用程序开发者中广泛使用的开放图形层。
IGameWindow
这是一个接口类,而不是一个类,它处理的是游戏窗口本身,而不是游戏。
| 事件名称 | 执行的操作和时机 |
|---|---|
Load |
在窗口首次显示之前被触发。 |
RenderedFrame |
当渲染时间到达时被触发。 |
Unload |
当窗口被销毁时被触发。 |
UpdateFrame |
当需要更新帧时被调用。 |
IPhoneOSGameView
这是OpenTK类持有的GameView。
| 事件名称 | 执行的操作和时机 |
|---|---|
Closed |
当游戏视图被关闭但未处置时调用。 |
Disposed |
当游戏视图被处置时被调用。 |
Load |
在运行循环开始之前被触发。 |
RenderedFrame |
作为运行循环处理的一部分,当应该渲染帧时被触发。 |
Resize |
当视图大小改变时被调用。 |
TitleChanged |
当视图标题改变时被调用。 |
Unload |
当运行循环结束时被触发。 |
UpdateFrame |
当帧作为运行循环的一部分更新时被触发。 |
VisibleChanged |
当视图的可见性发生变化时被调用。 |
WindowStateChanged |
当窗口状态改变时被触发。 |
摘要
可以说,事件是使你的 iPhone 成为现在的设备的原因。你几乎不太可能需要这里列出的大多数事件,但如果你像我一样讨厌搜索,那么这一章将来真的会对你有所帮助。
第七章:手势
在撰写本文时,美国法院正在就谁发明了拖动图像等问题进行争论,但毫无疑问,iOS 的一个关键特性是能够使用手势。简单来说,当您点击屏幕以启动应用程序或选择图像的一部分以放大它或类似操作时,您正在使用手势。
本章将涵盖以下主题:
-
什么是手势?
-
向 UI 添加手势
-
处理手势
-
处理拖放
手势
(在 iOS 的术语中)手势是 UI 与设备之间的任何触摸交互。iOS 6 提供了六种用户可以使用的手势。以下表格列出了这些手势及其简要说明:
| 类 | 名称和类型 | 手势 |
|---|---|---|
UIPanGestureRecognizer |
PanGesture;连续类型 |
通过在屏幕上拖动来平移图像或超大视图 |
UISwipeGestureRecognizer |
SwipeGesture;连续类型 |
与平移类似,但它是滑动 |
UITapGestureRecognizer |
TapGesture;离散类型 |
连续点击屏幕(可配置) |
UILongPressGestureRecognizer |
LongPressGesture;离散类型 |
长按屏幕 |
UIPinchGestureRecognizer |
PinchGesture;连续类型 |
通过捏住区域并移动手指来缩放 |
UIRotationGestureRecognizer |
RotationGesture;连续类型 |
通过相反方向移动手指来旋转 |
可以通过编程或通过 Xcode 添加手势。以下截图列出了可用的手势,以及设计师右侧的其余小部件:

要添加手势,将您想要使用的手势拖动到视图栏下的视图(如下截图所示):

按照您想要的样式设计 UI,同时按住Ctrl键,将手势拖动到您想要识别的对象上。在我的例子中,您想要识别的对象可以在屏幕上的任何位置。一旦将手势连接到您想要识别的对象,您将看到手势的可配置选项。
Taps字段是在触发识别器之前所需的点击次数,而Touches字段是在屏幕上需要触摸的点数,以触发识别器。
当您连接 UI 时,必须添加手势。
手势代码
当使用 Xcode 时,编写手势代码很简单。Xcode 中为点击手势定义的类称为tapGesture,并在以下代码中使用:
private int tapped = 0;
public override void ViewDidLoad()
{
base.ViewDidLoad();
tapGesture.AddTarget(this, new Selector("screenTapped"));
View.AddGestureRecognizer(tapGesture);
}
[Export("screenTapped")]public void SingleTap(UIGestureRecognizer s)
{
tapped++;
lblCounter.Text = tapped.ToString();
}
代码本身并没有什么特别之处;它只是显示屏幕被点击了多少次。
当检测到点击时,代码会调用Selector方法。只要Selector和Export名称相同,方法名无关紧要。
类型
当最初描述手势类型时,它们被赋予了一个类型。这个类型反映了发送到Selector方法的消息数量。离散类型生成单个消息。连续类型生成多个消息,这要求Selector方法更加复杂。复杂性是通过Selector方法必须检查手势的状态来决定如何处理消息以及是否已完成而增加的。
在代码中添加手势
使用 Xcode 添加手势不是必需的。要在以下代码中执行与我在 Xcode 中先前代码相同的任务非常简单。代码将如下所示:
UITapGestureRecognizer t'pGesture = new UITapGestureRecognizer()
{
NumberOfTapsRequired = 1
};
然后可以使用AddTarget中的其余代码。
连续类型
以下代码,一个捏合识别器,展示了简单的缩放。代码之后,我将解释几个其他状态。设计师代码中唯一的区别是,我使用了UIImageView而不是标签,以及UIPinchGestureRecognizer类而不是UITapGestureRecognizer类。
public override void ViewDidLoad()
{
base.ViewDidLoad();
uiImageView.Image =UIImage.FromFile("graphics/image.jpg")Scale(new SizeF(160f, 160f);
pinchGesture.AddTarget(this, new Selector("screenTapped"));
uiImageView.AddGestureRecognizer(pinchGesture);
}
[Export("screenTapped")]public void SingleTap(UIGestureRecognizer s)
{
UIPinchGestureRecognizer pinch = (UIPinchGestureRecognizer)s;
float scale = 0f;
PointF location;
switch(s.State)
{
case UIGestureRecognizerState.Began:
Console.WriteLine("Pinch begun");
location = s.LocationInView(s.View);
break;
case UIGestureRecognizerState.Changed:
Console.WriteLine("Pinch value changed");
scale = pinch.Scale;
uiImageView.Image = UIImageFromFile("graphics/image.jpg")Scale(new SizeF(160f, 160f), scale);
break;
case UIGestureRecognizerState.Cancelled:
Console.WriteLine("Pinch cancelled");
uiImageView.Image = UIImageFromFile("graphics/image.jpg")Scale(new SizeF(160f, 160f));
scale = 0f;
break;
case UIGestureRecognizerState.Recognized:
Console.WriteLine("Pinch recognized");
break;
}
}
其他 UIGestureRecognizerState 值
下表列出了其他识别器状态:
| 状态 | 描述 | 备注 |
|---|---|---|
| 可能 | 默认状态;手势尚未被识别 | 所有手势都使用 |
| 失败 | 手势失败 | 此状态未发送任何消息 |
| 平移 | 滚动方向 | 用于滚动手势 |
| 速度 | 滚动速度 | 用于滚动手势 |
除了这些,还应注意的是,离散类型仅使用可能和已识别状态。
处理拖放
可以使用手势或使用TouchesBegan、TouchesMoved和TouchesEnded处理程序来处理拖放。本质上,可以使用如以下代码所示的定制UIImageView类:
public class myDragImage : UIImageView
{
private PointF myLoc, myStartLoc;
private bool TouchedOnce = false;
public myDragImage (RectangleF frame)
{
this.Frame = frame;
myStartLoc = this.Frame.Location;
}
public override void TouchesBegan(NSSet touches, UIEvent e)
{
myLoc = Frame.Location;
var touch = (UITouch)e.TouchesForView(this).AnyObject;
var bounds = Bounds;
myStartLoc = touch.LocationInView(this);
Frame = new RectangleF(Location,bounds.Size);
}
public override void TouchesMoved(NSSet touches, UIEvent e)
{
var bounds = Bounds;
var touch = (UITouch)e.TouchesForView(this).AnyObject;
myLoc.X += touch.LocationInView(this).X - myStartLoc.X;
myLoc.Y += touch.LocationInView(this).Y - myStartLoc.Y;
Frame = new RectangleF(myLoc, bounds.Size);
TouchedOnce = true;
}
public override void TouchesEnded(NSSet touches, UIEvent e)
{
myStartLoc = myLoc;
}
}
这是一种简单的处理拖放的方式。对于手势,应使用连续类型。
概述
手势无疑可以为您的应用增添很多功能。它们可以让用户快速浏览图片,在地图上移动,放大和缩小,以及选择视图上任何内容的区域。它们的灵活性是 iOS 被公认为用户操作屏幕上图像、视频和其他内容的极其灵活设备的根本原因。
第八章. 线程
iOS 被称为多线程系统,理解如何在应用中使用线程可能会有所帮助。
我们将在本章中介绍以下主题:
-
线程的简要介绍
-
主要 UI 线程
-
子线程
-
AppDelegate 类
线程概念
让我们讨论一种学习线程的简单方法。
可以将单线程环境视为去你当地的大学一样。你可以选择多条路线,但最终你都会到达那里,这个过程需要一定的时间;你出发,你旅行,你到达。
多线程环境需要被视为大学本身,每个线程都是一个学生。所有学生都是从早上 9 点开始,一直持续到中午 12 点。他们在那段时间里可能互相干扰,也可能不干扰;他们都会执行一个任务或合作完成任务以加快答案的交付。30 个不同的线程,同时以不同的速度工作,但到了中午 12 点,他们都能成功收敛并结束他们的活动,任务完成。他们从下午 1 点到 4 点重复这个过程,同样,在这些小时里,也存在有组织的混乱,但到了下午 4 点,一切都会收敛。讲师是控制谁做什么的人,在线程模型中,是控制线程。简单!
在 iOS 开发中,讲师会被归类为 UI 线程;它是可以启动新线程的线程,最终,所有信息都需要反馈到这个线程上。
主要 UI 线程
如其名所示,UI 线程控制着用户界面。它通常在资源和使用处理器时间方面需求最大。并非所有操作都在 UI 线程上运行。例如,如果 UI 调用一个方法,而这个方法不能在 UI 线程上运行(例如,在第十一章中列出的 SQLite 示例,处理数据),那么就会发生这种情况。一旦方法返回,代码就会执行,流程继续。
UI 线程不应被误认为是单一任务;它不是。单一任务会阻止其他应用程序运行,我们知道这不是情况(例如,你可以在玩愤怒的小鸟的同时收到短信)。
Xamarin.iOS允许非 UI 调用简单地跳回 UI 线程。
InvokeOnMainThread(delegate () {…});
或者,如果找不到线程的引用(例如超出范围或在非 UI 线程类中),可以使用以下代码行:
using (var pool = new NSAutoreleasePool()) {
pool.InvokeOnMainThread(delegate() {
// do something on the UI thread
});
}
死锁
在多线程系统的描述中,你可能在想,如果所有学生应该在的时候没有回来会怎样?那时会发生什么?这是一个好问题,因为它是在处理多线程环境时,如果不小心可能会遇到的问题。这被称为死锁,字面上说,它可能会锁定应用程序甚至设备(尽管这种情况很少发生)。另一个问题是线程覆盖相同的内存位置(想象一下这是两个人或更多人同时与讲师交谈;只有一种声音会被记住)。
在这个例子中,如果两个线程几乎同时运行,它们都有时间在任何人到达内部锁之前获取第一个锁。如果没有Sleep()调用,其中一个线程很可能在另一个线程甚至开始之前就有时间获取并释放两个锁。
// thread 1
lock(typeof(int)) {
Thread.Sleep(1000);
lock(typeof(float)) {
Console.WriteLine("Thread 1 got both locks");
}
}
// thread 2
lock(typeof(float)) {
Thread.Sleep(1000);
lock(typeof(int)) {
Console.WriteLine("Thread 2 got both locks");
}
}
避免同步访问器的死锁
避免这种形式死锁的一个简单方法是为每个持有类拥有它自己的私有死锁。这个问题及其解决方案在 MSDN 上描述得很好(msdn.microsoft.com/en-us/library/orm-9780596516109-03-18.aspx)。
从主 UI 线程启动新线程
从现有线程中产生的新线程被称为子线程。
在 iOS 设备上添加子线程的一个非常简单的方法是这样的。我首先创建了一个简单的 UI 来显示正在发生的事情。顶部的标签称为线程 1,底部的标签称为线程 2。

代码还展示了InvokeOnMainThread的作用——没有它,应用程序将失败:
using System.Threading;
...
private int i = 0;
public override void ViewDidLoad() {
base.ViewDidLoad();
var first = new Thread(new ThreadStart(firstThread));
var second = new Thread(new ThreadStart(secondThread));
btnStart.TouchUpInside += delegate {
first.Start();
Thread.Sleep(10);
// causes a 10ms delay between starting the next thread
second.Start();
};
}
private void firstThread() {
string text = string.Empty;
while (i < 10) {
text = string.Format("1st thread going i from {0} to {1}",i, ++i);
InvokeOnMainThread(delegate() {thread1.Text = text;
});
Thread.Sleep(100);
}
}
private void secondThread() {
string text = string.Empty;
while (i < 10) {
text = string.Format("2nd thread going i from {0} to {1}",i, ++i);
InvokeOnMainThread(delegate() {
thread2.Text = text;
});
Thread.Sleep(100);
}
}
当运行时,模拟器给出以下输出:

运行几次,你会得到不同的结果。线程在不同的时间在 UI 上执行操作;这很好地展示了线程的问题。如果 UI 正在等待线程 1 完成,但线程 2 完成了,那么它将不知道发生了什么。
在这种情况下,可以通过使用锁来清理代码。
使用锁
使用锁时要小心,可能是需要说的第一件事。锁用于同步线程并获得一个更加合理的输出。
private int i = 0;
private object lock_i = new object();
public override void ViewDidLoad() {
base.ViewDidLoad();
var first = new Thread(new ThreadStart(firstThread));
var second = new Thread(new ThreadStart(secondThread));
btnStart.TouchUpInside += delegate {
first.Start();
Thread.Sleep(10);
// causes a 10ms delay between starting the next thread
second.Start();
};
}
private void firstThread() {
string text = string.Empty;
do {
lock(this.lock_i) {
if (i >= 10) return;
text = string.Format("1st thread going i from {0} to {1}",i, ++i);
InvokeOnMainThread(delegate() {
thread1.Text = text;
});
}
Thread.Sleep(100);
}
while(true);
}
private void secondThread() {
string text = string.Empty;
do {
lock(this.lock_i) {
if (i >= 10) return;
text = string.Format("2nd thread going i from {0} to {1}",i, ++i);
InvokeOnMainThread(delegate() {
thread2.Text = text;
});
}
Thread.Sleep(100);
}
while(true);
}
这次当应用程序运行时,线程是同步的,结果总是相同的。使用这个锁定系统,应用程序可以自由地使用它需要的任何数量的线程来从 UI 线程完成任何工作。

AppDelegate 类
这里描述AppDelegate类可能看起来有些奇怪,但它很合适。AppDelegate类被称为单例类。它只使用一次,并且所有内容都来自它。将其视为超级线程;没有它,其他什么都不会发生。
我在第五章中对AppDelegate类进行了更详尽的处理,UI 控件,阅读完这一章后,你应该对其重要性有更清晰的认识。
摘要
在 iOS 应用程序中,多线程可以使用户体验更加响应,但与此同时,对于开发者来说,它也可能是导致许多夜晚晚归的原因,试图弄清楚为什么某些东西会在随机的时间崩溃,或者没有任何真正原因地突然停止工作。对待线程要小心,它们既可以是一种痛苦,也可以是一种乐趣。
第九章。线程任务
在上一章中,我们探讨了在 iOS 应用程序中使用线程的基本知识以及使用它们可能遇到的陷阱。在这一章中,我将继续这一主题,并探讨线程的其它方面以及异步调用。
在本章中,我们将涵盖以下主题:
-
在你的代码中使用后台线程和
System.Threading.Tasks -
使用异步代码
-
使用任务可能对线程模型产生的问题
线程简介
线程从其不起眼的起点发展起来,当开发者发现了它的力量时,就创造了后台线程和任务线程。后台线程正是如此——你设置一些在后台运行的任务,有时检查它,或者当它完成时,它会向你报告。在上一章中我使用的大学类比中,后台线程是行政人员——他们在后台工作,完成时报告。
线程任务需要被视为几乎独立的微型应用程序。它们开始、结束,并且可以在列表中的下一个任务上继续——在这整个过程中,应用程序可以自由地处理其他任务。任何线程操作都会有一些开销需要考虑,但除非你正在做非常复杂的事情,否则它不会很糟糕。
在你的应用程序中使用后台线程
后台线程来自 System.ComponentModel 命名空间,被称为 BackgroundWorker 线程。或者,ThreadPool.QueueUserWorkItem() 也做同样的事情(因为 ThreadPool 来自 System.Threading)。
BackgroundWorker
当你不想占用 UI 时,建议使用 BackgroundWorker 线程,因此创建大文件或将大量数据发送到服务器可以考虑使用 BackgroundWorker。当线程完成时,将触发 WorkerCompleted 事件。在 BackgroundWorker 的操作过程中,可以通过 ProgressChanged 事件更新 UI。后台操作持续一定的时间。重要的是要记住 BackgroundWorker 是一个异步任务。
当你使用 BackgroundWorker 线程时,你需要编写使用三个事件(如果你不想使用 ProgressChanged,则可以省略)的代码。
DoWork(object sender, DoWorkEventArgs e);
RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e);
ProgressChanged(object sender, ProgressChangedEventArgs e);
上一段代码演示了在应用程序中使用 BackgroundWorker。这很简单;它在屏幕上显示一个计数器,在下载图片的同时继续计数,当 RunWorkerCompleted 事件被触发时,显示下载的图片。
private UIImage downloadedImage;
private BackgroundWorker bgWorker;
private Timer t;
private int counter = 0;
public override void ViewDidLoad()
{
base.ViewDidLoad();
bgWorker = new BackgroundWorker();
bgWorker.DoWork += HandleDoWork;
bgWorker.RunWorkerCompleted += HandleRunWorkerCompleted;
btnStart.TouchUpInside += delegate
{
t = new Timer(1000); // 1 second
t.Elapsed += delegate
{
counter++;
InvokeOnMainThread(delegate()
{
lblCountValue.Text = counter.ToString();
});
};
t.Start();
bgWorker.RunWorkerAsync();
};
}
private void HandleRunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
{
uiImageView.Image =UIImage.FromImage(downloadedImage.CGImage).Scale(newSizeF(240f, 240f));
t.Stop();
}
private void HandleDoWork(object sender, DoWorkEventArgs e)
{
NSUrl url = new NSUrl("http://edmullen.net/test/rc.jpg");
NSData data = NSData.FromUrl(url);
downloadedImage = new UIImage(data);
}
上述代码足够简单,易于理解,它创建了BackgroundWorker对象,并为按钮创建了句柄和点击事件。在按钮内部,它设置了一个定时器,每秒更新计数器并启动BackgroundWorker。当BackgroundWorker的DoWork线程完成时,将显示并缩放图像。上述代码的结果以以下图像的形式展示:

记住,这是一个后台任务——UI 无法对数据进行操作,直到数据准备就绪。当你运行应用程序时,计数器的值也会有所不同,这取决于你是否在无线网络或户外,使用 3G 或 4G。
ThreadPool.QueueUserWorkItem
在了解了后台工作线程的工作方式后,让我们考虑使用ThreadPool.QueueUserWorkItem来完成相同任务:
private UIImage downloadedImage;
private System.Timers.Timer t;
private int counter = 0;
public override void ViewDidLoad()
{
base.ViewDidLoad();
btnStart.TouchUpInside += delegate
{
t = new Timer(100);
t.Elapsed += delegate
{
counter++;
InvokeOnMainThread(delegate()
{
lblCountValue.Text = counter.ToString();
});
};
t.Start();
ThreadPool.QueueUserWorkItem(delegate
{
ProcessFile();
});
};
}
private void ProcessFile()
{
NSUrl url = new NSUrl("http://edmullen.net/test/rc.jpg");
NSData data = NSData.FromUrl(url);
downloadedImage = new UIImage(data);
InvokeOnMainThread(delegate()
{
uiImageView.Image =UIImage.FromImage(downloadedImage.CGImage).Scale(newSizeF(240f, 240f));
});
t.Stop();
}
回调是一段作为参数传递的代码,需要在某个时间点执行。在多线程术语中,它通常在被调用或创建的线程中执行。回调告诉线程何时将其回调到主线程。最终结果是相同的,但QueueUserWorkItem方法可以用于前台和后台任务。
使用 System.Threading.Tasks
System.Threading.Tasks命名空间在线程内设置任务,因此线程可以执行一种小型程序,然后报告结果。它还可以用于启动任务。
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => GetMessage(currentPosition)).ContinueWith(ShowResults, scheduler);
调用启动了一个新的线程任务,该任务调用GetMessage。一旦返回,任务将继续执行ShowResults。调度器防止时间失控。

注意
虽然在www.gregshackles.com/2011/04/using-background-threads-in-mono-for-android-applications/上可用的代码是为 Android 设计的,但相同的代码(或多或少)可以用于 Xamarin.iOS,并提供了不同类型的线程及其使用方式的出色覆盖。

在线程上使用任务时遇到的问题
每当创建一个额外任务时,处理器必须开始在任务之间进行交换,这会减慢代码的执行速度。你还有跟踪任务以及它们如何与主 UI 线程协同工作的问题。一般来说,它们不应该引起问题,但你也要考虑到,除非你明确编程线程以同步方式运行,否则它们是异步运行的。为了防止代码失控,需要使用锁或回调。锁可能导致死锁条件,所以请小心!请参阅第八章,线程,以了解死锁及其避免的概述。
使用异步代码
异步是.NET 的重大变化之一,并在.NET v4 中发布,但直到最近才在 Mono 框架中实现,因此也在 Xamarin.Android 和 Xamarin.iOS 中实现。正如我在前一章中解释的,异步代码可能有点棘手,但幸运的是,这部分棘手的内容很容易理解。
任务和事件处理器
以以下代码为例:
var webView = new UIWebView();
webView.LoadStarted += HandleLoadStarted;
…
private void HandleLoadStarted (object sender, EventArgs e)
{}
LoadStarted事件的处理程序是一个同步过程——换句话说,它就像直线上去酒吧一样。问题是,当webView正在加载页面时,所有事情都会被阻塞——所以如果页面加载缓慢或者你需要运行其他东西(比如播放音乐),就会出现瓶颈。这就是异步LoadStarted事件可以发挥作用的地方。
var webView = new UIWebView();
webView.LoadStarted += async(object sender, EventArgs e) =>{HandleLoadStarted(sender, e);};
async方法与普通方法在以下代码中看起来不同:
private async void HandleLoadStarted(object sender, EventArgs e)
一个更实际的例子
在实现async方法之前,必须实现一个事件系统,以便一旦数据返回,就可以由方法处理(例如,从网络数据下载)。例如(以下是一个伪代码,以便你理解这个概念):
login.name = "nodoid201213";
login.password = "312102diodon";
login.DataReturned += HandleDataReturned;
callLoginService(login);
private void HandleDataReturned { … }
之前的代码现在可以由单个async方法处理,如下所示:
private async Task<bool> LogUserIn()
{
login.name = "nodoid201213";
login.password = "312102diodon";
bool loginResult = await callLoginService(login);
return loginResult;
}
这里关键是await——这会阻止下一行执行,直到callLoginService方法返回。这大大提高了响应速度——代码更少,需要监听的事件更少,而且几乎不需要做额外的事情。
如果一个方法返回一个值,需要在方法名之前使用Task<T>参数。如果没有返回值(例如,响应按钮点击),则需要使用void。
摘要
背景线程和异步调用有许多用途,一般建议如果某个过程耗时较长,就将其放在后台执行。使用线程时要小心。虽然大多数时候它们都很好,但你仍然需要在真实设备上测试所有应用程序,以确保线程正常工作。记住,模拟器是有缺陷的(例如,模拟器在即时编译处理器模型上工作,而不是手机使用的预编译,结果是网络服务可能不会按计划工作)并且与手机的工作方式不同。
第十章。动画
动画是静态(静止)图像运动的感觉。为了做到这一点,通常每秒大约需要移动 1/25。移动某物的步骤越多,运动越平滑,越容易欺骗大脑。我们已经看到了 第六章,事件,如何使用 UIAlertView 实现动画。现在我们需要看看我们如何使用 CoreAnimation 和 CoreGraphics 命名空间正常地做到这一点。这不会是一个详尽无遗的研究,但它会给你一个基础知识。
在本章中,我们将涵盖以下内容:
-
处理位图(缩放和旋转)
-
使用后释放内存
处理位图
位图图像可以在应用程序内部或外部创建。外部位图被渲染到 UIImageView 中,如下面的代码所示:
uiImageView = UIImage.FromFile("path/tofile.ext");
我们可以获取图像,所以让我们对它做些什么。
缩放图像
可以通过设置缩放因子来实现缩放。
uiImageView = UIImage.FromFile("path/tofile.ext").Scale(new SizeF(float w, float h), float scaleFactor);
如果没有指定 scaleFactor,则默认为 1.0f。
因此,你可以创建如下所示的动画:
float w = 10f, h = 10f;
for (int i = 0; i < 100; ++i) {
uiImageView = UIImage.FromFile("path/tofile.ext").Scale(new SizeF(w, h));
w += (float)i + 5f;
h += (float)i + 5f;
}
这给人一种图像变大的印象。这并不很好,但给你一个想法。要做得更好(比如旋转),我们需要开始查看 CoreGraphics 和 CoreAnimation。
旋转图像 – 第一部分
我在这里假设有一个足够小的 UIImageView 小部件被设置在屏幕中间(比如说 122 x 122)。和之前一样,图像被加载进来了,但这次需要使用 CoreGraphics 图像。
uiImageView.Image = UIImage.FromFile("graphics/image.jpg").Scale(new SizeF(122f, 122f));
uiImageView.Transform = CGAffineTransform.MakeRotation((float)Math.PI / 15f);
图像被加载并旋转。旋转是静态的(换句话说,瞬间)。
对于动画,需要使用 CoreAnimation。然而,在进行旋转动画之前,让我们从更简单的事情开始——将某个东西在屏幕上移动并返回。为了做到这一点,让我们看看一些代码。
Private PointF startPoint;
public override void ViewDidLoad() {
base.ViewDidLoad();
uiImageView.Image = UIImage.FromFile("graphics/image.jpg").Scale(new SizeF(122f, 122f));
startPoint = uiImageView.Center;
UIView.BeginAnimations("moveImage");
UIView.SetAnimationDuration(2);
UIView.SetAnimationCurve(UIViewAnimationCurve.EaseInOut);
UIView.SetAnimationRepeatCount(2);
UIView.SetAnimationRepeatAutoreverses(true);
UIView.SetAnimationDelegate(this);
UIView.SetAnimationDidStopSelector(new Selector("moveImageStopped:"));
uiImageView.Center = new PointF(UIScreen.MainScreen.Bounds.Right – uiImageView.Frame.Width /2, uiImageView.Center.Y);
UIView.CommitAnimations();
}
[Export("moveImageStopped")]
private void moveImageStopped() {
uiImageView.Center = startPoint;
}
关于此代码有两个重要点需要注意。
-
代码在
UIView而不是UIImageView上操作 -
Xamarin.iOS 和底层 Objective-C 之间的绑定在动画和一般绘图(绑定是选择器)中变得非常明显。
基础绑定
在前面的例子中,代码正在为底层 Objective-C 创建一个接口层。与正常代码相比,编译器以稍微不同的方式处理这一点。添加这种 Selector 代码也可以用于其他方式(例如,访问私有 API 代码——尽管应该避免这样做,因为这会使应用程序无法被接受到应用商店)。应该注意的是,与 Objective-C 层的绑定有时可能会导致提交到苹果商店的问题。
代码分析
之前代码的分析可以总结如下:
-
startPoint 是图像开始时的位置。
-
为了告诉应用程序将要有一个动画,需要调用
BeginAnimations。 -
Duration是动画的长度,RepeatCount是动画被调用的次数。 -
RepeatAnimationCurve定义了动画如何进行(在这种情况下,重复动画曲线,曲线不一定是圆上的弧线,它也可以是直线)。 -
EaseInOut从慢速开始动画,然后加速并减速。 -
EaseIn从慢速开始动画。 -
EaseOut在结束时减速。 -
Linear提供了均匀的速度。 -
绑定在动画结束后将图像重置到中心。
-
CommitAnimations设置动画开始。Xamarin.iOS 提供了一个很好的动画使用块的示例,这将进一步支持这个主题。
使用后释放内存
通常,一旦一个类超出作用域,垃圾收集器(GC)就会释放该类内部进程使用的内存。然而,由于 Xamarin.iOS 作为底层 Objective-C 的绑定层工作,有时释放内存变得很重要;这通常发生在处理动画和图形时。
可能最简单的方法是在创建新的 View Controller 时进行清理。
public override void DidReceiveMemoryWarning() {
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
例如,要释放 uiImageView,请按照以下步骤操作:
uiImageView.Release();
如果代码没有引发内存警告,ViewDidDisappear() 也可以用来以相同的方式释放内存。
另一种简单的释放内存的方法是在代码超出作用域后让 GC 做其工作。考虑以下(简化的)代码:
private async void doSomething() {
UIImageView image = new UIImageView(new RectangleF(0, 0, 100,100));
string filename = await GetFileName();
image.Image = UIImage.FromFile(filename);
// do a lot of bits and pieces
if (condition)
return;
else
callNewMethod();
}
代码执行并加载 UIImageView,按照返回的字符串指定的图像。如果 condition 成立(即,它是 true),则方法返回。如果 condition 不成立,方法将跳转到 callNewMethod。这两个问题都不大,除了垃圾收集器(GC)不会在类本身超出作用域之前被调用。因此,UIImageView 控件占用的任何内存仍然被使用,尽管它在一个类中只被使用了三行。由于图像太多和操作太多,内存很快就会消失。
如果你考虑一个普通的动画,可能会有 300 张带有背景的图片,所以内存很快就会被耗尽。
一个简单的解决方案是只创建和使用你需要的东西,并在代码超出作用域后调用 GC。以下代码行展示了如何做到这一点:
private async void doSomething() {
using (UIImageView image = new UIImageView()) {
image.Frame = new RectangleF(0, 0, 100, 100);
string filename = await GetFileName();
image.Image = UIImage.FromFile(filename);
};
// do a lot of bits and pieces
if (condition)
return;
else
callNewMethod();
}
虽然看起来很相似,但正在创建的图像被使用,一旦完成,使用的内存就会被释放,而不是必须等待类超出作用域。
旋转图像 – 第二部分
要使图像旋转,必须使用 CoreGraphics 图像,然后将其转换为位图。以下展示了如何进行旋转。将 RotateCTM 和 TranslateCTM 从正数变为负数(反之亦然)应该会得到不同的结果。
public static UIImage rotateImage(UIImage uiImage) {
UIImage result;
using (CGImage cgImage = uiImage.CGImage) {
CGImageAlphaInfo alpha = cgImage.AlphaInfo;
CGColorSpace colour = CGColorSpace.CreateDeviceRGB();
if (alpha == CGImageAlphaInfo.None)
alpha = CGImageAlphaInfo.NoneSkipLast;
int width = cgImage.Width, height = cgImage.Height;
CGBitmapContext bitmap = new CGBitmapContext(IntPtr.Zero, height,
width, cgImage.BitsPerComponent, cgImage.BytesPerRow,colour, alpha);
bitmap.RotateCTM((float)Math.PI / 2); // rotate right.
bitmap.TranslateCTM(0, -height);
bitmap.DrawImage(new Rectangle(0, 0, width, height), cgImage);
result = UIImage.FromImage(bitmap.ToImage());
bitmap = null; // free memory
}
return result;
}
摘要
在 iOS 上,动画和图形处理是一个广泛的主题。虽然本章对这一主题进行了简要概述,但我建议您阅读由 Michael Bluestein 所著、Pearson Education, Inc.出版的《学习 MonoTouch》。他的书对这一主题的覆盖比这里所允许的更为详细。
第十一章。处理数据
不时地,对于应用程序来说,存储或操作数据是很重要的。随着 LINQ 的出现,操作现在变得极其简单。然而,问题是您需要以某种方式存储数据。幸运的是,使用 SQLite 这也同样简单。
我们将在本章中介绍以下主题:
-
使用 SQLite
-
设置 SQLite 辅助类
-
使用 LINQ
-
在 iOS 上使用 LINQ 的危险
使用 SQLite
SQLite 是一个非常简单但功能强大的数据库系统。本书的范围不包括为您提供使用 SQLite 的高级课程,但了解如何设置和使用该系统将有所帮助。
安装和设置 SQLite
安装可以通过两种方式之一进行;要么你可以从 Xamarin 组件市场安装(它很有用,因为它为你提供了示例,还包括 Android 版本)或者你可以手动下载并安装软件。由于没有额外的库(SQLite 作为一个单独的 C#文件提供),两种方式都很好。
一旦您已经复制了 C#文件或安装了组件,SQLite.net实现就准备好使用了。只需在源文件顶部插入以下using指令即可,操作完成:
using SQLite;
数据库基础
考虑数据库的一个简单方法是将它比作一个古老的卡片索引系统(通常称为卡片索引系统)。信息(数据)可以添加、更新、读取或删除——SQLite 在移动环境中提供了这些功能。数据存储在一个包含表格的文件中,这些表格(表格可以被认为是存放卡片的盒子)包含了您想要的信息。
在可以使用卡片索引系统之前,必须定义存储方法。最简单的方法是创建一个包含原始类型的类。SQLite 只能存储某些类型的数据:integer、real、text、none和numeric。不允许其他类型——这包括数组集合(如List<T>)。编程中通常使用的类型(如string和double)被映射到这些内部类型。
一个表也需要一个主键。这是通常自动递增的主要索引键。在数据类中,这将使用[PrimaryKey, AutoIncrement]定义。
一个简单的数据库类
作为演示,可以使用一个简单的数据库类:
using SQLite;
public class demoRow
{
public demoRow ()
{}
[PrimaryKey, AutoIncrement]
public int ID
{get; set;}
public string Name
{get;set;}
public double Value
{get;set;}
public override string ToString()
{
return string.Format("[demoRow : ID={0}, Name={1},Value={2}]", ID, Name, Value);
}
}
然后,可以将demoTable变量引入数据库。
创建到数据库的连接
在数据库可以使用之前,需要设置与服务器的连接。就像任何类一样,数据库类也需要设置。我将其命名为DataManager。SQLite 需要一个指向数据库文件的路径。
DataManager dm = new DataManager("path_to_database");
dm.Setup(); // calls the creation of the database code
上述代码设置了一个DataManager实例。为了使数据库能够在整个应用程序中使用,以下内容应添加到AppDelegate类中:
private static DataManager dm{ get; set; }
在 DataManager 类中,需要一个锁(以防止任何时刻对数据库进行多个操作),以及数据库路径的本地副本。
public DataManager(string path)
{
dataLock = new object();
dataBasePath = path;
}
private string dataBasePath;
private object dataLock;
public string DataPath{
get
{
return dataBasePath;
}
}
最后,需要在数据库中设置表。
public bool Setup()
{
lock(dbLock)
{
try
{
using (SQLiteConnection sqlCon = newSQLiteConnection(DBPath))
{
sqlCon.CreateTable<demoRow>();
}
return true;
}
catch (SQLiteException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
}
}
你会注意到,你需要定义一个包含以下常量的类:
public const string DBClauseSyncOff = "PRAGMA SYNCHRONOUS=OFF;";
public const string DBClauseVacuum = "VACUUM;";
DBClauseVacuum 常量用于最后的执行查询。DBClauseSyncOff 常量用于第一个。
到目前为止,你可能已经注意到了使用 SQLite 的某些方面。它被用作类中的正常方法。这样是可以的。
设置 SQLite 辅助类
通常使用 SQLite 需要你全局创建并存储连接(以节省设备资源并减少安全问题的可能性),然后在每次调用数据库时设置一组查询和问题。即使是简单的数据库,这也可能导致大量重复的代码,代码行数越多,出现错误的可能性就越大。
辅助类封装了你需要的所有功能,并且很容易编写。
编写辅助类方法
正如我一开始提到的,数据库允许你从/向表中读写数据。首先,能够读取数据是有意义的。以下有两个类:第一个返回 List<demoRow>,另一个返回给定 ID 的名称。
Public List<demoRow> getAllListOfRows()
{
lock (dbLock)
{
using (SQLiteConnection sqlCon = newSQLiteConnection(this.DBPath))
{
sqlCon.Execute(Constants.DBClauseSyncOff);
sqlCon.BeginTransaction();
List<demoRow> toReturn = new List<demoRow>();
toReturn = sqlCon.Query<demoRow>("SELECT * FROMdemoRow");
return toReturn.Count != 0 ? toReturn : newList<demoRow>();
}
}
}
public string getNameForID(int id)
{
lock (dbLock)
{
using (SQLiteConnection sqlCon = newSQLiteConnection(DBPath))
{
sqlCon.Execute(Constants.DBClauseSyncOff);sqlCon.BeginTransaction();string toReturn = string.Empty;toReturn = sqlCon.ExecuteScalar<string>("SELECT NameFROM demoRow WHERE ID=?", id);
return !string.IsNullOrEmpty(toReturn) ?toReturn : "No name found";
}
}
}
两种方法之间几乎没有区别,除了数据库调用。列表版本需要一个查询——这用于非原始类型的数据,并期望返回数据。ExecuteScalar 方法期望返回原始类型的数据行。
访问这些辅助方法与访问任何其他方法相同。
string name = dm.getNameForID(3);
或者
List<demoRow> dT = dm.getListOfTables();
向数据库添加数据
数据添加以插入或更新形式出现,与读取不同,数据添加需要捕获失败的情况,并且数据库需要回滚到添加数据尝试之前的状态。
幸运的是,插入或更新可以在一个方法中处理。
public void AddOrUpdateTable(demoRow dTRow){
lock (dataLock)
{
using (SQLiteConnection sqlCon = new SQLiteConnection( DBPath))
{
sqlCon.Execute(Constants.DBClauseSyncOff);
sqlCon.BeginTransaction();
try
{
if (sqlCon.Execute("UPDATE dataRow SET " +"ID=?, " + "Name=?, " +"Value=? WHERE " +"ID=?",dRow.ID,dRow.Name,dRow.Value,dRow.ID) == 0)
{
sqlCon.Insert(dRow, typeof(demoRow));}
sqlCon.Commit();
}
catch (Exception ex)
{
Console.WriteLine("Error in AddOrUpdateTable :{0}-{1}", ex.Message, ex.StackTrace);sqlCon.Rollback();
}
}
}
}
再次,为了使前面的代码能够正常工作,需要将 demoRow 类的单个实例传递到方法中。使用 demoRow 的 List 很容易处理。
public void AddOrUpdateTables(List<demoRow>rows)
{
foreach(demoReow row in rows)
AddOrUpdateTable(row);
}
使用 LINQ 进行数据操作
持续访问数据库会对它所在的系统造成压力,如果你考虑任何时刻正在使用的数据库数量,很快就会清楚,不断击打 SQL 服务的开销会很高。
LINQ 允许将数据库样式查询应用于数据,并输出结果。虽然有许多优秀的书籍(其中许多是免费的),但我将介绍一些在 iOS 应用程序中使用 LINQ 的便捷方法。
虽然 LINQ 非常强大,但在 iOS 上编码时,并不是所有使用 LINQ 的操作都不会出现问题。主要原因在于 iPhone 的工作方式。简单来说,iOS 想要知道将要发生什么提前时间(AOT)。它不喜欢惊喜,并且真的不喜欢它不知道的数据结构。例如:
List<string> data = new List<string>();
之前的代码不应该引起任何问题。但换一个角度思考——List的大小将会有多大?当你提前工作时,空间分配通常是有限的。(例如,一个int值的数组可能有 20 个值的内存预留,没有更多,AOT 系统喜欢这样——它提前知道需要预留 20 个int。)
考虑到这一点,当在应用程序中使用 LINQ 时,你可能会遇到随机崩溃——这不太可能,但它可能发生,并且值得提及。
LINQ – 快速浏览
对于我的示例,我将使用本章中已经使用的demoRow表。
List<demoRow> myRow = dm.getListOfRows();
在返回的List中某个地方有一个名为Fred Moriarty的名字,我想从List中获取具有该名字的类的实例。我知道列表中只有一个这样的实例。
var demo = myRow.SingleOrDefault(t=>t.Name == "Fred Moriarty");
之前的代码获取表并返回单个实例。如果找不到该名字,则返回null(或默认值)。
假设我的列表在Name字段中有多个Fred Bloggs实例。
var bloggs = myTRow.Where(t=>t.Name == "Fred Bloggs").ToList();
在 LINQ 出现之前,这将会是一项相当缓慢的工作,并且可能需要如下代码:
List<demoRow> bloggs = new List<demoRow>();
foreach(demoRow blog in bloggs)
{
if (blog.Name == "Fred Bloggs")
bloggs.Add(blog);
}
之前的示例非常简单。LINQ 也可以执行非常复杂的操作,例如:
var res = from inviter in ContactListfrom tester in inviteswhere inviter.UserID == tester.UserIdselect tester;
这里有两个列表(ContactList和invites)。LINQ 查询创建了两个循环,并在外层循环的UserID与内层循环的UserId匹配时选择实例测试器。结果是几乎瞬间的。
LINQ 中的 SELECT 和 WHERE – 常见的混淆原因
每个人在某个时候都会遇到的一个非常常见的 LINQ 错误是将WHERE语法与SELECT语法混淆。
WHERE语法是一个条件(例如,WHERE ID==311或WHERE A==B)。它只返回设置的条件。在以下示例中,testClass是一个列表,它包含一个有字符串Value的类。
var retString = testClass.Where(t=>t.Value == "fred").ToList();
retString变量将包含所有来自testString的结果的List<string>,其中Value == "fred"。
SELECT语法返回传入对象中所有项的内容。结果可能是项本身,也可能是其他内容。
var retString = inString.Select(t=>t.ToUpper()).ToList();
之前的代码将inString的内容转换为大写,并输出到一个列表中。
var retString = inString.Select(t=>t.Value, Func<inString,outString>).ToList();
这里Func<inString, outString>将inString的元素转换为outString,并将元素作为outString列表输出。
在 LINQ 中使用 Select
看以下示例,并记住Select执行的是转换操作:
string[] teams = {"Liverpool", "Everton", "Oldham", "Leeds"};
var result = teams.Select(t=>t.ToUpper());
foreach (string team in result)
{
Console.WriteLine(team);
}
这将是输出:
LIVERPOOL
EVERTON
OLDHAM
LEEDS.
但是发生了什么? Select 语句在字符串数组 teams 上执行。然后它指定了一个 lambda 表达式 (t=>),它将表达式转换为大写 (ToUpper()).
Select 语句(如前代码所示)也有一个重载的方法。同样,它也是一个转换方法。
string[] teams = {"Liverpool", "Everton", "Oldham", "Leeds"};
var trans = teams.Select(teams, index) =>new {index, str = teams.Substring(0, index)});
如果代码运行并使用了合适的输出,你会看到 E,Ol,Lee——起始索引是 0,所以没有看到任何字符。
替换 SQL 为 LINQ
根据您的需求,用一系列 List<T> 类替换 SQLite 数据库可能是一个更好的选择,并使用 LINQ 来替换 SQL 查询。例如,如果您有一个非常简单的数据库(例如我们的 demoTable 数据库),它具有非常有限的用于操作的范围,那么使用类列表可能是一个更好的主意,您可以像通常那样向其中添加内容,并使用 LINQ 执行查询。由于没有对数据库服务器的打击,这可能会从应用程序中获得更快的响应时间。
对于结构更复杂的表格,其中 SQL 本身执行对另一个表的 JOIN 操作,或者涉及复杂的操作,使用 LINQ 可能不会得到一个可用的系统。
LINQ 可以执行 JOIN 条件,以及 SQLite 可以执行的大多数其他功能——只是不那么容易。
但是,请注意,iPhone 上的 LINQ 可能会决定直接崩溃,而 SQLite 不会。
摘要
iPhone 上的数据存储可以很简单,也可能充满问题。为了安全和可靠性,iPhone 上首选 SQLite 选项。对于速度,LINQ 无敌,但您必须确保在使用 LINQ 时在物理设备上测试 LINQ 项目。
第十二章. 外设
拥有智能手机的全部意义在于它不仅仅是一部电话,它还是 GPS、媒体中心、消息系统以及视频系统。简而言之,你口袋里的那个小设备是传说中的“万能的,但精通所有技艺。”
在本章中,我们将涵盖以下主题:
-
使用相机
-
地图和 GPS
-
手机存储
-
打电话
-
发送和接收短信
-
访问互联网
-
多媒体
使用相机
iPhone 上的相机能够录制静态图像和视频。我们将很快处理视频。
相机可以通过两种方式之一访问。Xamarin 已经在其组件商店中发布了 Xamarin.Mobile,它提供了一个跨平台的方法来访问 GPS、相机和通讯录。为了完整性,我们将涵盖原生和组件版本。
访问相机(Xamarin.Mobile)
Xamarin.Mobile 组件提供了一个简单的方法来访问相机。一个简单的示例方法如下:
var picker = new MediaPicker ();
if (!picker.IsCameraAvailable)
Console.WriteLine ("No camera!");
else {
picker.TakePhotoAsync (new StoreCameraMediaOptions {
Name = "test.jpg",
Directory = "MediaPickerSample"
}).ContinueWith (t => {
if (t.IsCanceled) {
Console.WriteLine ("User canceled");
return;
}
Console.WriteLine (t.Result.Path);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
Xamarin 组件的问题在于它目前没有提供访问前置相机的功能;然而,前面的代码将保存点击的图片。
访问相机(原生)
相机是通过 UIImagePickerController 访问的。要使用它,首先检查设备是否实际上有相机(iPod Touch 没有相机,模拟器也没有)。为此,检查 IsSourceTypeAvailable 布尔值。
var myCamera = UIImagePickerControllerSourceType.Camera;
if (UIImagePickerController.IsSourceTypeAvailable(myCamera))
{
UIImagePickerController myCameraPicker = newUIImagePickerController();
myCameraPicker.SourceType = myCamera;
myCameraPicker.Delegate = new myImageDelegate(this);
PresentModalViewController(myCameraPicker, true);
}
代理处理关闭模态窗口以及您想要的其他任何过程(例如显示图片或保存图片)。一个简单的代理示例如下(如果需要可以扩展):
public class myImageDelegate : UIImagePickerControlDelegate
{
private UIViewController myController;
public myImageDelegate(UIViewController control) {
myController = control;
}
public override void FinishedPickingMedia (UIImagePickerController thePicker, NSDictionary I) {
myController.DismissModalViewControllerAnimated(true);
}
}
保存到相册(原生)
以下代码将放置在 myCameraPicker.Delegate 代码中的 FinishedPickingMedia() 方法内,这将以原生方式保存图片到相机胶卷:
UIImage myImage = UIImage.FromFile(filename);
myImage.SaveToPhotoAlbum(delegate (UIImage image, NSError err) {
Console.WriteLine("Image saved fine"); });
GPS 和地图
这也由 Xamarin.Mobile 组件部分覆盖(由于该组件支持的其他平台没有等效方法,Xamarin.Mobile 组件中没有复制 Core Location 的全部功能)。幸运的是,该组件和 Core Location 可以无缝协作。
使用 Xamarin.Mobile 的 GPS
Xamarin.Mobile 组件允许您通过 PositionChanged 事件监听设备的位置变化,并在位置发生变化时采取行动。以下代码演示了如何使用它:
var iPhoneLocationManager = new Geolocator();
iPhoneLocationManager.DesiredAccuracy = 5;
iPhoneLocationManager.StartListening(1, 10);
iPhoneLocationManager.PositionChanged += (object sender,PositionEventArgs e) => {
double geoLocationLong = e.Position.Longitude;
double geoLocationLat = e.Position.Latitude;
iPhoneLocationManager.StopListening();
};
在分析前面的代码行时,我们遇到了以下术语:
-
DesiredAccuracy:这是在触发事件之前所需的距离(以米为单位)。这本身可能会引起问题。任何低于该值的值都意味着事件不会被触发。值太大,精度就会受到影响。 -
StartListening:它接受两个参数:minimumTime和minDistance。在这种情况下,它应该在第一分钟内返回,或者如果手机移动超过 10 米。 -
StopListening:它停止监听服务。
Xamarin.Mobile 模块还提供了一个异步方法——GetPositionAsync,用于异步获取您的位置。
private TaskScheduler sched =TaskScheduler.FromCurrentSynchronizationContext();
private CancellationTokenSource cancel;
geolocator.GetPositionAsync (timeout: 10000,cancelToken: cancel.Token, includeHeading: true).ContinueWith (t=> {
if (t.IsFaulted)
Console.WriteLine("Position faulted : {0}".((GeolocationException)t.Exception.InnerException).Error);
else if (t.IsCanceled)
Console.WriteLine("Canceled");
else {
Console.WriteLine("Timestamp {0}",t.Result.Timestamp.ToString("G"));
Console.WriteLine("La: {0}", t.Result.Latitude.ToString("N4"));
Console.WriteLine("Lo: {0}", t.Result.Longitude.ToString("N4"));}}, sched);
存储在 Result 中的返回值也提供了速度。这并不可靠。使用您自己的方法会更准确,但要做到这一点,您需要知道您已经行驶了多远。
计算您的速度
CoreLocation 命名空间中有一个名为 DistanceFrom 的方法,用于计算距离。该方法的工作方式如下:
-
获取新位置。
-
获取旧位置。
-
创建两个
CLManager实例。 -
将两个位置的坐标分别放入两个实例中。
-
使用
DistanceFrom。
结果的 double 值为您提供以米为单位的距离和以秒为单位的时间,因此您所行驶的速度是以米/秒(即距离/时间)计算的。然而,这里的计算问题可能是考虑了鸟的飞行方式,而不是您的移动方式。例如,如果期望的精度太低,超时时间太高,使用 DistanceFrom 的方法仍然可以工作;然而,如果距离不是 100 米,而是显然是 35 米(考虑鸟儿直接飞越田野,而不是沿着道路行走,左转,右转或绕行然后再次左转),距离会更短,因此速度也会相应地改变。
使用 Core Location
Core Location 是 GPS 的默认框架。它是一个强大的系统,用于确定设备的定位,其作用范围广泛,包括如何执行。与 iOS 中的许多其他功能一样,在设置 Core Location 框架时需要代理。代理通常处理事件。
设置 Core Location 和代理
如以下代码所示,可以设置并使用 Core Location 和代理:
private CLLocationManager locManager;
public override void ViewDidLoad() {
base.ViewDidLoad();
locManager = new CLLocationManager() {
DesiredAccuracy = CLLocation.AccuracyBest,
};
}
CLLocationManager 被设置为期望的精度尽可能高。接下来,为了使其有用,我们首先需要用以下代码捕获任何错误:
locManager.Failed += (object sender, NSErrorEventArgs e) => {
UIAlertView alert = new UIAlertView() {
Title = "Location manager failed",
Message = string.Format("The following error was encountered –{0}", e.Error.ToString())
}.Show();
locManager.StopMonitoring();};
一旦错误系统被设置,就需要处理定位变化的监控。
locManager.LocationsUpdated += (object sender,CLLocationsUpdatedEventArgs e) => {
CLLocation[] locs = e.Locations;
Console.WriteLine("lat = {0},long = {1}",locs[0].Coordinate.Latitude,locs[0].Coordinate.Longitude);
};
如果应用被暂停,则需要处理以下事件:
bool paused = false;
locManager.LocationUpdatesPaused += delegate {
if (!paused)
locManager.StopUpdatingLocation();
else
locManager.StartUpdatingLocation();
paused = !paused;
};
要开始更新监控的检查,请使用以下行代码:
locManager.StartUpdatingLocation();
要停止更新监控的检查,请使用以下行代码:
locManager.StopUpdatingLocation();
CLLocationManager 类还允许您监控您移动的方向。
locManager.UpdatedHeading += (object sender,CLHeadingUpdatedEventArgs e) => {
CLHeading heading = e.NewHeading.TrueHeading;Console.WriteLine("New heading : {0}", heading.ToString());
};
locManager.StartUpdatingHeading();
查找用户的位置
拥有坐标是件好事,但谁又能真正说出它们代表什么?例如,如果我要给你 53.431/-2.956 的经纬度坐标,你会知道那是什么地方吗?可能性很大,你不会知道!
这就是反向地理编码发挥作用的地方。MKReverseGeocoder 类位于 MonoTouch.MapKit 命名空间中。
private MKReverseGeocoder geoCoder;
geoCoder = new MKReverseGeocoder(locManager.Location.Coordinate);geoCoder.Delegate = new ReverseGeocoder(this);geoCoder.Start(); // geoCoder.Stop() stops the geoCoder
与locManager不同,ReverseGeocoder类必须放置在委托中。MKReverseGeocoder不包含任何可以附加的事件。
private class ReverseGeocoder : MKReverseGeocoderDelegate {
UIViewController view;
public ReverseGeocoder(UIViewController myView) {
view = myView;
}
public override void FoundWithPlacemark(MKReverseGeocoder geocoder, MKPlacemark placemark) {
Console.WriteLine("Address");
Console.WriteLine("{0}, {1}, {2}, {3}, {4}",placemark.SubThoroughfare,placemark.Thoroughfare,placemark.Locality,placemark.AdministrativeArea,placemark.Country);
}
}
Xamarin.Mobile组件不包含执行ReverseGeocoder操作的能力。但编写一个并不困难。
在原始示例中,LocationsUpdated方法被作为一个内联示例给出。如果将其更改为指向一个方法,则可以执行ReverseGeolocation方法:
private void OnPositionChanged(object sender,PositionEventArgs e) {
double lng = e.Position.Longitude;
double lat = e.Position.Latitude;
CLLocation clloc = new CLLocation(lat, lng);
CLLocation oldLoc = new CLLocation(prevLat,prevLong);
CLGeocoder geoRevGeo = new CLGeocoder();
geoRevGeo.ReverseGeocodeLocation(clloc, GetAddressFromLoc);
}
private void GetAddressFromLoc(CLPlacemark[] place,NSError error) {
if (place.Length == 0) {
Console.WriteLine("Don't know where I am – help!");
return;
}
else {
Console.WriteLine("Address");
Console.WriteLine("{0}, {1}, {2}, {3}, {4}",place.SubThoroughfare,place.Thoroughfare,placemark.Locality,placemark.AdministrativeArea,placemark.Country);}
}
添加地图
GPS 的最后一部分是添加地图。为此,需要通过在 Xcode 中添加或在代码中添加来使用Map视图。就我的目的而言,我将假设它是通过 Xcode 添加的,并命名为mapViewer。有三种类型的地图可供选择,因此还添加了一个UISegment控件。

需要设置mapViewer对象:
public override void ViewDidLoad() {
base.ViewDidLoad();mapViewer.ShowsUserLocation = mapViewer.ZoomEnabled = true;
mapViewer.MapType = MKMapType.Standard;
mapViewer.Region = new MKCoordinateRegion(
new CLLocationCoordinate2D(53.431, -2.956),
new MKCoordinateSpan(0.5, 0.5)
);mapViewer.ScrollEnabled = mapViewer.UserInteractionEnabled =true;
}
前面代码中传入的坐标对应的结果位置在以下屏幕截图中显示:

下一个阶段是使UISegment控件生效:
mapType.ValueChanged += delegate {
switch (mapType.SelectedSegment) {
case 0: mapViewer.MapType = MKMapType.Standard;
break;
case 1: mapViewer.MapType = MKMapType.Satellite;
break;
case 2: mapViewer.MapType = MKMapType.Hybrid;
break;
}
};

如果您不知道,地理位置是利物浦足球俱乐部的主场安菲尔德。
一些属性需要简要解释:
-
ShowUserLocation: 显示一个蓝色点来显示用户的位置 -
ZoomEnabled: 允许用户进行缩放 -
ScrollEnabled: 允许用户滚动查看视图 -
UserInteractionEnabled: 启用放置在地图上的图钉在被点击时(或不)做出响应 -
MKCoordinateSpan(0.5,0.5): 这是缩放设置——数字越小,初始视图的缩放越大
添加图钉
当谈到地图时,图钉非常有用。虽然一个小蓝点很整洁,但图钉实际上显示了您所在的位置,并且可以添加信息到它。
MKUserLocation loc = new MKUserLocation() {
Title = "Anfield stadium",
Coordinate = new CLLocationCoordinate2D(53.431, -2.956),
Subtitle = "Home of Liverpool FC",
};mapViewer.AddAnnotationObject(loc);
最后,始终将地图居中并固定在屏幕上
mapViewer.SetCenterCoordinate(loc.Coordinate, true);

备注
图钉在模拟器上不一定总是有效。

手机存储
.NET 在SpecialFolders枚举中指定了多个位置(例如Program Files、My Music和My Pictures;完整列表可以在 Microsoft 网站上找到)。由于 iOS 设备的安全限制,其中只有少数可用。最安全的方法是将任何用户数据保存到My Documents。在My Documents中,您可以创建自己的目录并按自己的喜好使用它们。
打电话
这听起来可能有些荒谬。为什么你想编写代码来打电话?按照真正的开发者传统,答案是为什么不呢?重要的是要注意,字符串号码只是那样。它不能包含空格、连字符、括号或加号(+);它只能包含数字。
private void callNumber(string number) {
string phoneURLString = string.Format("tel:{0}", number);
NSUrl phoneURL = new NSUrl(phoneURLString);
UIApplication.SharedApplication.OpenUrl(phoneURL);
}
继续前进...
发送和接收短信
iPhone 自带内置的消息软件。然而,有些时候您需要在发送消息之前对其进行编码。然而,苹果公司出于其智慧,不允许您在不通过其自己的消息软件的情况下发送消息。这并不是说您不能以其他方式发送消息(例如通过专门的网站或消息服务),但对于标准用户来说,您不能。
您不能以截获或阻止短信的方式编写代码。iOS 没有提供公开可用的方法来截获和读取短信。考虑到这一点,发送短信是一个相对简单的事情。"MFMessageComposeViewController"位于"MonoTouch.MessageUI"命名空间中。
private void sendTextMessage(string number, double myLat,double myLong) {
if (MFMessageComposeViewController.CanSendText) {
MFMessageComposeViewController message = newMFMessageComposeViewController();
message.MessageComposeDelegate = newCustomMessageComposeDelegate();
message.Recipients = new string[] { number };
message.Body = string.Format("Help! I am currently athttps://maps.google.com/maps?q={0},{1}&z=18 and needassistance", myLat, myLong);
NavigationController.PresentModalViewController(message,true);
}
}
public class CustomMessageComposeDelegate :MFMessageComposeViewControllerDelegate {
public override void Finished(MFMessageComposeViewController controller,MessageComposeResult result) {
if (result == MessageComposeResult.Failed ||
result == MessageComposeResult.Cancelled) {
UIAlertView alert = new UIAlertView() {
Title="Message sending error",
Message="Your message failed to send"
}.Show();
}
controller.DismissViewController(true, null);
}
}
访问互联网
通过UIWebView控制器访问互联网。在尝试访问网站之前,确保有活跃的网络连接是个好主意。这是通过NetworkReachability类完成的。
using MonoTouch.SystemConfiguration;
NetworkReachability reach = new NetworkReachability("www.bbc.co.uk");
NetworkReachabilityFlags flags = new NetworkReachabilityFlags();
reach.TryGetFlags(out flags);
Console.WriteLine("Network flag = {0}", flags.ToString());
WriteLine命令将导致以下标志之一:
| 标志 | 值 |
|---|---|
Reachable |
主机可达 |
IsWWAN |
通过 EDGE、3G 或 4G 建立连接 |
IsLocalDevice |
连接到本地设备 |
ConnectionAutomatic |
自动建立连接。这是ConnectionOnTraffic的别名。 |
ConnectionOnTraffic |
结合Reachable和请求数据时建立连接 |
ConnectionOnDemand |
在连接开始时发生。连接在套接字连接后发生。 |
ConnectionRequired |
主机可达,但必须建立连接 |
IsDirect |
直接建立连接 |
InterventionRequired |
连接到主机时,用户必须执行某些操作 |
TransientConnection |
主机可达,但通过一个启动和停止的系统(如 PPP 或任何其他非持久性网络连接) |
假设存在网络连接,下一步是加载网页。
我在第三章中介绍了加载网页和一些设置,视图和布局。也可以从您动态生成的数据中加载网页。以下代码示例可以简单地演示这一点。首先,按照以下方式设置应用程序结构,在.xib文件中使用UIWebview:

HTML 目录可以按照您的意愿命名。我保持简单,只叫它 HTML。当在您的应用程序中生成自己的 HTML 时,您可以选择:
-
从头开始生成自己的文件,保存并获取输出
-
使用
StringBuilder生成 HTML 文件并输出该字符串 -
混合前两个选项
从本质上讲,两种生成方法相同,区别在于使用StringBuilder生成字符串或连接字符串。考虑以下代码行:
string html = "<html>\n";
html += "<title>Hello World</title>\n";
html += "<body>\n";
html += "<h1>Hello World!</h1>\n";
html += "</body>\n";
html += "</html>";
上述代码行应该执行你预期的操作,一旦通过以下代码传递给UIWebview:
webView.LoadHTMLString(string s, NSUrl baseurl);
//The baseurl here would be null.
当这些代码行执行其工作,基于应用程序内部的数据使用这些代码行创建你的网页可能会很耗时。一个更简单的方法是从应用程序中的某些 HTML 片段中拉取页眉和页脚。
然而,要从应用程序内部读取文件,需要采取一些步骤。第一步是将要构建的 HTML 片段设置为BundleResource。你将加载的是属于捆绑包的文件,而不是应用程序可写文件夹中的文件。第二步是将 HTML 捆绑包加载到源中:
var documents = NSBundle.MainBundle.BundlePath;
StringBuilder sb = new StringBuilder();
sb.Append(File.ReadAllText(Path.Combine(documents,"HTML/top.html")));
NSBundle.MainBundle.BundlePath是已安装应用程序的路径。下一步是添加数据;在我的例子中,我添加了一个联赛表格:
var leagues = (from t in teams
from p in t.points
orderby p
select t).Take(4).ToList(); // takes top 4 only
sb.Append(@"<table width=100%>");
sb.Append(@"<tr width=100%>");
sb.Append(@"<td width=70%>Team name</td>");
sb.Append(@"<td width=10%>Played</td>");
sb.Append(@"<td width=10%>G Diff</td>");
sb.Append(@"<td width=10%>Points</td>");
for (int i = 0; i < 4; ++i) {
sb.Append(@"<tr width=100%>");
sb.Append(@"<td width=30%>" + teams[i].TeamName + "</td>");
sb.Append(@"<td width="10"%>" + teams[i].TeamPlayed.ToString() +"</td>");
sb.Append(@"<td width="10"%>" + teams[i].TeamGDiff.ToString() +"</td>");
sb.Append(@"<td width="10"%>" + teams[i].TeamPts.ToString() +"</td>");
sb.Append(@"</tr>");
}
sb.Append(@"</table>");
sb.Append(File.ReadAllText(Path.Combine(documents,"HTML/bottom.html")));
顶部的表格部分可以很容易地成为它自己的 HTML 片段。一旦完成最终的追加,HTML 就准备好了。
webView.LoadHTMLString(sb.ToString(), null);
记住,你正在在应用程序内部创建自己的网页。如果你想包含样式表,你可以;如果你想包含 JavaScript,你也可以;唯一的限制是你可以使用启用 JavaScript 的移动 Safari。
多媒体
这是一个非常广泛的话题。幸运的是,使用相机的部分已经在本章的开头进行了介绍。几乎你需要的一切都在MonoTouch.MediaPlayer命名空间内。就像使用相机一样,首先检查设备是否具有视频功能是非常重要的,方式与检查相机完全相同(实际上,它使用与相机相同的IsSourceTypeAvailable(myCamera)命令)。
播放视频
与webView一样,视频可以是设备外部的或内部的;如果是内部的,它可能是捆绑包的一部分(这不是一个好主意,因为视频会占用设备大量空间,从而导致下载时间过长),或者位于My Document区域(已下载)中,或者在照片胶卷中。
外部 URL
我在这里要使用的是 YouTube 上的视频(你可以选择任何你喜欢的视频)。
var videoPlayer = new MPMoviePlayerController(NSUrl.FromString("http://www.youtube.com/watch?v=cVikZ8Oe_XA"));
videoPlayer.Play();
内部源
从内部源播放视频可以以类似于外部视频文件的方式执行:
var videoPlayer = new MPMoviePlayerController(NSUrl.FromFilename("myVideo.mp4"));
videoPlayer.Play();
从照片库
从相册中选择视频的方式与从照片库中选择图片的方式几乎相同。
UIImagePickerController ipcPicker = new UIImagePickerController()
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = new [] {"public.movie"},
Delegate = new ImagePickerDelegate(this)
};
上述代码遍历照片库,寻找任何返回public.movie类型的文件。如果找到,该文件将被添加到数组中,并且可以通过代理看到。
录制视频
这与拍照并没有实质性的区别,只是你有一些可以调整的额外参数,例如VideoQuality和AllowEditing。
要录制视频
录制视频是一个简单的任务,如下面的代码所示:
var camera = UIImagePickerControllerSourceType.Camera;
UIImagePickerController ipcVideo = new UIImagePickerController()
{
SourceType = camera,
MediaTypes = new [] {"movies.public"},
AllowEditing = true,
VideoQuality = UIImagePickerControllerQualityType.Medium,
Delegate = new ImagePickerDelegate(this)
};
在前面的示例中,AllowEditing已设置为true,这意味着用户可以编辑此视频。如果这是意图,则应使用UIVideoEditorController执行编辑。此控制器允许三个事件:Failed(编辑由于某些原因失败),UserCancelled(不言而喻),和Saved(用户选择保存编辑;路径在e.Path事件中返回)。
保存视频
视频处理完毕后,下一步是保存视频:
UIVideo.SaveToPhotoAlbum(videoPath, delegate (string path,NSError error) {
Console.WriteLine("Video saved.");
});
音频系统
iPhone 和 iPad 等设备(以及大多数苹果设备)都配备了出色的音频系统,这允许播放高质量的音频,并且能够进行录音。这些功能可以通过AVAudioPlayer类或SystemSound获得。如果文件位于应用程序包中,则在构建应用程序时必须将其设置为Content。
播放
如果音频播放时间少于或超过 30 秒,可以认为是短或长。一般来说,SystemSound最适合用于时长少于 30 秒的音频文件,以及未压缩的音频格式,如.wav和.caf(核心音频文件)。SystemSound不支持 MP3 文件。
短文件
SystemSound方法是一种快速简便地播放音频文件的方式,开销非常小。
var sound = SystemSound.FromFile("myAudio.caf");
sound.PlaySystemSound();
如果你需要播放音频文件,但你处于需要安静的地方(比如图书馆),设备可以通过文件长度振动。
sound.Vibrate.PlaySystemSound();
长的(和压缩的)文件
在这种情况下,AVAudioPlayer类发挥了作用,允许你有效地调整功率级别(通道上的音量),暂停,播放和停止音频文件。它还处理压缩音频格式,如 MP3。
设置功率级别
在设置功率级别之前,AveragePower和PeakPower.MeteringEnabled布尔值必须设置为true,并且必须调用名为UpdateMeters()的方法。然后只需将AveragePower(uint)或PeakPower(uint)设置为所需的值(以 dB 为单位)。
播放音频文件
播放音频文件并不困难;选择文件并告诉设备播放。以下代码演示了如何操作:
var fileToPlay = AVAudioPlayer.FromUrl(NSUrl.FromFilename("myAudio.mp3"));
fileToPlay.Play();
fileToPlay.FinishedPlaying += delegate {
fileToPlay.Dispose(); // clean up
};
调整音量
如果你不希望贝多芬的第九交响曲从你的 iOS 设备中播放出来,降低音量是个好主意,如下所示:
using MonoTouch.MediaPlayer
var mpPlayer = new MPMusicPlayerController();
mPlayer.Volume = 0.01f; // max volume – range 0 to 1
然而,设置音量级别时有一个注意事项:不要将其设置为零。这会惹恼 iOS 设备,并随后会惹恼用户,因为设备会非常乐意提醒你音量已设置为0。由于该值是一个浮点数,0.01与0一样可以有效地静音设备。
录制音频
录音并不像播放那样简单。虽然 AVAudioRecorder 类做了很多工作,但程序员也需要做大量工作来录制音频。关键点是要记住,在发生任何操作之前,必须设置 NSDictionary。这个字典包含重要信息,例如音频类型、采样率、质量等。
设置音频 NSDictionary
NSDictionary(以及以 NS 开头的任何其他内容)是 Xamarin.iOS 利用 Objective-C 绑定来允许在 iOS 上使用 .NET 框架进行开发的接口。因此,它不能像普通字典那样设置。为了克服这个障碍,可以使用一个通用的 NSObject 对象,一个用于设置,另一个用于描述(这相当于 .NET 中的值和键)。
var settings = new NSObject[] {
NSNumber.FromFloat(22050.0f),
NSNumber.FromInt32((int)AudioFileType.WAVE),
NSNumber.FromInt32(2),
NSNumber.FromInt32((int)AVAudioQuality.Min)
};
var keysToSettings = new NSObject[] {
AVAudioSettings.AVSampleRateKey,
AVAudioSettings.AVFormatKey,
AVAudioSettings.AVNumberOfChannelsKey,
AVAudioSettings.AVEncoderAudioQualityKey
};
var dict = NSDictionary.FromObjectsAndKeys(settings,keysToSettings);
准备录音
下一步是设置录音器本身以及保存音频文件的位置。
var docs = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var audio = NSUrl.FromFilename(Path.Combine(docs,"testaudio.wav");
var error = new NSError(); // catch the errors
录制音频文件
最后,是时候开始录音了。当文件正在记录时,它也会保存在设备上。录音完成后,需要销毁录音器对象。幸运的是,我们可以通过使用 RecordFor("float time") 方法来控制录音的时长。
var myRecorder = AVAudioRecorder.ToUrl(audio, dict, out error);
myRecorder.FinishedRecording += delegate {
Console.WriteLine("Audio file created");
myRecorder.Dispose();
};
myRecorder.RecordFor(10f);
摘要
使用 iOS 设备提供的子系统非常简单,只要记住限制条件,例如只能发送文本而不能接收。设备提供的功能远超普通用户所见,只需稍加想象就能看到如何利用这些功能创建真正优秀的应用程序;从地图到网页视图再到拨打电话,一切都在你的掌控之中。现在,开始吧!
第十三章:用户偏好
有时,用户能够存储偏好设置(例如联系人的铃声)是非常有用的。幸运的是,iOS 提供了一个内置的偏好设置系统。如果你只想为 iOS 编写应用程序,这没问题,但如果目的是要创建一个可以用于任何可以使用.NET 的平台的应用程序,则需要不同的方法。
本章涵盖的主题包括:
-
使用内置偏好设置系统
-
自定义偏好设置系统
内置系统
首先,需要向应用程序中添加一个名为Settings.bundle的特殊文件夹,其中包含一个名为Root.plist的文件。
这不是一个普通文件,.plist文件需要将Build Action设置为Content。

.plist文件是一个 XML 文件,条目以常规字典对象的形式存储,形式为<key><type>,其中类型可以是任何东西(例如string、int和double)。
例如,对于创建以UISlider类显示的偏好设置,它将按照以下代码所示进行存储:
<dict>
<key>Type</key>
<string>UISlider</string>
<key>Key</key>
<string>My slider</string>
<key>DefaultValue</key>
<int>40</int>
<key>MinimumValue</key>
<int>0</int>
<key>MaximumValue</key>
<int>75</int>
</dict>
理解起来很简单,但文件会根据存储的信息量快速增长。
读取和写入.plist 文件
幸运的是,从.plist文件中读取很简单。所需的一切就是识别值的键以及了解值类型以正确调用相关方法。
int read = NSUserDefaults.StandardDefaults.IntForKey("DefaultValue");
string name = NSUserDefaults.StandardDefaults.StringForKey("Key");
写入文件可以通过两种方式完成。最简单的方法是直接写入文件,如下面的代码所示:
NSUserDefaults.StandardDefaults["DefaultValue"] = 34;
NSUserDefaults.StandardDefaults["Key"] = "Hello mum!";
另一种方法是调用NSNotificationCenter类,该类向应用程序广播通知。NSUserDefaults类使用这个系统在Settings值改变时发出NSUserDefaultsDidChangeNotifcation通知。这里的好处是任何NSObject类都可以设置为通知的观察者。观察者提供了一个回调方法,如下面的代码所示:
public mySettingsClass()
{
NSNotificationCenter.DefaultCenter.AddObserver
(
this, new Selector("updateSettings"),
new NSString ("NSUserDefaultDidChangeNotification"),
null
);
[Export ("updateSettings:")]private void UpdateSettings()
{
doSomething();
}
private void doSomething()
{
// do something here
}
}
自定义设置系统
虽然使用内置设置系统可能看起来很有用,但使用 Xamarin 产品系列的目的是在现在支持.NET 的任何平台上能够使用大量相同的代码。虽然,在撰写本文时,Windows Mobile 的市场份额仅为所有智能手机的约三%,但微软不太可能允许这种情况继续,并将推动其庞大的储备力量以促使人们采用他们的智能手机。如果你要计算纯数学,97%的所有智能手机都可以使用.NET 平台进行编码。(Blackberry 为其提供了 mono 的端口,但支持不佳,因此可以忽略。)
因此,存储用户设置需要不同的策略。我们可以使用 SQLite 数据库来存储详细信息,但正如已经指出的那样,对子系统的每次访问都会导致性能下降。
最简单的方法是创建一个settings类,并在需要时序列化或反序列化值。这是一个极其灵活的方法,并且效果非常好。
序列化和反序列化数据
以下代码是一个非常简单的序列化器和反序列化器:
public class Serializer
{
public static void XmlSerializeObject<T>(T obj, string filePath)
{
using (StreamWriter sw = new StreamWriter(filePath))
{
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
xmlSer.Serialize(sw, obj);
}
}
public static T XmlDeserializeObject<T>(string filePath)
{
using (StreamReader sr = new StreamReader(filePath))
{
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
return (T)xmlSer.Deserialize(sr);
}
}
}
上述代码将序列化和反序列化通过它的任何对象类型。这包括如List<>和Dictionary<,>等泛型类型对象。不要传递接口成员或循环引用(例如,一个引用到另一个对象的对象,该对象又引用到原始对象)——这将导致序列化器崩溃。
设置设置文件
与使用NSUserDefaults.plist文件不同,这里使用的设置文件仍然是一个 XML 文件,但它不需要设置为任何特定类型。由于它也是一个简单的 XML 文件,因此可以复制并传输到不同平台上的相同应用程序。

注意
假设你正在为 iPhone 上的应用程序配置文件,并且你想要在 Android 手机上运行它。你将复制Settings文件,并在 Android 手机上运行应用程序,你立即就有了在 iPhone 上设置的设置。

Settings文件由两个类组成——handler类和data类。
处理类
处理类负责设置Settings文件以及数据类的访问器。以下代码中重现了重要部分:
public static class myAppData
{
private static AppSettings appSetting;
public static AppSettings appSettings
{
get
{
if (appSetting == null)
{
if (File.Exists(AppSettingsFile))
appSetting = Serializer.XmlDeserializeObject<AppSettings>(AppSettingsFile);
else
{
appSetting = new myAppData.AppSettings();
Serializer.XmlSerializeObject <AppSettings>(appSetting, AppSettingsFile);
}
}
return appSetting;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value is null!");
}
appSetting = value;
if (File.Exists(AppSettingsFile))
File.Delete(AppSettingsFile);
Serializer.XmlSerializeObject <AppSettings>(appSetting, AppSettingsFile);
}
}
private static string pathAppSettingsFile;
public static string AppSettingsFile
{
get
{
if (string.IsNullOrEmpty(pathAppSettingsFile))
pathAppSettingsFile =Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments"),"AppSettings.xml");
return pathAppSettingsFile;
}
}
}
添加访问器
以下代码用于将任何类型的对象传递到配置文件中:
public static List<string> theAlias
{
get { return appSettings.TheAlias;}
set
{
AppSettings settings = appSettings;
settings.TheAlias = value;
appSettings = settings;
}
}
上述简单的访问器可以根据需要多次复制粘贴。最好的是,List<>对象可以是任何类型——你甚至可以在其中有一个完整的类列表!
数据类
数据类除了包含与数据处理器类中匹配的访问器列表外,还包含一个默认构造函数,如下所示:
public class AppSettings
{
public AppSettings()
{
}
public List<string> TheAlias
{get;set;}
}
使用上述代码,你可能有最灵活的配置和设置系统。
摘要
用户设置很重要——谁会愿意每次都要重新设置一切,或者更糟糕的是,每次应用程序更新都要设置?在更新应用程序时,应用程序文件夹中的一个文件将被保留,并且始终可用(除非应用程序被卸载)。存储的用户设置并不总是保证在版本之间持久存在。在本章中,我们看到了两种完全不同的存储配置设置的方法:一种是提供的,另一种是创建的。还有其他方法可以做到这一点,就像我在本章中演示的那样。
第十四章:测试和发布
您的应用已经准备好,等待向全世界发布。您已经坐下来,编码,测试,编码,测试,最后决定您已经创造了世界上最棒的应用,现在是时候将其上传到苹果商店了。问题是只有您测试过它。与 Android 测试不同,iOS 测试并不那么直接,将您的应用放入商店也不是那么简单。
在本章中,我们将涵盖:
-
应用配置和签名
-
使用 TestFlight 测试您的应用
-
打包和签名应用
-
在 App Store 上发布
应用配置和签名
对于对自用手机的常规调试,通常不需要您对应用进行签名,因为当您部署到手机时,会使用一个通用的开发者代码。这个代码被称为配置文件。我们现在正在进入一个更大的领域,应用需要测试。您可能会想知道为什么您的应用需要其他人进行测试。答案很简单:为了确保最佳的用户体验。
从开发者的角度来看,一款软件遵循一定的模式:A 到 B,B 到 C,C 到 D 或 E,依此类推。当我们测试我们的代码时,我们会走这条路径;用户不会。他们将从 A 到 C 到 F 到 B 再到 C,最后当代码在尝试直接从 C 到 H 时出现异常并崩溃时,他们会大喊。在发布前让更多人测试您的软件,当用户从Mac App Store下载应用时,他们会更加满意,而且针对您的负面评论也会更少。
对于测试,您必须分发应用,这并不像上传应用到您的个人网站并说“嘿,去下载并安装”那样简单,就像典型的桌面(或 Android)应用一样。为了确保可用应用的质量,苹果对分发系统有相当严格和严格的要求,即使是测试分发。幸运的是,有一种方法可以在向公众发布之前分发您的应用:TestFlight。
TestFlight
TestFlight是一个平台,允许您将测试(或测试版)软件上传到受邀用户(iOS 设备)或注册的 iOS 设备。这是一个免费服务,不会违反苹果的任何规则或规定,而且,不需要设备越狱。设置此服务是一个三步过程:
-
配置
-
邀请
-
构建和上传
配置
配置设置设备以允许测试非苹果商店批准的应用。首先,您需要在developer.apple.com注册为 iOS 开发者——费用大约为每年 100 美元(在撰写本文时约为 65 英镑)。这是一项年度费用,您确实会得到很多回报。完成之后,您需要登录并为您应用创建一个 ID。

注册应用
首先,选择标识符:您需要给您的应用起一个名字。在我的例子中,如下所示,我已经使用一个唯一的标识符填写了捆绑名称和应用程序名称——始终将您的应用分开是一个好主意。如果您愿意,这里可以设置一个“捕获所有”的标识符。

灰色区域(在先前的截图中)将是苹果在您首次创建账户时提供给您的 ID。
一旦您对值满意(除非您需要推送通知或应用内购买等功能,您只需创建 ID 即可),您将需要确认详细信息。一旦确认,您将看到一个页面,详细列出您正在开发的所有应用。

创建开发者配置文件
一旦您创建了应用 ID,您需要为它创建一个开发者配置文件。
-
在左侧菜单中,点击配置文件然后点击开发。您将看到以下屏幕:
![创建开发者配置文件]()
-
在分发下,您需要选择Ad Hoc。点击继续。接下来的几个屏幕都很简单。
![创建开发者配置文件]()
-
从下拉菜单中选择您的应用。完成后,点击继续。
-
接下来,您将看到一个屏幕,要求您选择要用于签名应用的证书。该证书是您独有的。如果您还没有,现在可以快速创建一个新的证书。
创建您的证书
启动 Xcode,在窗口选项下选择组织者。然后选择设备选项。您将在左侧看到一个菜单。滚动到库并选择配置文件。您需要输入您的 Apple ID 和密码。完成后,点击刷新图标。Xcode 会通知您是否没有开发者配置文件,并给您提交一个创建一个的选项。点击带有该选项的按钮。一旦提交请求,证书将被添加到您的密钥链中。将证书保存在安全的地方,并为其设置用户名和密码。这就完成了!
然后,您需要下载并安装证书。在左侧菜单中,点击证书。您将有一个选项来加载一个.cert文件——这正是您刚刚创建的文件。上传文件,完成后,您将看到以下截图。下载文件,双击安装,工作就完成了。只要您向苹果支付费用,您的证书就是有效的,并且这是您进行开发和分发所需的一切。

返回注册您的应用
一旦您选择了您的应用,您就可以选择它需要安装到哪些设备上。如果您没有设备,可以跳过此页面并下载证书。下载后,双击它,配置文件就安装完成了。
在 Xamarin Studio 中启用 TestFlight
Xamarin Studio 默认支持TestFlight。要使用 Xamarin Studio 启用构建,您需要正确设置项目。
在项目下选择您的应用。接下来,需要将其启用为AdHoc/企业分发。您可以在 IPA 部分找到它(勾选复选框以启用)。
最后(目前是这样),选择正确的配置;这里,是分发(自动)以及应用证书的名称:

在 TestFlight 上注册
在TestFlight上注册非常简单。创建一个账户,他们将会提供给您一个应用和团队 ID。不用担心记住这些,因为当您构建到TestFlight时,Xamarin Studio 会自动获取这些值。
邀请和注册设备
注册后,您将被邀请创建一个团队。这是必需的,这样您就可以邀请人们测试您的应用。点击创建新团队按钮,输入您想要给该区域的名称,然后点击保存。

接下来,您将被邀请上传一个构建版本。这就像在您还没有上课之前就要求您提供汽车一样。在这里上传构建版本是没有意义的,因为没有人能够安装应用!为了实现这一点,测试人员必须在其设备上注册您的开发者配置文件,而这只有在您邀请他们之后才会发生。
在此之前,您需要创建一个应用。点击页面顶部的应用链接,然后点击创建应用。您将看到以下屏幕:

应用名称是应用的通用名称(例如,mywhizzoapp)。为了方便,请将包标识符与您在 adhoc 配置文件中使用的相同(com.sporkish.mywhizzoapp)。您需要将其添加到 Xamarin Studio 中的应用配置文件中(位于包下)。创建并保存后,您就可以开始邀请人们了。
邀请和注册某人非常简单。在TestFlight网站上,点击顶部的人员选项。接下来,创建一个新的分发列表。这些列表非常有用,因为它意味着您可以使用一个账户向大量人员分发大量应用。

给列表命名,由于只有一个人可用(即您自己),选择您的配置文件,然后点击保存。
接下来,您需要邀请人们。在左侧菜单中,点击邀请。由于还没有邀请测试人员,您将被提示邀请某人。

如果你邀请的人也是你团队中的开发者,请开启****团队开发者选项;否则,请保持关闭。一旦你点击发送邀请按钮,将会给那个人发送一封电子邮件,那个人会接受邀请并注册设备。完成此操作后,你会收到一封电子邮件。
复制设备的UUID(或者如果它已经通过电子邮件发送,你可以上传包含设备UUID代码的文件),然后前往苹果开发者网站。登录并选择设备。在这里你可以添加一个或多个设备。一旦添加,返回到开发者配置选项,并启用该设备。你需要重新下载配置文件,并使用 Xcode 再次打开它,然后使用更新的配置重新构建应用。如果不这样做,用户将无法安装应用。
构建到 TestFlight
确保你的项目中的BundleID与TestFlight的BundleID相同后,事情变得非常简单。

在完成输入详细信息(类似于前面的截图)后,点击确定,并将构建设置为Ad Hoc。选择项目,然后选择TestFlight选项。

初始时,此窗口将是空的。点击获取你的 API 令牌,然后点击获取你的团队令牌,这将启动你的默认网页浏览器。将这些值复制并粘贴到发布到 TestFlight屏幕上的API 令牌和团队令牌字段(如前述截图所示)。接下来,提供一些关于文件中更改的详细信息,最后选择你已设置的分发列表。确保前两个复选框被勾选(这有助于让人们知道何时上传了某些内容)。
当你完成操作后,点击发布,假设没有构建问题,应用将被发送到TestFlight,并且通知列表中的测试者会收到通知。
经过几周的测试后,你应该准备好将应用提交给苹果进行分发。这是获得你的应用批准的最终步骤,但你需要注意一些障碍。
发布你的应用
测试已完成,并且你已经解决了大多数 bug。可能还有一些 bug 存在,但现在应用表现良好,你准备好将其发布给世界了。
应用清单
在你将应用提交给苹果通过其 App Store 发布之前,你必须确保以下事项:
-
正确的数量和尺寸的图标(见下文以获取尺寸)
-
应用需要正确签名
-
必须包含对 iPhone 5 的支持(这包括小部件被正确放置在新更大的可见屏幕区域)
-
你没有使用任何私有 API 或禁止的 API 调用(苹果不希望你使用它无法控制的库,并将丢弃绕过硬件或软件控制或干扰其他软件操作的应用)。
图标尺寸
在将您的应用提交给苹果之前,以下所有图标必须以正确的尺寸存在。图标的尺寸(以像素为单位)如下:
| iPhone | iPad |
|---|---|
| 应用图标 | 正常 |
| 57 x 57 | 114 x 114 |
| 启动图像 | 正常 |
| 320 x 480 | 640 x 960 |
| iTunes 艺术品 | 正常 |
| 512 x 512 | 1024 x 1024 |
准备打包
在放置好图标后,接下来需要一个 App Store 配置文件。这的设置方式与开发者或Ad Hoc配置文件类似:

创建App Store配置文件的流程与Ad Hoc配置文件生成类似,除了您不需要选择要安装测试软件的设备。下载配置文件,并像为Ad Hoc配置文件那样安装它。尽管如此,您仍需要为配置文件提供一个名称。
打包您的应用
一旦创建并安装了配置文件,接下来需要为分发打包应用。这并不像看起来那么简单。
您已经创建了一个开发证书;现在需要重复此过程以生成分发证书。执行此操作的过程与开发证书完全相同。完成此操作后,将证书安装到您的密钥链中。

一旦证书安装完成,下一个阶段将在 Xamarin Studio 中进行。
创建构建配置
这是创建 iOS 应用所需的配置文件。加载您希望提交的项目。导航到项目 | 解决方案配置文件。在左侧,点击配置选项,如下所示:

要添加配置文件(假设它尚未存在),请点击添加。您将看到一个新配置框。您需要按照所示填写此信息。完成此操作后,点击确定,然后再次点击确定以关闭解决方案选项窗口。

如果您创建的应用是通用型(换句话说,适用于 iPhone 和 iPad),平台应该是通用型。如果不是,对于 iPad 应用,平台将是 iPad。
接下来,应用程序需要签名。导航到项目 | mywhizzoapp(或您的应用名称)。在构建选项下,选择iOS 构建并选择您的 AppStore 配置。之后,选择 iOS 捆绑签名选项。在身份下拉菜单中,将其设置为分发(自动),并将配置文件设置为用于分发的配置文件(而不是用于 TestFlight 分发的配置文件)。
完成这些后,点击 确定 以关闭窗口并将应用程序的构建类型设置为 AppStore。

为了安全起见,点击 构建 | 清理所有,然后 构建 | 构建所有。如果一切正常,我们就在 App Store 中了。
App Store 提交流程
第一步是使用 iTunes Connect (itunesconnect.apple.com) 创建一个记录。这是一个简单的流程。描述必须反映应用程序的功能(例如,您不能提交一个实际上是游戏的即时通讯应用程序)。
假设记录已正确创建,点击 准备上传 二进制按钮。您将面临一系列问题,您可以通过正确回答这些问题来继续操作。最后一屏将告诉您 App Store 已准备好上传,您可以使用上传工具。但请不要这样做。Xamarin Studio 和 Xcode 有一个更简单的方法,问题更少。第一部分是创建存档。
创建存档
导航到 构建 | 存档。这将构建应用程序并从中创建一个正确配置以供分发的存档。一旦存档构建完成,代码设计器中将会出现一个存档标签。在这里,您可以查看您创建的任何存档。
通过 Xcode 提交
启动 Xcode 并导航到 窗口 | 组织者。点击 存档 标签。您将看到所有用于分发的应用程序列表。选择您想要提交的应用程序,您将看到两个选项:验证 和 提交。点击 验证。如果没有应用程序的问题,请继续到 提交。通常,如果应用程序没有正确应用配置文件证书或找不到证书,验证会失败。修复问题,重新存档,并再次验证。
提交向导
为了指导您完成提交过程,苹果公司包含了一个非常易于使用的提交向导。您必须确保在此阶段您有一个网络连接,并且应用程序已准备好上传。
点击 提交。Xcode 与 iTunes Connect 通信并检索您已提交以供上传的应用程序列表。选择您想要提交的应用程序。Xcode 将为您上传存档。一旦上传完成,如果您回到 iTunes Connect,您将看到应用程序现在正在等待批准。(请不要担心,这基本上是一个自动化的过程。)一旦批准,您就完成了,您的应用程序现在在 App Store 中。
摘要
测试和分发 iOS 应用可能看起来是浪费时间,但最终,控制谁在测试你的应用让你能够挑选和选择你希望测试代码的人。此外,它不允许版本泄露,从而避免给你带来潜在的坏名声。是的,这可能会显得非常控制欲强,但这就是苹果希望你玩的游戏,如果你想通过他们分发你的应用,这就是你必须玩的游戏。
第十五章:索引
A
-
ABAddressBook / ABAddressBook
-
ABNewPersonViewController / ABNewPersonViewController
-
ABPeoplePickerNavigationController / ABPeoplePickerNavigationController
-
ABPersonViewController / ABPersonViewController
-
ABUnknownPersonViewController / ABUnknownPersonViewController
-
加速度事件 / UIAccelerometer
-
AcceptEvent 事件 / CFSocket
-
AccessoryTapped 事件 / StyledStringElement
-
动作
- 关于 / 使用 Xcode 创建用户界面
-
ActionFinished 事件 / AdBannerView, AdInterstitialAd
-
ActivityElement / 用于标准 UITextField
-
活动指示器视图 / 其他视图
-
AdBannerView
- 关于 / AdBannerView/ AdBannerView
-
AdBanner View / 其他视图
-
广告类
-
AdBannerView / AdBannerView
-
AdInterstitialAd / AdInterstitialAd
-
OpenTK / OpenTK
-
IGameWindow / IGameWindow
-
IPhoneOSGameView / IPhoneOSGameView
-
-
地址解析事件 / NSNetService
-
AdInterstitialAd / AdInterstitialAd
-
AdLoaded / AdBannerView
-
AdLoaded 事件 / AdBannerView, AdInterstitialAd
-
AdUnloaded 事件 / AdInterstitialAd
-
广告开始事件 / CBPeripheralManager
-
提前时间 (AOT) / 使用 LINQ 进行数据操作
-
AllEditingEvents 事件 / 其他重要控制事件
-
AllEvents 事件 / 其他重要控制事件
-
AllTouchEvents 事件 / 其他重要控制事件
-
安卓
- 与 iOS UI 控件对比 / 对比 Android 和 iOS UI 控件
-
Android SDK 管理器 / 为 Android 开发安装附加代码
-
AnimationEnded 事件 / CAAnimation
-
AnimationStarted 事件 / CAAnimation
-
AnimationWillEnd 事件 / UIView
-
AnimationWillStart 事件 / UIView
-
应用
-
注册 / 注册应用, 返回注册您的应用
-
释放 / 释放您的应用
-
清单 / 应用清单
-
打包 / 打包您的应用
-
-
AppDelegate 类
- 关于 / AppDelegate 类/ 创建数据库连接
-
App Store 提交流程
-
关于 / App Store 提交流程
-
归档,创建 / 创建归档
-
通过 Xcode / 通过 Xcode 提交
-
提交向导 / 提交向导
-
-
异步代码
- 使用 / 使用异步代码
-
异步事件处理
- 与同步事件处理对比 / 同步与异步事件处理对比
-
异步遍历 / 异步遍历
-
异步方法 / 任务和事件处理器, 一个更实际的例子
-
音频
- NSDictionary,设置 / 设置音频 NSDictionary
-
AudioConverter / AudioConverter, AudioConverter
-
音频文件
-
播放 / 播放音频文件
-
录制 / 录制音频文件
-
-
AudioRouteChanged 事件 / AudioSession
-
AudioSession / AudioSession
-
AudioUnit / AUGraph 和 AudioUnit
-
AUGraph / AUGraph 和 AudioUnit
-
AuthorizationChanged 事件 / CLLocationManager
-
AVAudioPlayer / AVAudioPlayer 和 AVRecordClass
-
AVAudioSession / AVAudioSession
-
AVRecordClass / AVAudioPlayer 和 AVRecordClass
B
-
backBarButtonItem / 使用 Xcode, 使用 UITableView 进行导航
-
背景线程
- 在应用中使用 / 在您的应用中使用背景线程
-
BackgroundWorker DoWork 线程 / BackgroundWorker
-
BackgroundWorker 线程 / BackgroundWorker
-
BadgeElement / 标准 UITextField 的使用, 徽章元素、BaseBooleanImageElement、玻璃按钮、加载更多元素、消息元素和字符串元素
-
BarButtonItem / 其他控件
-
BaseBooleanImageElement / 标准 UITextField 的使用, 徽章元素、BaseBooleanImageElement、玻璃按钮、加载更多元素、消息元素和字符串元素
-
BeginAnimations / 代码分析
-
BeginInterruption 事件 / AVAudioPlayer 和 AVRecordClass, AVAudioSession
-
位图
-
处理 / 处理位图
-
图像,缩放 / 缩放图像
-
图像,旋转 / 旋转图像 - 第一部分
-
绑定,支撑 / 支撑绑定
-
代码,分析 / 代码分析
-
-
Bonjour
- 关于 / 在 PC 上
-
BookmarkButtonClicked 事件 / UISearchBar
-
BooleanElement / 标准 UITextField 的使用
-
BooleanImageElement / 标准 UITextField 的使用
-
BoolElement / 标准 UITextField 的使用, BoolElement
-
构建配置
- 创建 / 创建构建配置
-
内置系统
- 关于 / 内置系统
C
-
.caf (核心音频文件) / 播放
-
CAAnimation / CAAnimation
-
日历选择器类 / EKCalendarChooser
-
回调 / ThreadPool.QueueUserWorkItem
-
调用登录服务 / 更实用的示例
-
调用新方法 / 使用后释放内存
-
调用辅助控件点击事件 / MKMapView
-
相机
-
关于 / 使用相机
-
Xamarin.Mobile 组件 / 访问相机 (Xamarin.Mobile)")
-
访问,使用 UIImagePickerController / 访问相机 (原生)")
-
-
可接受字节事件 / CFStream
-
取消按钮点击事件 / UISearchBar
-
取消事件 / ABPeoplePickerNavigationController, EKCalendarChooser, UIActionSheet 和 UIAlertView, UIImagePickerController
-
卡德克斯系统 / 数据库基础
-
类别变更事件 / AVAudioSession
-
CBCentralManager / CBCentralManager
-
CBPeripheral / CBPeripheral
-
CBPeripheralManager / CBPeripheralManager
-
证书
- 创建 / 创建您的证书
-
CFSocket / CFSocket
-
CFStream / CFStream
-
CGColor
- 关于 / UIColor
-
拖动状态变更事件 / MKMapView
-
事件变更 / EntryElement, UITextView
-
特征订阅事件 / CBPeripheralManager
-
特征取消订阅事件 / CBPeripheralManager
-
复选框元素 / 标准 UITextField
-
CIColor
- 关于 / UIColor
-
Clicked 事件 / UIActionSheet 和 UIAlertView, UIButtonBarItem
-
CLLocationManager / CLLocationManager
-
Closed 事件 / IPhoneOSGameView
-
ClosedEvent 事件 / CFStream
-
Collection Reusable View / 其他视图
-
Collection View / 其他视图
-
CommitAnimations / 代码分析
-
Completed 事件 / EKEventEditViewController 和 EKEventViewController
-
ConnectedPeripheral 事件 / CBCentralManager
-
ConnectEvent 事件 / CFSocket
-
ConnectionAutomatic 标志 / 访问互联网
-
ConnectionFailed 事件 / GKSession
-
ConnectionOnDemand 标志 / 访问互联网
-
ConnectionOnTraffic 标志 / 访问互联网
-
ConnectionRequest 事件 / GKSession
-
ConnectionRequired 标志 / 访问互联网
-
连接器
- 关于 / 使用 Xcode 创建用户界面
-
控件
-
选择 / 控件选择
-
列表 / 其他控件
-
-
CoreAnimation / 旋转图像 – 第一部分
-
CoreBluetoothCentralManager / CBCentralManager
-
核心定位
- 关于 / GPS 和地图
-
Core Location
-
关于 / 计算你的速度
-
使用 / 使用 Core Location
-
设置 / 设置 Core Location 和代理
-
代理,设置 / 设置 Core Location 和代理
-
用户,查找 / 查找用户的位置
-
地图,添加 / 添加地图
-
纽扣,添加 / 添加纽扣
-
-
CoreLocation LocationManager 类 / CLLocationManager
D
-
数据
-
添加到数据库 / 向数据库添加数据
-
使用 LINQ 进行操作 / 使用 LINQ 进行数据操作
-
反序列化 / 序列化和反序列化数据
-
序列化 / 序列化和反序列化数据
-
-
数据库
-
基础知识 / 数据库基础知识
-
类 / 一个简单的数据库类
-
创建连接 / 创建数据库连接
-
-
数据类
- 关于 / 数据类
-
DataManager / 创建数据库连接
-
DataManager 类 / 创建数据库连接
-
DataReceived 事件 / GKMatch
-
DateElement / 对于标准的 UITextField
-
DatePicker / 其他控件
-
DateSelected 事件 / DateTimeElement
-
DateTimeElement / 对于标准的 UITextField, DateTimeElement
-
DateTime Picker View / DateTimeElement
-
DBClauseSyncOff 常量 / 创建数据库连接
-
DBClauseVacuum 常量 / 创建数据库连接
-
死锁
-
关于 / 死锁
-
避免同步访问器的死锁 / 避免同步访问器的死锁
-
-
DecelerationEnded 事件 / UIScrollView, UITextView
-
DecelerationStarted 事件 / UIScrollView, UITextView
-
DecodeError 事件 / AVAudioPlayer 和 AVRecordClass
-
DeferredUpdatesFinished 事件 / CLLocationManager
-
代理事件
-
关于 / 代理
-
附加到多个控件 / 将事件附加到多个控件
-
-
demoTable 变量 / 一个简单的数据库类
-
反序列化数据 / 序列化和反序列化数据
-
DesiredAccuracy / 使用 Xamarin.Mobile 的 GPS
-
详细信息披露 / 创建只读表格
-
详细信息披露按钮 / 创建只读表格
-
详细文本标签 / UITableViewCell
-
开发者个人资料
- 创建 / 创建开发者个人资料
-
DialogViewController / DialogViewController
-
DidAddAnnotationViews 事件 / MKMapView
-
DidAddOverlayViews 事件 / MKMapView
-
DidBeginCustomizingItems 事件 / UITabBar
-
DidCancel 事件 / MPMediaPickerController
-
DidChangeUserTrackingModel 事件 / MKMapView
-
DidDeselectAnnotationView 事件 / MKMapView
-
DidDismiss 事件 / QLPreviewController, UIPopoverController, UIPrintInteractionController
-
DidEndCustomizingItems 事件 / UITabBar
-
DidFailToLocateUser 事件 / MKMapView
-
DidFailWithError 事件 / GKMatchmakerViewController
-
DidFindMatch 事件 / GKMatchmakerViewController
-
DidFindPlayers 事件 / GKMatchmakerViewController
-
DidFinishAnimating 事件 / UIPageViewController
-
DidFinish 事件 / GKAchievementViewController, GKFriendRequestComposeViewController, and GKLeaderboardViewController
-
DidFinishJob 事件 / UIPrintInteractionController
-
DidPresentPrinterOptions 事件 / UIPrintInteractionController
-
DidSelectAnnotationView 事件 / MKMapView
-
DidShowViewController 事件 / UIImagePickerController
-
DidStartMonitoringForRegion 事件 / CLLocationManager
-
DidStopLocatingUser 事件 / MKMapView
-
用户位置更新事件 / MKMapView
-
放大事件 / UIScrollView, UITextView
-
揭示 / 创建只读表格
-
断开连接事件 / EAAccessory
-
断开外围设备连接事件 / CBCentralManager
-
发现特征事件 / CBPeripheral
-
发现描述符事件 / CBPeripheral
-
发现包含的服务事件 / CBPeripheral
-
发现外围设备事件 / CBCentralManager
-
发现服务事件 / CBPeripheral
-
撤销事件 / UIActionSheet 和 UIAlertView
-
释放事件 / IPhoneOSGameView
-
域移除事件 / NSNetServiceBrowser
-
拖放
- 关于 / 处理拖放
-
拖放结束事件 / UIScrollView, UITextView
-
拖放开始事件 / UIScrollView, UITextView
-
在矩形内绘制事件 / GLKView
-
持续时间 / 代码分析
E
-
EAAccessory / EAAccessory
-
平滑进入 / 代码分析
-
平滑进入/退出 / 代码分析
-
平滑退出 / 代码分析
-
编辑更改事件 / 其他重要控制事件
-
编辑开始事件 / 其他重要控制事件
-
编辑结束事件 / 其他重要控制事件
-
编辑结束并退出事件 / 其他重要控制事件
-
EK 日历选择器 / EKCalendarChooser
-
EK 事件编辑视图控制器 / EK 事件编辑视图控制器和 EK 事件视图控制器
-
EK 事件视图控制器 / EK 事件编辑视图控制器和 EK 事件视图控制器
-
元素
-
关于 / MonoTouch.Dialog (MT.D)")
-
类型 / MonoTouch.Dialog (MT.D)")
-
-
元素 / 对于标准 UITextField
-
编码对象事件 / NSKeyedArchiver
-
编码器回调事件 / AudioConverter
-
结束事件 / UITextField, UITextView
-
结束中断事件 / AVAudioPlayer 和 AVRecordClass, AVAudioSession
-
EntryElement / 对于标准 UITextField, EntryElement
-
EntryElement 类 / 对于 MT.D
-
错误事件 / CFStream
-
EventArgs e / 代理
-
事件
-
处理 / 处理事件
-
处理,使用的代理 / 代理
-
-
执行标量方法 / 编写辅助类方法
-
外部更改事件 / ABAddressBook
F
-
失败,识别器状态 / 其他 UIGestureRecognizerState 值
-
失败事件 / CLLocationManager, GKMatch, GKSession
-
FailedToConnectPeripheral 事件 / CBCentralManager
-
FailedToReceive / AdBannerView
-
失败接收广告事件 / AdBannerView, AdInterstitialAd
-
完成自定义视图控制器事件 / UITabBarController
-
完成事件 / EKCalendarChooser, NSKeyedArchiver, NSKeyedUnarchiver, GKGameCenterViewController, MFMailComposeViewController 和 MFMessageComposeViewController, PKAddPassesViewController, SKStoreProductViewController
-
完成选择图片事件 / UIImagePickerController
-
FinishedPickingMedia() 方法 / 保存到相册(原生)")
-
FinishedPickingMedia 事件 / UIImagePickerController
-
FinishedPlaying 事件 / AVAudioPlayer 和 AVRecordClass
-
完成事件 / NSKeyedArchiver, NSKeyedUnarchiver
-
固定/灵活 BarButtonItem / 其他控件
-
FloatElement / 对于标准的 UITextField
-
FoundDomain 事件 / NSNetServiceBrowser
-
FoundService 事件 / NSNetServiceBrowser
-
FromBundle / UIImageView
-
FromFile / UIImageView
-
FromImage / UIImageView
-
FromResource / UIImageView
-
FromWhiteAlpha
- 关于 / UIColor
G
-
垃圾收集器 (GC)
- 关于 / 使用后释放内存
-
手势
-
关于 / 手势
-
UIPanGestureRecognizer 类 / 手势
-
UISwipeGestureRecognizer 类 / 手势
-
UITapGestureRecognizer 类 / 手势
-
UILongPressGestureRecognizer 类 / 手势
-
UIPinchGestureRecognizer 类 / 手势
-
UIRotationGestureRecognizer 类 / 手势
-
添加 / 手势
-
代码 / 手势代码
-
tapGesture / 手势代码
-
Selector 方法 / 手势代码
-
类型 / 类型
-
在代码中添加 / 在代码中添加手势
-
连续类型 / 连续类型
-
拖放,处理 / 处理拖放
-
-
GetCell 方法 / 创建只读表格, 表格内的可复用单元格
-
GKAchievementViewController / GKAchievementViewController, GKFriendRequestComposeViewController, 和 GKLeaderboardViewController
-
GK 类
-
GKAchievementViewController / GKAchievementViewController, GKFriendRequestComposeViewController, and GKLeaderboardViewController
-
GKFriendRequestComposeViewController / GKAchievementViewController, GKFriendRequestComposeViewController, and GKLeaderboardViewController
-
GKLeaderboardViewController / GKAchievementViewController, GKFriendRequestComposeViewController, and GKLeaderboardViewController
-
GKGameCenterViewController / GKGameCenterViewController
-
GKMatch / GKMatch
-
GKMatchmakerViewController / GKMatchmakerViewController
-
GKSession / GKSession
-
-
GKFriendRequestComposeViewController / GKAchievementViewController, GKFriendRequestComposeViewController, and GKLeaderboardViewController
-
GKGameCenterViewController / GKGameCenterViewController
-
GKLeaderboardViewController / GKAchievementViewController, GKFriendRequestComposeViewController, and GKLeaderboardViewController
-
GKMatch / GKMatch
-
GKMatchmakerViewController / GKMatchmakerViewController
-
GKSession / GKSession
-
GlassButton / 徽章元素、基础布尔图像元素、GlassButton、加载更多元素、消息元素和字符串元素
-
GLKit
- 关于 / GLKit
-
GLKit 视图 / 其他视图
-
GLKView / GLKView
H
-
处理类
-
关于 / 处理类
-
访问器,设置 / 添加访问器
-
-
处理代码 / 视图和视图控制器
-
HandleScanResult 方法 / 编程上下文中的
-
HasBytesAvailableEvent 事件 / CFStream
-
HtmlElement / 标准 UITextField
I
-
IGameWindow / IGameWindow
-
图像
-
添加 / UIButton
-
位图图像,处理 / 处理位图
-
缩放 / 缩放图像
-
旋转 / 旋转图像 – 第一部分, 旋转图像 – 第二部分
-
-
ImageElement / 标准 UITextField
-
ImageStringElement / 标准 UITextField
-
ImageView / 其他视图
-
输入辅助视图方法 / 对于 MT.D
-
InputAudioQueue / InputAudioQueue
-
输入可用性更改事件 / AVAudioSession
-
输入通道更改事件 / AVAudioSession
-
输入完成事件 / InputAudioQueue
-
输入数据事件 / AudioConverter
-
互联网
- 访问 / 访问互联网
-
中断事件 / AudioSession
-
需要干预标志 / 访问互联网
-
无效服务事件 / CBPeripheral
-
在主线程上调用方法 / 活动指示器和进度视图
-
IOError 事件 / MidiClient
-
iOS 布局
-
关于 / iOS 布局
-
画布模型 / 画布模型
-
问题,避免 / 如何避免这些问题
-
-
IOStream / CFStream
-
iOS UI 控件
- 以及 Android,比较 / 比较 Android 和 iOS UI 控件
-
IPhoneOSGameView / IPhoneOSGameView
-
IsDirect 标志 / 访问互联网
-
IsLocalDevice 标志 / 访问互联网
-
IsSourceTypeAvailable(myCamera)命令 / 多媒体
-
IsWWAN 标志 / 访问互联网
-
项目选中事件 / UITabBar
-
ItemsPicked 事件 / MPMediaPickerController
-
iTunes Connect
- URL / App Store 提交流程
J
- JsonElement / 对于标准的 UITextField
K
-
键盘
- 工具栏,添加 / 向键盘添加工具栏
-
键盘类型
- 更改 / 更改键盘类型
L
-
标签 / 其他控件
-
左侧栏按钮项 / 使用 Xcode
-
LINQ
-
使用,用于数据操作 / 使用 LINQ 进行数据操作
-
关于 / LINQ – 快速浏览
-
SELECT / LINQ 中的 SELECT 和 WHERE – 常见混淆原因
-
WHERE / LINQ 中的 SELECT 和 WHERE – 常见混淆原因
-
SELECT, 使用 / 在 LINQ 中使用 Select
-
SQL,替换为 / 用 LINQ 替换 SQL
-
-
ListButtonClicked 事件 / UISearchBar
-
利物浦 / 创建只读表格
-
LoadError 事件 / UIWebView
-
Load 事件 / IGameWindow, IPhoneOSGameView
-
LoadFinished 事件 / UIWebView
-
LoadFromData / UIImageView
-
LoadingMapFailed 事件 / MKMapView
-
LoadMoreElement / 对于标准的 UITextField, BadgeElement, BaseBooleanImageElement, GlassButton, LoadMoreElement, MessageElement, 和 StringElement
-
LoadStarted / 任务和事件处理器
-
LoadStarted 事件 / UIWebView
-
LocationsUpdated 事件 / CLLocationManager
-
LocationUpdatesPaused 事件 / CLLocationManager
-
LocationUpdatesResumed 事件 / CLLocationManager
-
锁
- 关于 / 使用锁
M
-
地图
- 添加 / 添加地图
-
MapLoaded 事件 / MKMapView
-
MapView
- 关于 / MapView
-
地图视图 / 其他视图
-
地图查看器 / 添加地图
-
主-详情,项目类型 / 应用程序类型及其视图类型
-
内存
-
使用后释放,/ 使用后释放内存
-
图片,旋转 / 旋转图片 – 第二部分
-
-
MessageElement / 对于标准的 UITextField, BadgeElement, BaseBooleanImageElement, GlassButton, LoadMoreElement, MessageElement, 和 StringElement
-
MessageReceived 事件 / MidiEndpoint 和 MidiPort
-
MFMailComposeViewController / MFMailComposeViewController 和 MFMessageComposeViewController
-
MFMessageComposeViewController / MFMailComposeViewController 和 MFMessageComposeViewController
-
MidiClient / MidiClient
-
MidiEndpoint / MidiEndpoint 和 MidiPort
-
MidiPort / MidiEndpoint 和 MidiPort
-
MKCoordinateSpan(0.5,0.5) / 添加地图
-
MKMapView / MKMapView
-
MKReverseGeocoder 类 / 查找用户位置
-
监控失败事件 / CLLocationManager
-
Monotouch.CoreGraphics 命名空间 / UIButton
-
Monotouch.Dialog / Monotouch.Dialog
-
MonoTouch.MapKit 命名空间 / 查找用户位置
-
MonoTouch.MediaPlayer 命名空间 / 多媒体
-
MPMediaPickerController / MPMediaPickerController
-
MT.D
-
关于 / MonoTouch.Dialog (MT.D)")
-
键盘类型,更改 / 更改键盘类型
-
ShouldReturn, 使用 / 使用 ShouldReturn
-
ResignFirstResponder, 使用 / 使用 ResignFirstResponder
-
工具栏,添加到键盘 / 将工具栏添加到键盘
-
支持的类型 / 对于标准的 UITextField
-
选择器,创建 / 在 MT.D 上创建自己的选择器
-
-
MT.D 视图
-
元素 / MonoTouch.Dialog (MT.D)")
-
部分 / MonoTouch.Dialog (MT.D)")
-
根 / MonoTouch.Dialog (MT.D)")
-
-
多行元素 / 对于标准的 UITextField
-
多媒体
-
关于 / 多媒体
-
视频,播放 / 播放视频
-
视频,录制 / 录制视频
-
音频系统 / 音频系统
-
音频,录制 / 录制音频
-
-
多个视图控制器
- 使用,用于实现视图 / 使用多个视图控制器实现视图
N
-
导航栏 / 其他控件
-
导航控制器 / 代码内, 使用 Xcode
-
导航项 / 其他控件
-
NewPersonComplete 事件 / ABNewPersonViewController
-
NewPersonViewController / ABNewPersonViewController
-
NextStep (NS) / NS 类
-
NotSearched 事件 / NSNetServiceBrowser
-
NSAutoreleasePool 类 / 活动指示器和进度视图
-
NSCache / NSCache
-
NS 类
-
NSCache / NSCache
-
NSKeyedArchiver / NSKeyedArchiver
-
NSKeyedUnarchiver / NSKeyedUnarchiver
-
NSNetService / NSNetService
-
NSNetServiceBrowser / NSNetServiceBrowser
-
NSStream / NSStream
-
-
NSKeyedArchiver / NSKeyedArchiver
-
NSKeyedUnarchiver / NSKeyedUnarchiver
-
NSNetService / NSNetService
-
NSNetServiceBrowser / NSNetServiceBrowser
-
NSNotificationCenter 类 / 读取和写入 .plist 文件
-
NSObject 类 / 读取和写入 .plist 文件
-
NSStream / NSStream
-
NSUserDefaults 类 / 读取和写入 .plist 文件
O
-
ObjectAdded 事件 / MidiClient
-
ObjectRemoved 事件 / MidiClient
-
OnCustomizingViewController 事件 / UITabBarController
-
OnEditingStarted 事件 / UISearchBar
-
OnEditingStopped 事件 / UISearchBar
-
OnEndCustomizingViewController 事件 / UITabBarController
-
OnEvent 事件 / NSStream
-
OnSelection 事件 / DialogViewController
-
OpenCompletedEvent 事件 / CFStream
-
OpenGL,项目类型 / 应用程序类型及其视图类型
-
OpenGL for Embedded Systems (OpenGL ES) / GLKit
-
OpenTK
- 关于 / OpenTK
-
输出
- 关于 / 使用 Xcode 创建用户界面
-
输出集合
- 关于 / 使用 Xcode 创建用户界面
-
OutputAudioQueue / OutputAudioQueue
-
OutputChannelsChanged 事件 / AVAudioSession
-
OutputCompleted 事件 / OutputAudioQueue
-
OwnerDrawnElement / 对于标准的 UITextField
P
-
.plist 文件
-
关于 / 内置系统
-
读取 / 读取和写入 .plist 文件
-
写入 / 读取和写入 .plist 文件
-
-
打包
-
准备打包 / 准备打包
-
应用程序 / 打包您的应用程序
-
构建配置,创建 / 创建构建配置
-
-
页面控制
- 关于 / PageControl/ 其他控件
-
PeerChanged 事件 / GKSession
-
PerformAction 事件 / ABPeoplePickerNavigationController
-
PerformDefaultAction 事件 / ABPersonViewController, ABUnknownPersonViewController
-
PersonCreated 事件 / ABUnknownPersonViewController
-
电话
- 存储 / 手机上的存储
-
电话通话
- 进行 / 进行电话通话
-
选择器
- 创建,在 MT.D 上 / 在 MT.D 上创建自己的选择器
-
选择视图 / 其他视图
-
钩子
- 添加 / 添加钩子
-
PKAddPassesViewController / PKAddPassesViewController
-
播放
-
关于 / 播放
-
短文件 / 短文件
-
长文件(和压缩文件) / 长文件(和压缩文件)
-
功率级别,设置 / 设置功率级别
-
音频文件,播放 / 播放音频文件
-
音量,调整 / 调整音量
-
-
可能的,识别器状态 / 其他 UIGestureRecognizerState 值
-
功率级别
- 设置 / 设置功率级别
-
Presented 事件 / UIActionSheet 和 UIAlertView
-
ProgressChanged 事件 / BackgroundWorker
-
进度视图 / 其他视图
-
项目,类型
-
关于 / 应用程序类型及其视图类型
-
主-详细视图 / 应用程序类型及其视图类型
-
单视图 / 应用程序类型及其视图类型
-
标签页 / 应用程序类型及其视图类型
-
OpenGL / 应用程序类型及其视图类型
-
URL / 应用类型及其视图类型
-
-
PropertyChanged 事件 / MidiClient
-
配置
- 关于 / 配置
-
配置文件
- 关于 / 配置和签名您的应用
-
Published 事件 / NSNetService
-
PublishFeature 事件 / NSNetService
Q
-
QLPreviewController / QLPreviewController
-
QueueUserWorkItem 方法 / ThreadPool.QueueUserWorkItem
R
-
RadioElement / 针对标准 UITextField
-
可达性标志 / 访问互联网
-
只读表
- 创建 / 创建只读表
-
ReadRequestReceived 事件 / CBPeripheralManager
-
ReadyToUpdateSubscribers 事件 / CBPeripheralManager
-
ReceivedAcceptFromHostedPlayer 事件 / GKMatchmakerViewController
-
ReceiveData 事件 / GKSession
-
ReceivedResponse 事件 / SKProductsRequest
-
记录
- 设置 / 设置以记录
-
RefreshRequested 事件 / DialogViewController
-
RegionChanged 事件 / MKMapView
-
RegionEntered 事件 / CLLocationManager
-
RegionLeft 事件 / CLLocationManager
-
RegionWillChange 事件 / MKMapView
-
RenderCallback 事件 / AUGraph 和 AudioUnit
-
RenderedFrame 事件 / IGameWindow, IPhoneOSGameView
-
RepeatAnimationCurve / 代码分析
-
ReplacingObject 事件 / NSKeyedArchiver, NSKeyedUnarchiver
-
RequestFailed 事件 / SKProductsRequest, SKRequest
-
RequestFinished 事件 / SKProductsRequest, SKRequest
-
ResignFirstResponder
- 使用 / 使用 ResignFirstResponder
-
解析失败事件 / NSNetService
-
恢复事件 / AudioSession
-
获取到的已连接外设事件 / CBCentralManager
-
获取到的外设事件 / CBCentralManager
-
retString 变量 / LINQ 中的 SELECT 和 WHERE – 常见混淆原因
-
返回键类型 / 更改键盘类型
-
右侧条目栏按钮 / 使用 Xcode
-
根
- 关于 / MonoTouch.Dialog (MT.D)")
-
根元素 / 对于标准的 UITextField
-
Rssi 更新事件 / CBPeripheral
-
运行工作完成事件 / BackgroundWorker
S
-
样本率更改事件 / AVAudioSession
-
缩放因子 / 缩放图像
-
屏幕
-
原点 / 屏幕原点和尺寸
-
尺寸 / 屏幕原点和尺寸
-
-
滚动动画结束事件 / UIScrollView, UITextView
-
滚动事件 / UIScrollView, UITextView
-
滚动到顶部事件 / UIScrollView, UITextView
-
滚动启用 / 添加地图
-
滚动视图 / UITextView
-
滚动视图 / 其他视图
-
SDK / 为 Android 开发安装附加代码
-
搜索栏 / 其他控件
-
搜索按钮点击事件 / UISearchBar
-
搜索移除事件 / NSNetServiceBrowser
-
搜索开始事件 / NSNetServiceBrowser
-
搜索停止事件 / NSNetServiceBrowser
-
搜索文本更改事件 / DialogViewController
-
部分
- 关于 / MonoTouch.Dialog (MT.D)")
-
索引标题 / TableView 上的索引
-
分段控制器 / 其他控件
-
SELECT
- 使用,在 LINQ 中 / 在 LINQ 中使用 Select
-
SelectedScopeButtonIndexChanged 事件 / UISearchBar
-
SelectionChanged 事件 / EKCalendarChooser, UITextView
-
选择器方法 / 手势代码
-
SelectPerson 事件 / ABPeoplePickerNavigationController
-
序列化数据 / 序列化和反序列化数据
-
SerialPortOwnerChanged 事件 / MidiClient
-
ServiceAdded 事件 / CBPeripheralManager
-
SetImage 方法 / UIButton
-
Settings.bundle / 内置系统
-
设置文件
-
设置配置 / 设置设置文件
-
处理器类 / 处理器类
-
数据类 / 数据类
-
-
设置系统
-
关于 / 创建自己的设置系统
-
文件,设置配置 / 设置设置文件
-
-
SetupChanged 事件 / MidiClient
-
ShouldReturn
- 使用 / 使用 ShouldReturn
-
ShouldReturn 事件 / EntryElement
-
ShowUserLocation / 添加地图
-
单视图,项目类型 / 应用类型及其视图类型
-
SK 类
-
SKProductsRequest / SKProductsRequest
-
SKRequest / SKRequest
-
SKStoreProductViewController / SKStoreProductViewController
-
-
SKProductsRequest / SKProductsRequest
-
SKRequest / SKRequest
-
SKStoreProductViewController / SKStoreProductViewController
-
Sleep() 调用 / 死锁
-
滑块 / 其他控件
-
速度
- 计算 / 计算你的速度
-
SQL
- 替换,使用 LINQ / 用 LINQ 替换 SQL
-
SQLite
-
关于 / 使用 SQLite
-
安装 / 安装和设置 SQLite
-
-
SQLite 辅助类
-
关于 / 设置 SQLite 辅助类
-
方法 / 编写辅助类方法
-
数据,添加到数据库 / 向数据库添加数据
-
-
开始事件 / UITextField, UITextView
-
开始监听 / 使用 Xamarin.Mobile 的 GPS
-
状态更改事件 / GKMatch
-
状态更新事件 / CBPeripheralManager
-
停止监听 / 使用 Xamarin.Mobile 的 GPS
-
停止事件 / NSNetService
-
字符串元素 / 用于标准的 UITextField, BadgeElement, BaseBooleanImageElement, GlassButton, LoadMoreElement, MessageElement, 和 StringElement
-
样式多行元素 / 用于标准的 UITextField
-
样式字符串元素 / 用于标准的 UITextField, StyledStringElement
-
同步事件处理
- 与异步事件处理对比 / 同步与异步事件处理对比
-
同步行走 / 同步行走
-
系统
- 内置设置 / 创建自己的设置系统
-
System.ComponentModel 命名空间 / 在您的应用中使用后台线程
-
System.Threading.Tasks
- 使用 / 使用 System.Threading.Tasks
-
System.Threading.Tasks 命名空间 / 使用 System.Threading.Tasks
-
SystemSound 方法 / 短文件
T
-
TabBar / 其他控件
-
TabBarItem / 其他控件
-
TabBars
-
关于 / TabBars
-
处理,在代码中 / 在代码中处理 TabBar
-
-
标签页,项目类型 / 应用程序类型及其视图类型
-
表视图
- 索引 / TableView 上的索引
-
表视图 / 其他视图
-
TableViewCell / UITableViewCell
-
表视图方法
- 关于 / UITableView 和 UITableViewCell
-
捕获手势 / 手势代码
-
捕获事件 / BadgeElement, BaseBooleanImageElement, GlassButton, LoadMoreElement, MessageElement, 和 StringElement
-
Task
参数 / 一个更实际的例子 -
任务
-
在线程上使用,遇到的问题 / 在线程上使用任务时的问题
-
和事件处理器 / 任务和事件处理器
-
-
testappViewController.cs 文件 / 使用 Xcode 创建用户界面
-
testappViewController.designer.cs 文件 / 使用 Xcode 创建用户界面
-
testappViewController_iPad.xib 文件 / 使用 Xcode 创建用户界面
-
testappViewController_iPhone.xib 文件 / 使用 Xcode 创建用户界面
-
TestFlight
-
关于 / TestFlight
-
配置 / 配置
-
应用,注册 / 注册应用,返回注册应用
-
开发者配置文件,创建 / 创建开发者配置文件
-
证书,创建 / 创建您的证书
-
启用,在 Xamarin Studio 中 / 在 Xamarin Studio 中启用 TestFlight
-
在 TestFlight 上注册 / 在 TestFlight 上注册
-
设备,邀请 / 邀请和注册设备
-
构建到 / 构建到 TestFlight
-
应用,发布 / 发布您的应用
-
应用,清单 / 应用清单
-
图标,尺寸 / 图标尺寸
-
-
TextChanged 事件 / UISearchBar
-
TextField / 其他控件
-
TextLabel / UITableViewCell
-
文本消息
-
发送 / 发送和接收文本消息
-
接收 / 发送和接收文本消息
-
-
文本视图 / 其他视图
-
线程
-
关于 / 线程概念, 线程简介
-
UI 线程 / 主 UI 线程
-
死锁 / 死锁
-
新线程,从主 UI 线程启动 / 从主 UI 线程启动新线程
-
锁,使用 / 使用锁
-
应用程序中的后台线程,使用 / 在应用程序中使用后台线程
-
BackgroundWorker 线程 / BackgroundWorker
-
ThreadPool.QueueUserWorkItem / ThreadPool.QueueUserWorkItem
-
System.Threading.Tasks, 使用 / 使用 System.Threading.Tasks
-
-
ThreadPool.QueueUserWorkItem
- 关于 / ThreadPool.QueueUserWorkItem
-
ThruConnectionsChanged 事件 / MidiClient
-
TimeElement / 对于标准的 UITextField
-
ToolBar / 其他控件
-
工具栏
- 添加,到键盘 / 将工具栏添加到键盘
-
TouchCancel 事件 / 事件和控件参考
-
TouchDown 事件 / 事件和控件参考
-
TouchDownRepeat 事件 / 事件和控件参考
-
TouchDragEnter 事件 / 事件和控件参考
-
TouchDragExit 事件 / 事件和控件参考
-
TouchDragInside 事件 / 事件和控件参考
-
TouchDragOutside 事件 / 事件和控制参考
-
TouchUpInside 事件 / 将事件附加到多个控件, 在编程环境中, 事件和控制参考
-
TouchUpOutside 事件 / 事件和控制参考
-
TransientConnection 标志 / 访问互联网
-
翻译,识别器状态 / 其他 UIGestureRecognizerState 值
U
-
UIAccelerometer / UIAccelerometer
-
UIActionSheet / UIActionSheet 和 UIAlertView
-
UIActivityIndicatorView 类 / 活动指示器和进度视图
-
UIAlertView / UIActionSheet 和 UIAlertView
-
UIButton
- 关于 / UIButton, UIButton
-
UIButtonBarItem / UIButtonBarItem
-
UIButton 类 / 控件选择
-
UIClasses
-
UIAccelerometer / UIAccelerometer
-
UIActionSheet / UIActionSheet 和 UIAlertView
-
UIAlertView / UIActionSheet 和 UIAlertView
-
UIButtonBarItem / UIButtonBarItem
-
UIImagePickerController / UIImagePickerController
-
UIPageViewController / UIPageViewController
-
UIPopoverController / UIPopoverController
-
UIPrintInteractionController / UIPrintInteractionController
-
UIScrollView / UIScrollView
-
UISearchBar / UISearchBar
-
UISplitViewController / UISplitViewController
-
UITabBar / UITabBar
-
UITabBarController / UITabBarController
-
UITextField / UITextField
-
ScrollView / UITextView
-
UITextView / UITextView
-
UIView / UIView
-
UIWebView / UIWebView
-
-
UICollectionView
-
关于 / UICollectionView
-
单元 / 单元格
-
补充视图 / 补充视图
-
装饰视图 / 装饰视图
-
数据源 / 数据源
-
单元重用 / 单元重用
-
-
UICollectionView 类 / 单元格
-
UIColor
- 关于 / UIColor
-
UI 控件
-
关于 / UI 控件
-
选择 / 控件选择
-
-
UIControlStates
- 关于 / UIControlStates
-
UIGestureRecognizerState 值
-
可能状态 / 其他 UIGestureRecognizerState 值
-
失败状态 / 其他 UIGestureRecognizerState 值
-
平移状态 / 其他 UIGestureRecognizerState 值
-
速度状态 / 其他 UIGestureRecognizerState 值
-
-
UIImagePickerController / UIImagePickerController
- 使用,用于访问相机 / 访问相机(原生)
-
UIImageView
-
关于 / UIImageView
-
FromFile / UIImageView
-
FromImage / UIImageView
-
FromResource / UIImageView
-
FromBundle / UIImageView
-
从数据加载 / UIImageView/ UIButton, 使用后释放内存
-
-
UILabel
- 关于 / 颜色、按钮和标签
-
UILabel 类 / UIButton
-
UILongPressGestureRecognizer 类 / 手势
-
UIPageControl 方法 / 页面控制
-
UIPageViewController / UIPageViewController
-
UIPanGestureRecognizer 类 / 手势
-
UIPinchGestureRecognizer 类 / 手势
-
UIPopoverController / UIPopoverController
-
UIPrintInteractionController / UIPrintInteractionController
-
UIProgressView 类 / 活动指示器和进度视图
-
UIRotationGestureRecognizer 类 / 手势
-
UIScrollView
- 关于 / UIScrollView/ UIScrollView
-
UISearchBar / UISearchBar
-
UISplitViewController / UISplitViewController
-
UIStepper
- 关于 / UIStepper
-
UISwipeGestureRecognizer 类 / 手势
-
UITabBar / UITabBar
-
UITabBarController / UITabBarController
-
UITableView
-
使用 UITableView 进行导航 / 使用 UITableView 进行导航
-
代码中 / 代码中
-
使用 Xcode / 使用 Xcode
-
用于导航 / 使用 UITableView 进行导航
-
根视图,返回 / 返回根视图
-
-
UITableViewCell
-
关于 / UITableViewCell
-
表格中的单元格,可重用 / 表格中的可重用单元格
-
部分 / 部分和行
-
行 / 部分和行
-
在 TableView 上的索引 / TableView 上的索引
-
-
UITableViewCell 方法 / UITableView 和 UITableViewCell
-
UITableViewCellStyle
-
子标题 / UITableViewCell
-
Value1 / UITableViewCell
-
Value2 / UITableViewCell
-
-
UITableView 方法 / UITableView 和 UITableViewCell
-
UITapGestureRecognizer 类 / 手势
-
UITextField
- 关于 / 对于标准的 UITextField/ UITextField
-
UITextField 方法 / 对于标准的 UITextField
-
UITextView / UITextView
-
UI 线程
-
关于 / 主 UI 线程
-
新线程,从主 UI 线程开始 / 从主 UI 线程启动新线程
-
-
UIView / UIView
-
UIViewElement / 对于标准的 UITextField
-
UIWebView
- 关于 / UIWebView/ UIWebView
-
未知人员视图控制器 / ABUnknownPersonViewController
-
卸载事件 / IGameWindow, IPhoneOSGameView
-
更新特征值事件 / CBPeripheral
-
更新标题事件 / CLLocationManager
-
更新位置事件 / CLLocationManager
-
更新名称事件 / CBPeripheral
-
更新通知状态事件 / CBPeripheral
-
更新状态事件 / CBCentralManager
-
更新文本记录数据事件 / NSNetService
-
更新值事件 / CBPeripheral
-
更新框架事件 / IGameWindow, IPhoneOSGameView
-
用户交互启用 / 添加地图
-
用户界面
- 创建,使用 Xcode / 使用 Xcode 创建用户界面
V
-
值更改事件 / 其他重要控制事件, BoolElement
-
速度,识别器状态 / 其他 UIGestureRecognizerState 值
-
视频
-
录制 / 录制视频, 录制视频
-
保存 / 保存视频
-
-
视频,播放
-
关于 / 播放视频
-
外部 URL / 外部 URL
-
内部源 / 内部源
-
从照片库中 / 从照片库中
-
-
查看
-
关于 / 视图和视图控制器
-
活动指示器视图 / 其他视图
-
进度视图 / 其他视图
-
收集视图 / 其他视图
-
可复用集合视图 / 其他视图
-
表视图 / 其他视图
-
图片视图 / 其他视图
-
文本视图 / 其他视图
-
网页视图 / 其他视图
-
地图视图 / 其他视图
-
滚动视图 / 其他视图
-
选择器视图 / 其他视图
-
广告横幅视图 / 其他视图
-
GLKit 视图 / 其他视图
-
实现,使用多个视图控制器 / 使用多个视图控制器实现视图
-
-
视图出现事件 / DialogViewController
-
视图控制器
- 关于 / 视图和视图控制器
-
视图控制器选择事件 / UITabBarController
-
ViewDidLoad() 方法
- 关于 / UIScrollView
-
视图消失事件 / DialogViewController
-
可见性改变事件 / IPhoneOSGameView
-
VisualStudio
-
启用,以构建 iOS 应用程序 / 启用 Visual Studio 以构建和运行 iOS 应用程序, 在 Mac 上
-
启用,以运行 iOS 应用程序 / 启用 Visual Studio 以构建和运行 iOS 应用程序
-
在 Mac 上 / 在 Mac 上
-
在 PC 上 / 在 PC 上
-
-
音量
- 修改 / 修改音量
W
-
已取消事件 / GKMatchmakerViewController
-
网页视图 / 其他视图
-
WHERE 语法 / SELECT 和 WHERE 在 LINQ 中的用法 – 常见混淆原因
-
小部件
-
添加 / 使用 Xcode 创建用户界面
-
连接 / 使用 Xcode 创建用户界面
-
-
将开始自定义项事件 / UITabBar
-
将要消失事件 / QLPreviewController, UIActionSheet 和 UIAlertView
-
WillDismissPrinterOptions 事件 / UIPrintInteractionController
-
WillEndCustomizingItems 事件 / UITabBar
-
WillEndDragging 事件 / UIScrollView, UITextView
-
WillEvictObject 事件 / NSCache
-
WillHideViewController 事件 / UISplitViewController
-
WillLoad 事件 / AdBannerView, AdInterstitialAd
-
WillPresent 事件 / UIActionSheet 和 UIAlertView
-
WillPresentPrinterOptions 事件 / UIPrintInteractionController
-
WillPresentViewController 事件 / UISplitViewController
-
WillPublish 事件 / NSNetService
-
WillResolve 事件 / NSNetService
-
WillShowViewController 事件 / UIImagePickerController, UISplitViewController
-
WillStartJob 事件 / UIPrintInteractionController
-
WillStartLoadingMap 事件 / MKMapView
-
WillStartLocatingUser 事件 / MKMapView
-
WillTransition 事件 / UIPageViewController
-
窗口小部件 / 控件和小部件
-
Windows 7
- Bonjour 服务,设置 / 在电脑上
-
WindowStateChanged 事件 / IPhoneOSGameView
-
WorkerCompleted 事件 / BackgroundWorker
-
WriteRequestsReceived 事件 / CBPeripheralManager
-
WroteCharacteristicValue 事件 / CBPeripheral
-
WroteDescriptorValue 事件 / CBPeripheral
X
-
Xamarin.Android
-
安装 / 安装 Xamarin.iOS 和 Xamarin.Android
-
必需条件 / 安装 Xamarin.iOS 和 Xamarin.Android
-
软件,下载 / 下载软件
-
软件,安装 / 安装软件
-
关于 / 为 Android 开发安装附加代码
-
-
Xamarin.iOS
-
安装 / 安装 Xamarin.iOS 和 Xamarin.Android
-
需求 / 安装 Xamarin.iOS 和 Xamarin.Android
-
软件,下载 / 下载软件
-
软件,安装 / 安装软件
-
-
Xamarin.iOS VisualStudio
-
在 Mac 上 / 在 Mac 上
-
在 PC 上 / 在 PC 上
-
-
Xamarin.Mobile 组件 / 使用相机, 访问相机 (Xamarin.Mobile)")
-
关于 / 带有 Xamarin.Mobile 的 GPS
-
带有 GPS 的 / 带有 Xamarin.Mobile 的 GPS
-
-
Xamarin IDE(集成开发环境)
- 关于 / 针对 iOS 用户
-
XamarinInstaller.exe 文件 / 下载软件
-
Xamarin Studio
- 用于启用 TestFlight / 在 Xamarin Studio 中启用 TestFlight
-
Xcode / 安装软件
-
用于创建用户界面 / 使用 Xcode 创建用户界面
-
开始 / 使用 Xcode 创建用户界面
-
-
Xcode 设计器
- 添加 / 使用 Xcode 创建用户界面
Z
-
ZoomEnabled / 添加地图
-
ZoomingEnded 事件 / UIScrollView, UITextView
-
ZoomingStarted 事件 / UIScrollView, UITextView








浙公网安备 33010602011771号