14、利用iCloud和Core Data构建跨平台食谱应用

利用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[复制文件到目标位置]

未来,我们可以基于这个基础,进一步完善和扩展应用的功能,例如添加更多的交互元素、优化界面设计、实现数据的备份和恢复等,让这个食谱应用更加实用和强大。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值