版本记录
版本号 | 时间 |
---|---|
V1.0 | 2021.01.07 星期四 |
前言
今天翻阅苹果的API文档,发现多了一个框架SwiftUI,这里我们就一起来看一下这个框架。感兴趣的看下面几篇文章。
1. SwiftUI框架详细解析 (一) —— 基本概览(一)
2. SwiftUI框架详细解析 (二) —— 基于SwiftUI的闪屏页的创建(一)
3. SwiftUI框架详细解析 (三) —— 基于SwiftUI的闪屏页的创建(二)
4. SwiftUI框架详细解析 (四) —— 使用SwiftUI进行苹果登录(一)
5. SwiftUI框架详细解析 (五) —— 使用SwiftUI进行苹果登录(二)
6. SwiftUI框架详细解析 (六) —— 基于SwiftUI的导航的实现(一)
7. SwiftUI框架详细解析 (七) —— 基于SwiftUI的导航的实现(二)
8. SwiftUI框架详细解析 (八) —— 基于SwiftUI的动画的实现(一)
9. SwiftUI框架详细解析 (九) —— 基于SwiftUI的动画的实现(二)
10. SwiftUI框架详细解析 (十) —— 基于SwiftUI构建各种自定义图表(一)
11. SwiftUI框架详细解析 (十一) —— 基于SwiftUI构建各种自定义图表(二)
12. SwiftUI框架详细解析 (十二) —— 基于SwiftUI创建Mind-Map UI(一)
13. SwiftUI框架详细解析 (十三) —— 基于SwiftUI创建Mind-Map UI(二)
14. SwiftUI框架详细解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
15. SwiftUI框架详细解析 (十五) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(二)
16. SwiftUI框架详细解析 (十六) —— 基于SwiftUI简单App的Dependency Injection应用(一)
17. SwiftUI框架详细解析 (十七) —— 基于SwiftUI简单App的Dependency Injection应用(二)
18. SwiftUI框架详细解析 (十八) —— Firebase Remote Config教程(一)
19. SwiftUI框架详细解析 (十九) —— Firebase Remote Config教程(二)
开始
首先看下主要内容:
SwiftUI使创建与iOS文档交互系统一起使用的基于文档的应用程序比以往更加容易。 在本教程中,您将学习如何创建基于SwiftUI文档的
meme-maker
应用。内容来自翻译。
接着看下写作环境:
Swift 5, iOS 14, Xcode 12
下面就是正文啦
Documents
是计算的中心,基于SwiftUI
文档的应用程序比以往更容易使用iOS文档交互系统。iOS文档交互系统基于将您的应用程序与Files
应用程序的所有出色功能集成在一起,
在本教程中,您将使用MemeMaker,该应用程序可让您创建自己的memes
并将其保留为自己的meme
文档类型。
您将了解以下主题:
- 什么是
Uniform Type Identifiers (UTI)
? - 哪些组件包含
SwiftUI
基于文档的应用程序? - 您如何定义自己的具有唯一扩展名的文档?
- 如何在
iOS / iPadOS
和macOS
上运行基于SwiftUI
文档的应用程序?
事不宜迟,该一起看下了。
打开已有的初始项目。
构建并运行。 这是该应用程序的外观:
点击右上角的+
按钮以创建一个新文档。 文本编辑器将打开“ Hello,world!”
。 如图所示。 将文本更改为SwiftUI rocks!
然后点击左上角的back button
按钮关闭文档。
注意:在撰写本文时,存在一些与
SwiftUI
基于文档的应用程序有关的bug
。 有时您会在导航栏中看不到后退按钮或添加项目按钮之类的按钮,因为它们默认为白色。
切换到Browse
选项卡以查找刚刚创建的文档。 该选项卡如下所示:
通过点击打开新文件。文本编辑器打开,您可以阅读输入的文本。
这是创建memes
编辑器的良好起点。您将修改此应用程序,以便它与memes
文档类型一起使用,而不是处理原始文本。这是UTI
的作用。
Defining Exported Type Identifiers
用Apple的话Apple’s words来说,Unique Type Identifier or UTI
是“特定文件类型,数据类型,目录或bundle type
的唯一标识符”。例如,JPEG
图像是一种特殊的文件类型,由UTI
字符串public.jpeg
唯一标识。同样,UTI net.daringfireball.markdown
可以唯一标识以流行的Markdown markup language编写的文本文件。
UTI
的值是什么?由于UTI
是唯一的标识符,因此它们为您的应用程序提供了一种明确的方式来告知操作系统它可以打开和创建哪种类型的文档。由于iOS并未附带对“meme”
文档的内置支持,因此您需要在应用中为Meme
文件添加新的UTI
。这在Xcode中很简单。
在深入研究代码之前,您需要对项目设置进行一些更改。
在项目设置中选择MemeMaker(iOS)target
,选择Info
选项卡,然后展开Exported Type Identifiers
部分。
在这里定义文档的类型和元数据。当前,这仍然是为文本文档设置的。
进行以下更改:
- 将
Description
更改为A meme created with MemeMaker
。您可以查看说明,例如在Finder
的信息窗口中。 - 将
Identifier
更改为com.raywenderlich.MemeMaker.meme
。其他应用程序可以使用此标识符来导入您的文档。 - 将
Conforms
更改为“public.data,public.content”
。这些是UTI
,它们描述了您的UTI
使用的数据类型。用编程的话来说,您可以将它们视为您的UTI
遵循的协议。您可以使用许多类型,例如public.data
或public.image
。您可以在Apple’s documentation或Wikipedia中找到所有可用的UTI
的列表。 - 将
Extension
更改为meme
。这是.meme
文件扩展名,改扩展名已添加到使用MemeMaker
创建的文档中。
很好! 现在,您可以使用新扩展名.meme
创建文档了。
Using a DocumentGroup
DocumentGroup
是展示用于处理文档的系统UI的场景。 您可以在上面的屏幕截图中看到它的外观。 SwiftUI
使使用文档浏览器变得超级容易。 只需遵循MemeMakerApp.swift
中的代码即可:
DocumentGroup(newDocument: MemeMakerDocument()) { file in
ContentView(document: file.$document)
}
DocumentGroup
在处理文档时有两个初始化程序:init(newDocument:editor :)
和init(viewing:viewer :)
。 第一个允许您创建新文档和编辑现有文档,而第二个则只能查看文件。 因为您要创建和编辑memes
,所以入门项目将使用第一个初始化程序。
初始化程序收到它应该显示的文档。 在这种情况下,您将初始化一个新的空MemeMakerDocument
,将在以后使用。 初始化程序还会收到一个用于构建文件编辑视图的闭包。
Working With a File Document
FileDocument
是应用可以读取和写入设备的文档的基本协议。该协议包含两个静态属性:readContentTypes
和writableContentTypes
。两者都是UTType
数组,分别定义了文档可以读取和写入的类型。仅要求readyContentTypes
,因为writableContentTypes
也默认为readContentContentTypes
。
FileDocument
还需要带有FileDocumentReadConfiguration
的初始化程序。此配置将UTType
形式的文档类型与包含其内容的FileWrapper
捆绑在一起。
最后,任何符合FileDocument
的类或结构体都需要实现fileWrapper(configuration :)
。写入文档时会调用它,它以FileDocumentWriteConfiguration
作为参数,类似于读取配置,但用于写入。
听起来可能需要做很多工作,但请放心。在本教程的这一部分中,您将了解如何使用这两种配置。
1. Defining Exported UTTypes
打开MemeMakerDocument.swift
。在文件顶部,您会在UTType
上找到扩展名,该扩展名定义了入门项目正在使用的类型。
用以下代码替换此扩展名:
extension UTType {
static let memeDocument = UTType(
exportedAs: "com.raywenderlich.MemeMaker.meme")
}
在上面的代码中,您将memeDocument
定义为新的UTType
,以便可以在下一步中使用它。
仍在MemeMakerDocument.swift
中,找到readableContentTypes
。 如前所述,这定义了应用可以读取和写入的UTType
列表。 用以下新代码替换属性:
static var readableContentTypes: [UTType] { [.memeDocument] }
这会将您之前创建的新类型设置为MemeMakerDocument
文档可以读取的类型。 由于writableContentTypes
默认为readContentContentTypes
,因此您无需添加它。
2. Creating the Data Model
在继续使用MemeMakerDocument
之前,您需要定义其适用的meme
。 在Shared
组中创建一个名为Meme.swift
的新Swift文件,并选中Targets
中的两个复选框,以便将其同时包含在iOS
和macOS target
中。
添加以下代码:
struct Meme: Codable {
var imageData: Data?
var topText: String
var bottomText: String
}
MemeMaker
会将Meme
保存到磁盘。 它符合Codable
,因此您可以使用JSONEncoder
和JSONDecoder
将其转换为Data
并返回。 它还包装了表示一个Meme
所需的所有信息:两个字符串和一个图像数据。
再次打开MemeMakerDocument.swift
并在类的开头找到以下代码:
var text: String
init(text: String = "Hello, world!") {
self.text = text
}
MemeMakerDocument
现在可以保存实际的Meme
而不是文本。 因此,将这些行替换为以下代码:
// 1
var meme: Meme
// 2
init(
imageData: Data? = nil,
topText: String = "Top Text",
bottomText: String = "Bottom Text"
) {
// 3
meme = Meme(
imageData: imageData,
topText: topText,
bottomText: bottomText)
}
上面的代码就是这样:
- 1) 这是由
MemeMakerDocument
实例表示的meme
。 - 2) 您为
MemeMakerDocument
定义了一个初始化程序。 初始化程序接收图像的数据以及顶部和底部的文本。 - 3) 最后,根据这些参数初始化一个新的
Meme
。
此时,您将看到代码中的错误。 不用担心-在保存和加载文件时,还需要进行一些其他更改以对文档进行编码和解码。
3. Encoding and Decoding the Document
首先,对fileWrapper(configuration :)
进行更改。 用以下几行替换方法主体:
let data = try JSONEncoder().encode(meme)
return .init(regularFileWithContents: data)
这会将meme
转换为数据,并创建一个WriteConfiguration
,系统将其用于将该文档写入磁盘。
接下来,将init(configuration :)
的主体替换为以下代码:
guard let data = configuration.file.regularFileContents else {
throw CocoaError(.fileReadCorruptFile)
}
meme = try JSONDecoder().decode(Meme.self, from: data)
打开现有文档时,应用程序将调用此初始化程序。 您尝试从给定的ReadConfiguration
中获取数据,并将其转换为Meme
的实例。 如果该过程失败,则初始化程序将引发系统要处理的错误。
现在,您已经添加了对自定义meme
文档进行读写的支持。 但是,由于您没有显示Meme
编辑器,因此用户仍然看不到其中任何一个。 您将在下一部分中解决该问题。
Providing a Custom Editor
目前,该应用程序使用TextEditor
。 基于SwiftUI
文档的多平台应用程序的模板从此视图开始。 用于显示可编辑和可滚动的文本。
TextEditor
不适合创建和编辑模因,因此您将创建自己的视图来编辑MemeMakerDocument
。
在开始创建新的编辑器视图之前,您将删除旧的视图。 打开ContentView.swift
并将主体替换为空视图:
Spacer()
这样可以确保在构建新编辑器时不会出现编译器错误。
1. Creating the Image Layer
编辑器将包含两个子视图。 您将在创建实际的编辑器之前创建它们。
第一个是ImageLayer
,它代表图像。 在Shared
中创建一个名为ImageLayer.swift
的新SwiftUI View
文件,并在Targets
中选中MemeMaker(iOS)
和MemeMaker(macOS)
的复选框。 用以下内容替换文件中的两个结构:
struct ImageLayer: View {
// 1
@Binding var imageData: Data?
// 2
var body: some View {
NSUIImage.image(fromData: imageData ?? Data())
.resizable()
.aspectRatio(contentMode: .fit)
}
}
// 3
struct ImageLayer_Previews: PreviewProvider {
static let imageData = NSUIImage(named: "AppIcon")!.data
static var previews: some View {
ImageLayer(imageData: .constant(imageData))
.previewLayout(.fixed(width: 100, height: 100))
}
}
上面的代码正在执行以下操作:
- 1)
ImageLayer
具有SwiftUI
绑定到meme
图像的数据。 在后续步骤中,MemeEditor
将数据传递到此视图。 - 2) 它的
body
由NSUIImage
组成,NSUIImage
是您使用图像数据初始化的视图。 您可能想知道这种view
是什么。 这是iOS上的UIImage
和macOS
上的NSImage
的类型别名,以及扩展名。 它允许使用一种常见的图像类型,在两种平台上具有相同的方法和属性。 您可以在iOS组的NSUIImage_iOS.swift
文件和macOS组的NSUIImage_macOS.swift
中找到它。 根据您运行的是MemeMaker(iOS)
还是MemeMaker(macOS)
,它使用正确的类型。 - 3) 最后,添加预览以支持Xcode的预览功能。
查看预览以确保您的视图正在显示图像:
现在您正在显示图像,您可以继续显示文本!
2. Creating the Text Layer
TextLayer
是第二个子视图,它将顶部和底部文本放置在图像上方。 同样,在Shared
中创建一个新的SwiftUI View
文件,并将其命名为TextLayer.swift
。 切记将MemeMaker(iOS)
和MemeMaker(macOS)
选中为Targets
。
将此替换为生成的TextLayer
结构体:
struct TextLayer: View {
@Binding var meme: Meme
let imageContent: () -> ImageContent
}
TextLayer
具有两个属性:meme
,保存显示的Meme
; 和imageContent
。imageContent
是一个闭包,用于在TextLayer
的body
中创建另一个视图。 请注意,您已将视图声明为通用结构,其中图像内容视图可以是遵循View
的任何内容。
接下来,将body
添加到视图中:
var body: some View {
ZStack(alignment: .bottom) {
ZStack(alignment: .top) {
imageContent()
MemeTextField(text: $meme.topText)
}
MemeTextField(text: $meme.bottomText)
}
}
您可以在body
中使用两个ZStack
,以将顶部文本放置在图像顶部,将底部文本放置在图像底部。 为了显示图像,您调用传递给TextLayer
视图的闭包。 要显示文本,请使用MemeTextField
,这是在启动程序项目中设置的常规TextField
,用于显示格式化的文本。
最后,用以下内容替换预览:
struct TextLayer_Previews: PreviewProvider {
@State static var meme = Meme(
imageData: nil,
topText: "Top Text Test",
bottomText: "Bottom Text Test"
)
static var previews: some View {
TextLayer(meme: $meme) {
Text("IMAGE")
.frame(height: 100)
}
}
}
看一下预览:
目前,它看起来不算meme
。 不用担心,在下一节中,您将结合图像和文本图层来创建MemeEditor
。
3. Creating a Meme Editor
您之前创建的所有文件均独立于平台。 但是MemeEditor
将根据应用程序运行在iOS / iPadOS
还是macOS
上,使用特定于平台的不同方法来导入图像。
在后续步骤中,您将创建另一个MemeEditor
,以在macOS
上显示,但现在,从iOS和iPadOS版本开始。 创建一个新的SwiftUI
视图文件MemeEditor_iOS.swift
。 这次,它不应该在Shared
组中,而应该在iOS中。 请记住仅检查MemeMaker(iOS)target
。
用以下代码替换文件中的视图:
struct MemeEditor: View {
@Binding var meme: Meme
@State var showingImagePicker = false
@State private var inputImage: NSUIImage?
}
MemeEditor
具有与其呈现的meme
的绑定以及两个属性。 您将使用ShowingImagePicker
来决定何时显示可以让用户选择图像的图像选择器。 然后,您将图像存储在inputImage
中。
接下来,向该结构体添加一个新方法来存储输入图像:
func loadImage() {
guard let inputImage = inputImage else { return }
meme.imageData = inputImage.data
}
现在,您可以在视图内部添加body
:
var body: some View {
// 1
TextLayer(meme: $meme) {
// 2
Button {
showingImagePicker = true
} label: {
if meme.imageData != nil {
ImageLayer(imageData: $meme.imageData)
} else {
Text("Add Image")
.foregroundColor(.white)
.padding()
.background(Color("rw-green"))
.cornerRadius(30)
.padding(.vertical, 50)
}
}
}
// 3
.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
UIImagePicker(image: $inputImage)
}
}
这是body
发生的事情:
- 1) 首先,创建一个新的
TextLayer
并传递对meme
的绑定和一个闭包以创建ImageLayer
。 - 2) 在此闭包中,定义一个按钮,将其在点击时将
showingImagePicker
设置为true
。 使用上面定义的ImageLayer
作为label
,或者如果该meme
尚不包含图像,则显示一个按钮。 - 3) 每当
showingImagePicker
设置为true
时,使用sheet
显示UIImagePicker
。UIImagePicker
是UIImagePickerController
的包装,以使其可用于SwiftUI
。 它允许用户从其设备中选择图像,并且每当关闭picker
时都会调用loadImage
。
接下来,将文件中的预览替换为以下内容:
struct MemeEditor_Previews: PreviewProvider {
@State static var meme = Meme(
imageData: nil,
topText: "Top Text Test",
bottomText: "Bottom Text Test"
)
static var previews: some View {
MemeEditor(meme: $meme)
}
}
现在,您的预览应该显示对视图的测试:
最后,打开ContentView.swift
。 用以下代码替换body
的内容,该代码是专用的meme
编辑器,而不是文本编辑器:
MemeEditor(meme: $document.meme)
在这里,您用新的MemeEditor
替换了TextEditor
。 您将文档的meme
传递给MemeEditor
,从而使用户能够操作和处理meme
。
最后,完成所有这些编码之后,MemeMaker
准备在iPhone上运行! 选择MemeMaker(iOS)scheme
并构建并运行。 创建一个新文档,如下所示:
现在,您可以选择一个有趣的图像,添加一些文字并提高您的meme
制作技巧。 尝试创建一个像这样的有趣的meme
:
干得好!
Using the App on macOS
SwiftUI
的一大优势是您可以在所有Apple平台上使用它。 但是,尽管您使用了NSUIImage
,但仍需要进行一些更改才能在macOS
上运行MemeMaker
。
1. Implementing a MemeEditor for macOS
由于MemeEditor
使用UIImagePickerController
,因此您无法在macOS上使用它。 相反,您将创建另一个版本的MemeEditor
,该版本将在macOS上运行该应用程序时使用。 它将使用NSOpenPanel
让用户选择图片作为meme
的背景。
但是多亏了SwiftUI
,大多数视图可以保持不变。 您可以重用ImageLayer
和TextLayer
。 唯一的区别是用户如何选择图像。
在macOS组中创建一个新的SwiftUI View
文件,并将其命名为MemeEditor_macOS.swift
。 仅检查MemeMaker(macOS)target
。 用以下代码替换该文件的内容:
import SwiftUI
struct MemeEditor: View {
@Binding var meme: Meme
var body: some View {
VStack {
if meme.imageData != nil {
TextLayer(meme: $meme) {
ImageLayer(imageData: $meme.imageData)
}
}
Button(action: selectImage) {
Text("Add Image")
}
.padding()
}
.frame(minWidth: 500, minHeight: 500)
}
func selectImage() {
NSOpenPanel.openImage { result in
guard case let .success(image) = result else { return }
meme.imageData = image.data
}
}
}
在这里,您可以创建与之前为iOS创建的视图类似的视图。 不过,这一次,您添加了一个单独的按钮来调用selectImage
。 selectImage
使用NSOpenPanel
让您的用户选择图像。 如果选择成功,则将新的图像数据存储在模因中。
最后,在文件底部添加一个预览:
struct MemeEditor_Previews: PreviewProvider {
@State static var meme = Meme(
imageData: nil,
topText: "Top Text",
bottomText: "Bottom Text"
)
static var previews: some View {
MemeEditor(meme: $meme)
}
}
构建并运行。 (您需要使用macOS 11.0
或更高版本。)应用如下所示:
您可以在macOS
上创建相同的meme
:
Mac
应用程序无需任何额外的工作,就可以使用带有快捷方式的工作菜单。 例如,您可以使用Command-N
创建一个新文档,并使用Command-S
保存该文档,或者您可以使用Command-Z
撤消上一次更改。
创建使用文档并同时在iOS和macOS上运行的应用有多么容易,这是否令人惊讶?
文档是许多优秀应用程序的核心部分。 现在,借助SwiftUI
,为iOS,iPadOS和macOS构建基于文档的应用程序变得更加容易。
如果您想更深入地研究基于SwiftUI
文档的应用程序,请参阅 Build document-based apps in SwiftUI。
有关SwiftUI的更多信息,请查看SwiftUI: Getting Started或SwiftUI by Tutorials一书。
要创建基于文档的UIKit
应用,请在Document-Based Apps Tutorial: Getting Started中找到更多信息。
后记
本篇主要讲述了基于
SwiftUI
的Document-Based App
的创建,感兴趣的给个赞或者关注~~~