利用iCloud和Core Data构建跨平台食谱应用
1. iCloud与Core Data概述
iCloud是iOS/OS X和Core Data中一项强大的功能。尽管它目前还不够完美,但会不断迭代改进。在iOS 6.0和OS X 10.8系统中,它已具备可用性。随着用户对移动设备的使用愈发熟练,他们期望应用程序能够在不同设备间实现数据同步,而iCloud正是解决这一问题的方案。
此前,我们主要关注iOS平台,因为它是Core Data运行的主流平台。不过,Core Data最初是为OS X系统设计的,而且目前所涉及的大部分内容在OS X上的运行方式与iOS基本相同,但仍存在一些差异,下面我们将深入探讨。
2. 开发桌面应用的基础
我们之前主要聚焦于Core Data在iOS端的应用,虽然多数内容在Mac OS X和iOS上的表现一致,但仍有一些不同之处。接下来,我们将更深入地研究Core Data在Mac OS X上的应用。
为了研究Core Data在Mac OS X上的应用,我们将沿用熟悉的模式,先创建一个应用作为示例基础。由于我们已经开发了一个可以通过iCloud共享数据的iOS应用,因此开发一个能够同步数据的桌面端对应应用是很有必要的。
2.1 应用概述
在开始构建应用之前,我们先简要了解一下用户界面(UI)的外观和功能:
-
部分1
:允许用户编辑单个食谱的信息。用户可以在列表中选择一个食谱并编辑其详细信息。
-
部分2
:允许用户输入所选食谱的食材。每个食谱都有自己的食材列表,用户可以在此添加、查看和编辑食材。
-
部分3
:允许用户添加食谱的图片作为参考。这是一个只读元素,图片的添加将通过主菜单进行操作。
2.2 应用设计
对于改进后的桌面应用,我们将从头开始。启动Xcode,按照步骤创建应用并使其达到可用状态。到本章结束时,你可能会惊讶于创建Mac OS X应用所需的步骤如此之少。这种便捷性和高效性正是Cocoa开发的魅力和优势所在。结合Core Data,开发效率更是成倍提升。虽然我们已经习惯了在iOS上进行相对快速的开发,但在Mac OS X上进行开发,至少在原型阶段,仍然更加容易和快捷。一旦我们构建好原型并确认应用能够实现预期功能,所有的“小细节”就会变得清晰起来,这通常被亲切地称为“第二个80%”。
2.3 共享数据模型
由于我们已经为iOS开发了应用,因此希望尽可能地利用已有的知识。借助Core Data,这种复用是非常广泛的。Mac OS X和iOS的xcdatamodel文件结构相同,这意味着我们可以使用在iOS上使用过的相同数据模型。此外,由于数据模型可以共享和复用,数据对象也可以实现共享和复用。
目前,Xcode创建的项目中,.xcodeproj文件位于项目所需的其他所有文件之上。这样的设置是为了更方便地组织项目,以便在iOS和Mac OS X之间共享组件。因此,我们通过创建一个名为Desktop的新项目来启动桌面项目。建议将新项目创建在临时目录中,例如桌面。新项目创建完成后,退出Xcode。使用Finder将新项目的内容移动到现有项目中。我们可以通过重命名项目来进一步明确项目结构,但主要目标是共享数据模型和数据对象。最终结果如下:
当我们将数据对象和数据模型置于可共享的位置后,重新打开Desktop.xcodeproj项目,并将文件夹拖入Xcode,添加到桌面项目中。此时,我们的数据模型就构建完成了。
3. 构建控制器层
在开发Cocoa应用的经验中,Interface Builder在任何项目中都占据着重要地位。现在我们已经构建了数据模型,并在Xcode中准备好了模板,接下来是时候构建用户界面了。
在进入Interface Builder的有趣环节之前,有两点需要注意:
- 这不会是像Delicious Library那样的应用。我们将使用标准的小部件来构建应用,以尽量减少非Core Data代码的使用。
- 我们可以为这个应用添加很多功能,但我们将暂时保留。额外的功能虽然有用,但可能会分散我们当前将主要功能从iOS移植到桌面的注意力。一旦建立了新的基础,我们就可以开始添加功能了。
我们首先要处理的用户界面部分是XIB文件中的对象。与大多数应用一样,我们需要将AppDelegate添加到XIB中,以便它在启动时被实例化,并正确地连接到应用本身。
3.1 将AppDelegate添加到XIB
根据Xcode中模板的不同,打开MainMenu.xib时,AppDelegate可能已经存在于XIB文件中。如果是这样,那就太好了!可以直接进入下一部分。如果不存在,我们需要添加它。此外,请注意,根据运行的Xcode版本,应用程序委托的名称可能会在前面加上应用程序名称。如果是这种情况,我们必须在这种上下文中将该名称替换为对AppDelegate的引用。要将AppDelegate添加到XIB文件,请按照以下步骤操作:
1. 在库面板中找到NSObject,将其拖到XIB的窗口中。
2. 点击NSObject的名称,当它可编辑时,将其更改为AppDelegate。
3. 转到检查器面板的“身份”选项卡,将对象的类从NSObject更改为AppDelegate。
4. 从应用程序右键拖动到AppDelegate对象,然后选择“委托”。
完成这些步骤后,当我们的应用启动时,AppDelegate类将被实例化,并且应用程序将向它发送所有委托消息。
3.2 添加NSArrayController对象到XIB
我们希望应用程序能够在单个窗口中显示所有食谱的列表。为了实现这一点,我们需要能够引用数据以便进行显示。因此,我们将三个NSArrayController对象添加到XIB中,这些对象将引用相关数据。我们的窗口现在将引用这些NSArrayController对象。NSArrayController对象添加并配置完成后,XIB的外观如下:
要为食谱实体添加NSArrayController,请按照以下步骤操作:
1. 在库中找到NSArrayController对象,将其拖到XIB文件中。
2. 点击NSArrayController的名称,当它可编辑时,将其重命名为Recipes。如果难以进入编辑模式,可以在Interface Builder的身份检查器中更改名称,并在文档部分更改标签字段。
3. 在检查器的“属性”选项卡上,将模式从“类”更改为“实体”,并将实体名称更改为Recipe。
4. 确保选中“准备内容”标志。
5. 在检查器的“绑定”选项卡上,将ManagedObjectContext绑定到AppDelegate,模型键路径为managedObjectContext。
现在我们已经构建了Recipe实体的NSArrayController,接下来需要配置另外两个NSArrayController实例,一个用于RecipeIngredient实体,另一个用于Type实体。Type的NSArrayController的配置步骤与Recipe实体的NSArrayController相同,但需要将实体名称设置为Type,以便它能够填充Type对象。除了这一差异外,我们按照前面的步骤完成Type的NSArrayController的配置。
将最后一个NSArrayController(即食谱食材的NSArrayController)的身份设置为RecipeIngredient。在属性检查器中,选择“实体”,并将实体名称设置为RecipeIngredient。设置绑定方式与之前相同,但有一个额外的更改:在检查器的“绑定”选项卡上,启用控制器内容中的内容集,并将其指向食谱的NSArrayController,控制器键为“选择”,模型键路径为“食材”。
现在,我们已经准备好构建NSWindow了。
4. 构建用户界面
现在所有的数据对象都已正确引用,是时候构建用户界面了。虽然这个界面短期内不太可能获得苹果设计奖,但它可以让我们查看和编辑模型中的所有数据对象。我们正在构建的窗口如下所示:
让我们逐步了解设置过程,如需更详细的审查,请参考相关章节。
4.1 构建食谱源列表
我们正在构建的界面的第一部分位于左上角,搜索字段下方。这个视图是一个配置了一列的NSTableView。它没有水平滚动条,但有一个自动显示的垂直滚动条。此外,它的表头是隐藏的,高亮模式设置为“源列表”。滚动条的配置在NSScrollView的检查器中进行,列数和高亮选项在NSTableView检查器中进行配置。每个检查器可以通过Control + Shift点击(或Shift + 右键点击)NSTableView并从列表中选择相应的视图来访问。如果检查器不在屏幕上,可以通过“工具”>“检查器”菜单项将其显示出来。
要将此表绑定到我们的食谱的NSArrayController对象,我们需要深入操作,获取NSTableColumn,以便告诉该列要显示的内容。我们可以在表视图中点击,直到最终选择NSTableColumn,但幸运的是,有更简单的方法。如前所述,如果我们Shift + 右键点击表,会弹出一个包含所有视图的列表,我们可以从中选择NSTableColumn。
选择NSTableColumn后,我们打开其检查器中的“绑定”选项卡,并将其值绑定到食谱的NSArrayController,控制器键为“排列对象”,模型键路径为“名称”。设置完成后,我们的Recipe实体将显示在这个表中。更重要的是,当我们在列表中点击一个食谱时,该食谱将成为为其余UI提供数据的选择项。
接下来,我们添加用于控制食谱实体创建和删除的按钮。为此,从库中拖动一个NSButton(任意类型均可)到食谱表视图下方。在按钮的“属性”选项卡中,将其图像设置为NSAddTemplate(一个可供我们使用的系统级图像),将其样式更改为“圆角矩形”,如果有标题则将其删除。此外,我们必须选择“布局”>“调整大小以适应”菜单项,使按钮达到完美大小。完成添加按钮的这些步骤后,从主菜单中选择“编辑”>“复制”创建第二个按钮,并将第二个按钮的图像更改为NSRemoveTemplate。
然后,我们将NSTableView下方的按钮“连接”起来,直接将它们连接到食谱的NSArrayController。添加按钮将连接到add:动作,删除按钮将连接到remove:动作。这些按钮可以通过按住Control键,点击按钮,并从选择器发送的动作拖动到NSArrayController来连接到它们的动作。通过这些小改动,我们现在可以随意添加和删除食谱实体了。
4.2 添加食谱详细信息
现在源列表已经设置好,是时候添加食谱的详细信息了。这些详细信息(名称、份数、描述和类型)与食谱的NSArrayController上现在有效的选择控制器键相关联。因此,当用户在列表中点击时,食谱的相关详细信息将被选中。
前两个项目是文本字段,第三和第五个元素是弹出框,最后一个元素是文本区域。除了类型和作者的弹出框外,这些详细信息的配置方式非常相似。所有这些都通过控制器键“选择”和相应的模型键路径(如名称、份数和描述)与食谱的NSArrayController对象建立了值绑定。关于文本区域的一个提示:务必关闭“富文本”设置。当此设置开启时,该字段期望的是NSAttributedString而不是普通字符串,这可能会导致混淆。此外,为了规范操作,我们应该将一个NSNumberFormatter拖到“份数”文本字段,并将其配置为只允许输入整数。
弹出框的配置稍微复杂一些。虽然每个弹出框都与所选食谱相关联,但我们需要用值填充整个食谱列表。这些值属于关系另一侧的其他实体。例如,我们希望显示所选食谱的类型,但实际上需要显示的是所选食谱类型的名称。幸运的是,这是一个相当常见的用例,并且有内置工具可以处理。每个弹出框都设计为与一个NSArrayController相关联,每个NSArrayController引用我们希望在弹出框中显示的实体。此外,我们可以定义每个弹出框以显示这些实体的特定值。
我们需要设置三个部分的值,如下所示:
|部分|设置内容|
| ---- | ---- |
|内容部分|将其绑定到类型的NSArrayController,控制器键设置为“排列对象”。这一步指示弹出框从类型的NSArrayController获取要处理的对象。|
|内容值部分|将其绑定到类型的NSArrayController,控制器键设置为“排列对象”。我们还需要将模型键路径设置为“名称”。这一步指示弹出框访问名称属性以获取显示值。|
|所选对象部分|将其绑定到食谱的NSArrayController,控制器键设置为“选择”。此外,我们需要将模型键路径设置为“类型”。这指示弹出框执行以下操作:首先,检查所选食谱的类型关系并显示相关值;其次,当用户在类型弹出列表中选择不同的值时,更新所选食谱;最后,监视食谱NSArrayController,如果用户选择了不同的食谱,则更新自身。|
一旦我们设置好类型弹出框,就需要配置作者弹出框。这里的设置与类型弹出框相同,只是要使用的NSArrayController是作者的NSArrayController,所选对象的模型键路径设置为“作者”。
4.3 添加食材
现在食谱部分的用户界面已经完成,是时候添加食材了。食材构成了我们窗口左下角的表格。幸运的是,这部分的设置与食谱源列表几乎相同。然而,与食谱源列表不同的是,我们的NSTableView将有三列,显示表头和垂直滚动条,并隐藏水平滚动条。我们将列标题设置为“名称”、“数量”和“计量单位”。
就像在食谱源列表中一样,我们将NSTableView中每列的值绑定到我们的食谱食材的NSArrayController,使用控制器键“排列对象”和相应的模型键路径:名称、数量和计量单位。数量列(更具体地说,数量列中的表格单元格)还应该分配一个NSNumberFormatter,以便正确格式化其所持有的值。配置完成后,我们可以看到所选食谱的食材。
请记住,我们已经将食谱食材的NSArrayController配置为依赖于所选食谱,因此此时我们无需进行额外的操作。与食谱源列表一样,添加和删除按钮通过将它们绑定到食谱食材的NSArrayController对象(分别为add:和remove:方法)进行配置。至此,食材部分完成,我们的用户界面也接近完成。
graph LR
A[开始] --> B[创建桌面项目]
B --> C[共享数据模型]
C --> D[构建控制器层]
D --> E[添加AppDelegate到XIB]
D --> F[添加NSArrayController对象到XIB]
F --> F1[配置Recipe的NSArrayController]
F --> F2[配置Type的NSArrayController]
F --> F3[配置RecipeIngredient的NSArrayController]
D --> G[构建NSWindow]
G --> H[构建用户界面]
H --> I[构建食谱源列表]
H --> J[添加食谱详细信息]
H --> K[添加食材]
K --> L[完成用户界面]
5. 添加代码实现图片功能
你可能会好奇代码在哪里。实际上,我们的食谱应用目前已经完全可以正常运行了。无需我们编写任何实际代码,就可以立即开始输入食谱。Cocoa和Core Data的结合使得我们无需编写自定义代码就能开发出这个应用。不过,我们的脚步不会就此停下。
5.1 显示食谱图片
由于我们的iOS版本应用能够拍摄和显示图片,那么桌面版本应用也应该具备添加和显示图片的功能,这才公平合理。幸运的是,从用户界面的角度来看,添加这个功能并不困难。只需将一个NSImageView(也就是图像框)拖到我们的窗口中,并将其值路径设置绑定到食谱的NSArrayController的imagePath,控制器键设置为“选择”。
5.2 导入图片
在将NSImageView添加到用户界面后,我们需要让AppDelegate知晓它的存在。此外,还需要添加一种方式来设置我们食谱实体的图像路径。因此,我们必须更新AppDelegate.swift文件,添加一个用于食谱NSArrayController的IBOutlet和一个用于设置图像路径的IBAction。
// Shared/Desktop/PPRecipes/AppDelegate.swift
@IBOutlet var imageView: NSImageView? = nil
@IBOutlet var recipeArrayController: NSArrayController? = nil
@IBOutlet var window: NSWindow? = nil
这个@IBAction,具体来说是
@IBAction func addImage(sender: AnyObject)
,会从我们的主菜单被调用,然后显示一个打开文件对话框。与此同时,我们需要获取对所选食谱的引用,以便能够处理食谱实体。为了实现这一点,我们在AppDelegate中添加一个对在nib文件中实例化的食谱NSArrayController的引用。当食谱的NSArrayController被添加到AppDelegate头文件后,我们需要暂时回到Interface Builder,按住Control键从AppDelegate拖动到食谱的NSArrayController,完成绑定操作。
在此期间,我们要在文件菜单中添加一个菜单项,让用户能够为食谱添加图片。具体操作是确保MainMenu元素在Interface Builder中处于打开状态(它会以浮动菜单的形式显示),然后点击其文件菜单。接下来,我们可以添加一个新的NSMenuItem,或者使用一个未被使用的现有菜单项。由于“另存为”菜单项与我们的应用无关,我们可以将其重命名为“添加食谱图片”。重命名完成后,按住Control键将其拖动到AppDelegate,并将菜单项绑定到我们在头文件中定义的IBAction。
// Shared/Desktop/PPRecipes/AppDelegate.swift
@IBAction func addImage(sender: AnyObject) {
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = false
openPanel.canCreateDirectories = false
openPanel.allowsMultipleSelection = false
guard let window = window else {
fatalError("mainWindow is nil")
}
guard let recipe = recipeArrayController?.selectedObjects.last else {
fatalError("No recipe selected")
}
实现
addImage:
方法时,会显示一个NSOpenPanel,它会以表单的形式附加到窗口上,使其对窗口具有模态性。接着,我们对NSOpenPanel进行一些调整,使其不能选择目录、创建目录或选择多个文件。要注意,在打开面板之前,我们要检查是否已经选择了一个食谱。否则,如果没有食谱,我们就没有可以关联图片的对象。一点点的错误检查可能会起到很大的作用。
由于表单是异步工作的,我们需要添加一个完成块。当用户与NSOpenPanel交互完成后,这个完成块就会被调用。
// Shared/Desktop/PPRecipes/AppDelegate.swift
openPanel.beginSheetModalForWindow(window) { (result) in
if result == NSFileHandlingPanelCancelButton { return }
guard let fileURL = openPanel.URLs.last else {
fatalError("Failed to retrieve openPanel.URLs")
}
let fileManager = NSFileManager.defaultManager()
let support = fileManager.URLsForDirectory(.ApplicationSupportDirectory,
inDomains: .UserDomainMask)
let guid = NSProcessInfo.processInfo().globallyUniqueString
guard let destURL = support.last?.URLByAppendingPathComponent(guid) else {
fatalError("Failed to construct destination url")
}
do {
try fileManager.copyItemAtURL(fileURL, toURL: destURL)
总结
通过以上一系列步骤,我们成功地开发出了一个基于Mac OS X的食谱应用。从最初的iCloud和Core Data的基础了解,到共享数据模型、构建控制器层和用户界面,再到最后添加代码实现图片导入功能,我们逐步完成了整个应用的开发。
整个开发过程中,Core Data和Cocoa的结合为我们带来了高效的开发体验,让我们能够在较少编写自定义代码的情况下实现应用的核心功能。同时,通过合理的配置和绑定,我们实现了数据的有效管理和交互。
以下是整个开发过程的关键步骤总结表格:
|步骤|详细操作|
| ---- | ---- |
|创建桌面项目|启动Xcode,创建名为Desktop的新项目,将其内容移动到现有项目中|
|共享数据模型|利用Mac OS X和iOS相同的xcdatamodel文件结构,共享数据模型和数据对象|
|构建控制器层|添加AppDelegate到XIB,配置NSArrayController对象|
|构建用户界面|构建食谱源列表、添加食谱详细信息、添加食材|
|添加代码|实现图片显示和导入功能|
graph LR
M[添加NSImageView到窗口] --> N[更新AppDelegate.swift]
N --> O[添加IBOutlet和IBAction]
O --> P[绑定菜单项到IBAction]
P --> Q[实现addImage方法]
Q --> R[显示NSOpenPanel]
R --> S[配置NSOpenPanel]
S --> T[添加完成块]
T --> U[复制文件到目标位置]
未来,我们可以基于这个基础,进一步完善和扩展应用的功能,例如添加更多的交互元素、优化界面设计、实现数据的备份和恢复等,让这个食谱应用更加实用和强大。
超级会员免费看
7

被折叠的 条评论
为什么被折叠?



