基于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
的值。 为此,请将以下行添加到TaskStore
的init
顶部:
print(tasksDocURL)
运行应用程序。 然后打开Finder
,键入Shift-Command-G
,然后从控制台复制路径,而不使用起始file://
协议。
2. Understanding the Uniform Type Identifier
您使用的是上方的自定义扩展名(“ rwtl”)
。 您可以输入任何名称作为扩展名,它都会起作用。 那么系统如何知道用于每个扩展的应用程序?
以JPG
图片为例。 jpeg
和jpg
都是有效的扩展名。 为每个扩展名注册具有相同信息的相同文件没有意义。 但是系统如何知道它们是同一类型?
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
部分中,添加一个新条目并按以下方式对其进行配置:
-
Name:
TaskList
数据 -
Types:
com.raywenderlich.TaskList.TaskListData
-
Icon:单击+按钮,然后选择唯一可用的图像,其文件名为
icon-doc.png
-
Additional document type properties:添加两个条目
-
CFBundleTypeRole,类型为
String
和值Editor
-
带有
String
和值Owner
的LSHandlerRank**
-
CFBundleTypeRole,类型为
这将创建一个名为TaskList Data
的新UTI
,并带有一个关联的唯一标识符和一个图标。使用其他属性,您可以指定TaskList
是TaskList
数据文件的编辑者和所有者,以便iOS
知道对其进行最大程度的控制。
Defining Import Types
到目前为止,您已定义了新的UTI
,但未输入有关新扩展名的任何信息。接下来,您将要执行此操作。
您将在导出之前先从导入步骤开始,尽管您在此处所做的许多事情也将支持导出过程。
仍在Info
选项卡上时,在Document Types
下面查找Imported UTIs
部分。在本节中添加具有以下信息的新项目。
-
Description
:TaskList
数据 -
Identifier
:com.raywenderlich.TaskList.TaskListData
-
Conforms To
:public.data
-
Icon
:单击+按钮,然后选择唯一可用的图像,其文件名为icon-doc.png
-
Additional imported UTI properties
:添加一个类型为Dictionary
的条目UTTypeTagSpecification
。在其内部,添加键类型为Array
的public.filename-extension
键的一项,并在其内部添加两个值rwtl
和RWTL
。
注意:要在适当的列表编辑器中的字典或数组内添加条目,必须单击键名旁边的
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<UIOpenURLContext>
) {
// 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
现在,您已经有了TaskList
的import
逻辑,接下来可以进行下一步:设置导出逻辑。
1. Setting Up the Export Logic
您为导入过程所做的设置使导出设置更加容易。导出信息已经在您保存到文档目录中的文件中。通过创建的rwtl
文件类型,您无需进行任何转换即可使用该文件。
但是请稍等...在Info
选项卡下,Imported UTIs
和Exported 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
现在您已经准备好电子邮件表格,您可以添加最终成分:应用程序的数据。
MFMailComposeViewController
的addAttachmentData(_: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中的数据的导入和导出,感兴趣的给个赞或者关注~~~