基于Universal Type Identifiers的App数据导入和导出(一)

版本记录

版本号 时间
V1.0 2020.05.28 星期四

前言

这一篇主要看下使用Universal Type Identifiers进行App中的数据的导入和导出。

开始

首先看下主要内容:

主要看下使用Universal Type Identifiers进行App中的数据的导入和导出,内容来自翻译。

接着看下写作环境

Swift 5, iOS 13, Xcode 11

保存应用程序的数据很重要,但有时仅保存就不会削减数据。 您会发现许多用户还希望将其数据导出到另一个应用程序或从文件导入应用程序数据。

在本教程中,您将学习如何将应用程序的数据导出到电子邮件,将其共享为文件并将其导入回您的应用程序。

打开入门项目。 打开Starter文件夹中的TaskList.xcodeproj。该项目也是使用SwiftUI and Combin进行build的。

您将构建的项目是一个用于保存和跟踪您不想忘记的任务的应用程序。 它具有基本功能,例如添加,编辑和删除任务,以及将其标记为完成。

1. Exploring the App

在深入研究代码之前,您应该对项目结构有所了解。

该项目包含基本数据导出和导入所需的一切。 您将通过修改以下两个文件来增强该项目:

  • ContentView.swift是您启动应用程序时看到的视图。 它列出了数据存储中的所有项目及其优先级。
  • TaskStore.swift是应用程序的数据存储。 每当您更改列表中的某些内容(例如添加一项或将其标记为完成)时,store都会将其当前内容保存到documents目录中的plist文件中。

构建并运行您的项目,在四个优先级下您将看不到任何任务。

因此,现在您没有任何数据可导出。您可以一个接一个地添加项目,但这很繁琐。相反,为什么不导入现有任务列表?您将做到这一点。但是,在开始导入之前,您将创建一个自定义文件类型以简化此过程。


Creating a Custom File Type

您的应用应该能够识别它可以导入的文件。同时,方便用户在看到文件时识别与您的应用兼容的文件类型。

为此,您将创建一个新的文件扩展,并将新文件类型注册为您的应用程序可以导入的文件。在本教程中,您的自定义文件类型将是.RWTL,是Ray Wenderlich Tasks List的缩写。

1. Saving the New File Type

此过程的第一步是在应用程序中使用自定义文件类型。

该示例项目已经包含将任务保存到文件中的代码。由于Task符合Codable,因此使用Swift内置的PropertyListEncoder可以轻松地将任务列表编码为属性列表Data Blob。然后,您可以将数据写入用户设备上的文件中。

要在工作中看到这一点,请打开TaskStore.swift并查看shared实例声明下面的两行:

// 1
static let fileExtension = "plist"

// 2
let tasksDocURL = URL(
  fileURLWithPath: "PrioritizedTasks", 
  relativeTo: FileManager.documentsDirectoryURL)
  .appendingPathExtension(fileExtension)

在这两行中,您是:

  • 1) 将文件扩展名存储为静态变量。
  • 2) 构造用于读取和写入具有文件扩展名的列表数据的URL,并将其保存到名为PrioritizedTasks的文件中。

现在更改第一行,以使其使用新的文件扩展名:

static let fileExtension = "rwtl"

构建并运行,但是请注意没有任何变化。 好吧,您现在看不到任何东西。 继续添加一个项目。 之后,检查应用程序的文档目录,您会发现它创建了一个新的rwtl文件。

如果您逐个文件夹浏览数据文件夹,可能会很麻烦。 更好的方法是在TaskStore加载其优先任务之前打印taskDocURL的值。 为此,请将以下行添加到TaskStoreinit顶部:

print(tasksDocURL)

运行应用程序。 然后打开Finder,键入Shift-Command-G,然后从控制台复制路径,而不使用起始file://协议。

2. Understanding the Uniform Type Identifier

您使用的是上方的自定义扩展名(“ rwtl”)。 您可以输入任何名称作为扩展名,它都会起作用。 那么系统如何知道用于每个扩展的应用程序?

JPG图片为例。 jpegjpg都是有效的扩展名。 为每个扩展名注册具有相同信息的相同文件没有意义。 但是系统如何知道它们是同一类型?

Apple为此创建了统一类型标识符(Uniform Type Identifier)。 它使您可以定义类型,而不能具有不同的标识符和表示形式。

注意:您可以通过Apple关于UTI的文档 Apple’s documentation on UTIs深入了解Uniform Type Identifier

3. Registering Your New UTI

要向您的应用注册新文件类型:

  • 1) 在项目导航器中选择项目。
  • 2) 从Targets列表中选择app target(不是项目项)。
  • 3) 单击Info选项卡。

Document Types部分中,添加一个新条目并按以下方式对其进行配置:

  • NameTaskList数据
  • Typescom.raywenderlich.TaskList.TaskListData
  • Icon:单击+按钮,然后选择唯一可用的图像,其文件名为icon-doc.png
  • Additional document type properties:添加两个条目
    • CFBundleTypeRole,类型为String和值Editor
    • 带有String和值OwnerLSHandlerRank**

这将创建一个名为TaskList Data的新UTI,并带有一个关联的唯一标识符和一个图标。使用其他属性,您可以指定TaskListTaskList数据文件的编辑者和所有者,以便iOS知道对其进行最大程度的控制。


Defining Import Types

到目前为止,您已定义了新的UTI,但未输入有关新扩展名的任何信息。接下来,您将要执行此操作。

您将在导出之前先从导入步骤开始,尽管您在此处所做的许多事情也将支持导出过程。

仍在Info选项卡上时,在Document Types下面查找Imported UTIs部分。在本节中添加具有以下信息的新项目。

  • DescriptionTaskList数据
  • Identifiercom.raywenderlich.TaskList.TaskListData
  • Conforms Topublic.data
  • Icon:单击+按钮,然后选择唯一可用的图像,其文件名为icon-doc.png
  • Additional imported UTI properties:添加一个类型为Dictionary的条目UTTypeTagSpecification。在其内部,添加键类型为Arraypublic.filename-extension键的一项,并在其内部添加两个值rwtlRWTL

注意:要在适当的列表编辑器中的字典或数组内添加条目,必须单击键名旁边的disclosure triangle。如果三角形朝下,则可以添加条目。

这样,您可以使用两个文件扩展名:.rwtl.RWTL连接先前创建的文档类型。 当用户尝试使用具有以下扩展名之一的文件时,iOS现在将启动您的应用程序。 定义导入的UTI时,请确保该标识符与您在Document Types部分中定义的类型匹配。

您还需要让iOS知道您打算如何导入应用程序数据。 打开Info.plist并添加以下两个键:

  • LSSupportsOpeningDocumentsInPlace的值为NO,指示在将文档导入到您的应用程序之前应先对其进行复制。
  • UISupportsDocumentBrowser的值为NO,表示您的应用不是基于文档的应用。

Build并运行,然后在模拟器中关闭该应用以显示主屏幕。 还记得起始项目里的PrioritizedTasks.rwtl文件吗? 尝试将该文件拖放到模拟器上。

请注意,此操作将启动应用程序。


Importing Files to your App

尽管您的应用已启动,但其内容没有改变。如果您考虑一下,那是有道理的。项目中尚无实现来加载文件内容。接下来,您将对其进行修复。

1. Using Scenes

iOS 13引入了一个名为Scenes的新概念,该概念使您可以在应用程序中打开多个窗口。它在iPad上非常方便,从Xcode 11创建新项目时,实际上是默认情况下得到的。场景Scenes也很重要,因为iOS使用它们来通知用户用户正在尝试打开您的文件之一。

入门项目已经在使用Scenes,因此您准备学习如何导入应用程序数据。

打开SceneDelegate.swift并在类的最后添加最后的大括号之前添加以下代码:

// 1
func scene(
  _ scene: UIScene,
  openURLContexts URLContexts: Set
) {
  // 2
  guard let urlContext = URLContexts.first else {
    return
  }
  // 3
  TaskStore.shared.importPrioritizedTasks(from: urlContext.url)
}

您在上面的代码中正在做的事情是:

  • 1) iOS通过openURLContexts传递有关启动应用程序的源的信息。 如果用户通过点击文件启动应用程序,则上下文将包含该文件的URL
  • 2) 您检查至少一个有效的UIOpenURLContext。 如果没有,您将立即return
  • 3) 然后,您将文件的URL提供给TaskStore以供您的应用程序加载。 更新存储对象后,视图会重新加载新数据。

在应用程序运行时,Build并运行,然后将相同的PrioritizedTasks.rwtl文件再次拖放到模拟器上。

注意:在撰写本文时,macOS Catalina在将拖放的文件复制到Simulator时会遇到一些问题,尤其是从Downloads or Documents文件夹中进行拖动时。 如果将文件移到home目录(/Users/your-user-name)并从那里拖动,它应该可以起作用。

哇,新任务就出现在您的眼前! 多么酷啊!

2. Working Without Scenes

如果您想将此应用到非基于场景(scene-based)的应用程序怎么办? 您可以,但是导入数据的方法不同。 即使该逻辑不适用于该项目,您现在也要添加该逻辑。 (它也不会破坏任何内容,如果您以后决定构建非基于场景的应用程序,它将作为参考。)

将以下内容添加到AppDelegate.swift

func application(
  _ app: UIApplication, 
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  TaskStore.shared.importPrioritizedTasks(from: url)
  return true
}

还记得您在scene delegate中实现的方法来获取打开应用程序的文件的URL吗? 此方法执行相同的操作,但是在app delegate中。 在这种情况下,您会收到直接导入文件的路径,因此可以立即开始使用它。

但是,除非您实现application(_:didFinishLaunchingWithOptions :)applicationDidFinishLaunching(_ :),否则iOS不会调用此方法。 将以下方法添加到该类:

func applicationDidFinishLaunching(_ application: UIApplication) { 
}

即使该方法没有执行任何操作,但只要实现该方法,iOS即可调用application(_:open:options :)


Defining Export Types

现在,您已经有了TaskListimport逻辑,接下来可以进行下一步:设置导出逻辑。

1. Setting Up the Export Logic

您为导入过程所做的设置使导出设置更加容易。导出信息已经在您保存到文档目录中的文件中。通过创建的rwtl文件类型,您无需进行任何转换即可使用该文件。

但是请稍等...在Info选项卡下,Imported UTIsExported UTIs有什么区别?第一个具有您可以导入的文件类型的所有定义,第二个具有您可以导出的文件类型的所有定义吗?不完全是。

尽管您尚未向Exported UTIs添加任何内容,但已经具有导出文件所需的内容。如果您尝试导出rwtl文件,可能会认为您收到错误消息,提示您未在Exported UTIs中定义此类型。导出将起作用。那么,此部分的目的是什么?

可以将这些配置想成是:我想导入另一个应用程序创建的文件。我想导入该应用程序创建的文件。

Exported UTIs用于第二种情况。您的应用创建了.rwtl文件,因此将其归类为导入没有意义。因此,您无需在此处注册,而是在 Exported UTIs部分中进行注册。

2. Switching from Import to Export

要在项目中进行此切换,请用Source Code方式打开Info.plist(在Supporting Files文件夹中)。

查找文本UTImportedTypeDeclarations并将其替换为UTExportedTypeDeclarations

注意:项目的Info选项卡将不会反映您在源代码中对Info.plist所做的更改。 您需要重新打开项目才能看到这些。

为确保一切正常,请从模拟器中删除该应用程序。 构建并运行并将PrioritizedTasks.rwtl再次拖放到Simulator上。

一切仍然正常,但是现在您已正确配置了导出过程。


Exporting Files with Activity View Controller

Share Sheet,也称为UIActivityViewController,是在iOS上共享信息的最便捷方法。 它提供了很多渠道来做到这一点,并且它知道设备上哪些可用的应用程序可以处理用户想要共享的信息。

ContentView.swift中,在HStack中现有的添加按钮之后添加以下代码。

// Share Sheet
Button(action: { self.shareSheetIsPresented = true }) {
  Image(systemName: "square.and.arrow.up")
}
.frame(width: 44, height: 44, alignment: .center)
.sheet(isPresented: $shareSheetIsPresented) {
  ShareSheet(
    activityItems: [TaskStore.shared.tasksDocURL],
    excludedActivityTypes: [])
}

这将创建一个Share activity,并提供提供保存的任务列表文件的URL。`UIActivityViewController可以识别URL指向文件而不是网页,因此将其视为文件。

ShareSheet是入门项目中的SwiftUI视图,它包装了标准UIKit UIActivityViewController

构建并运行,然后单击新按钮以查看其运行情况。

如果您在模拟器中运行此程序,则不会看到与在物理设备上安装的应用程序一样多的应用程序。

1. Excluding Activity Types

现在您已启用共享,但是如果要禁用某些渠道的共享该怎么办? 好吧,UIActivityViewController允许您通过指定要排除的系统已知共享活动来做到这一点。

要排除某些活动,请使用excludeActivityTypes并为其提供要排除的UIActivity.ActivityType枚举值。 一些常见的示例是.postToFacebook,.airDrop.copyToPasteboard

试一试。 在ContentView.swift中,向传递给excludeActivityTypes的数组输入.copyToPasteboard

ShareSheet(
  activityItems: [TaskStore.shared.tasksDocURL],
  excludedActivityTypes: [.copyToPasteboard])

构建并运行,然后点击“共享”按钮以查看区别。

请注意,Copy选项不再可用。

您应该知道的一个缺点是,您只能排除系统可识别的类型。这意味着,如果像这样的应用程序能够导入您的文件,则您将无法将其排除。只能排除在UIActivity.ActivityType枚举中定义的类型。


Exporting Files via Email

如果要通过电子邮件导出信息怎么办?而且,如果您想提供电子邮件收件人,主题和正文,以使用户可以轻松发送它,该怎么办?当用户将错误日志发送到支持电子邮件地址时,这是一种常见的情况。

为此,您将使用MFMailComposeViewController。您可以指定上面提到的所有内容,添加附件,并准备好适当的电子邮件供用户按一下按钮发送。您唯一不能做的就是代表用户点击按钮。您不希望应用程序代表您发送电子邮件,苹果确保不会。

注意:下一部分需要已在其上设置了电子邮件帐户的设备。如果您尝试在未配置电子邮件帐户的情况下显示MFMailComposeViewController,它将崩溃。如果您在模拟器上设置一个,它仍然会报错。

ContentView.swift中,将此添加到您之前创建的Share按钮之前。

// Export Via Email
Button(action: { self.mailViewIsPresented = true }) {
  Image(systemName: "envelope")
}
.frame(width: 44, height: 44, alignment: .center)
.disabled(!MFMailComposeViewController.canSendMail())
.sheet(isPresented: $mailViewIsPresented) {
  MailView(
    messageBody: "This is a test email string",
    attachmentInfo: nil,
    result: self.$result)
}

这段代码创建了一个新按钮,点击该按钮将使用项目中已提供的MailView类创建MFMailComposeViewController

在您的设备上构建并运行该应用。 点击新的信封按钮,您将在正文中看到一封电子邮件草稿,其中包含文本“ This is a test email string”。 很酷吧?

1. Adding an Attachment

现在您已经准备好电子邮件表格,您可以添加最终成分:应用程序的数据。

MFMailComposeViewControlleraddAttachmentData(_:mimeType:fileName :)可以根据需要在电子邮件中添加尽可能多的附件。 您需要为其提供文件的数据,其MIME类型和所需的文件名。

ContentView.swift中,将上一步中的MailView(messageBody:attachmentInfo:result :)替换为:

MailView(
  messageBody: "This is a test email string",
  attachmentInfo: (
    fileURL: TaskStore.shared.tasksDocURL,
    mimeType: "application/xml"),
    result: self.$result)

构建并运行,然后点击信封按钮。 您会看到相同的电子邮件,但是这次有一个名为ExportData.rwtl的附件。 文件本身将显示您为该文件类型指定的图标。

2. Understanding the MIME Type

在上面的代码中,您为文件提供了MIME类型。 你问这是什么? 好吧,MIME代表Multipurpose Internet Mail Extensions。 可以将其视为对电子邮件中原始数据的描述。 收件人的电子邮件客户端解析此电子邮件时,它将使用该值来知道如何正确解析电子邮件的内容和附件。

在这种情况下,rwtl文件是具有不同扩展名的plist文件。 plist文件本质上是XML文件,解释了第二部分。 第一部分,应用程序application指示此类型仅可由应用程序而非用户读取。

现在,您可以从应用程序中导出任务列表,将其发送给您的朋友,然后让他们将其导入到他们自己的应用程序副本中!

本教程介绍了如何将应用程序数据导入数据源。 但是目前,导入过程将所有现有信息替换为新导入的数据。 这可能并不总是所需的功能。 您可能想将旧的与新的进行比较,并可能将它们合并。

如果是这样,请看一下这两篇文章,它们将向您展示如何预览文件甚至在不使用应用程序副本的情况下就地编辑它们: Document-Based Apps Tutorial: Getting Started和Apple’s Documentation。

如果您想了解有关UTI的更多信息,请查看Apple的以下文章:Uniform Type Identifiers Overview和Uniform Type Identifiers Reference。

后记

本篇主要讲述了使用Universal Type Identifiers进行App中的数据的导入和导出,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(基于Universal Type Identifiers的App数据导入和导出(一))