春天前销销售 - 保存一切。所有视频。所有书籍。 现在50%的折扣。

建立您的移动发展技能并保存!通过终极书籍和视频订阅,继续前进。作为春季前销销售的一部分,仅为149美元/年。

首页 iOS..& Swift Tutorials

使用SWIFTUI的基于文档的应用程序

SWIFTUI使其比以往任何时候都更容易创建与IOS文档交互系统一起使用的基于文档的应用程序。在本教程中,您将学习如何创建基于SWIFTUI文档的MEME-MAKER应用程序。

4.6/5 9评级

版本

  • Swift 5,iOS 14,Xcode 12

文档是计算的核心,基于SWIFTUI文档的应用程序根据将应用程序集成到文件应用程序的所有酷功能,使其与IOS文档交互系统一起使用。

在本教程中,您将努力工作 记忆库,一个应用程序,允许您创建自己的模因并持续为自己的MEME文档类型。

您将了解以下主题:

  • 什么是统一类型标识符(UTI)?
  • 哪些组件包含基于SWIFTUI文档的应用程序?
  • 如何使用唯一的扩展定义自己的文档?
  • 如何运行IOS / iPados和MacOS上基于SWIFTUI文档的应用程序?

没有进一步的ado,现在是时候潜入了。

入门

点击下载Starter项目 下载材料 按钮在教程的顶部或底部。

建立和运行。这是应用程序的样子:

Starter项目的屏幕截图:结束标签

点按 + 右上角的按钮以创建新文档。文本编辑器将打开“Hello,World!”。显示。将文本更改为 Swiftiui岩石! 通过点击敲击文档 返回键,它位于左上角。

笔记:在撰写本文时,有几个与基于SWIFTUI文档的应用程序有关的错误。有时,您将无法看到返回按钮或导航栏中添加项目按钮的按钮,因为默认情况下它们表示白色。

切换到 浏览 选项卡查找刚刚创建的文档。标签如下所示:

Starter项目的屏幕截图:浏览选项卡

通过点击它来打开新文件。文本编辑器打开,您可以阅读您输入的文本。

这是创建MEMES编辑器的一个很好的起点。您将修改此应用程序,以便它不适用于原始文本,它适用于MEME文档类型。这是Utis进来的地方。

定义导出类型标识符

唯一的类型标识符或UTI是 苹果的话语, a “unique identifier for a particular file type, data type, directory or bundle type.” For instance, a JPEG image is a particular file type, and it’s uniquely identified by the UTI string public.jpeg. Likewise, a text file written in the popular Markdown标记语言 is uniquely identified by the UTI net.daringfireball.markdown.

Utis的价值是多少?由于UTIS是唯一的标识符,因此它们为您的应用提供了一种明确的方式,以告诉操作系统它能够打开和创建的类型的文档。由于iOS不会为“MEME”文档提供内置支持,因此您将为MEME文件添加新的UTI到您的应用程序。这在Xcode中是简单的。

在潜入代码之前,您需要对项目设置进行一些更改。

选择 记忆库(ios) 目标在项目设置中,选择 信息 标签并扩展 导出类型标识符 section.

这是定义文档类型和元数据的地方。目前,这仍然是为文本文档设置的。

进行以下更改:

  • 改变 描述用记忆师创建的模因。你可以看到描述。在查找器的信息窗口中。
  • 改变 标识符com.raywenderlich.memaker.meme.。其他应用程序可以使用此标识符导入您的文档。
  • 改变 符合“public.data,public.content”。这些是UTI,他们描述了您UTI正在使用的数据类型。在编程贴盲中,您可以将这些视为您的UTI符合的协议。您可以使用多种类型,例如public.data或public.image。您将找到所有可用UTI的列表 苹果的文件 或者 维基百科.
  • 改变 扩大Meme.。这是用Mememaker创建的文档添加到您创建的文档中的.meme文件扩展名。

配置导出类型标识符

伟大的!现在您已准备好使用新的扩展创建文档, .meme。 :]

使用DocumentGroup.

DocumentGroup 是一个呈现系统UI的场景,用于处理文档。您可以看到它在上面的屏幕截图中的样子。 Swifui使其超级易于使用文档浏览器。所需要的只是遵循所找到的代码 记忆库app.swift.:

DocumentGroup(newDocument: MemeMakerDocument()) { file in
  ContentView(document: file.$document)
}

DocumentGroup has two initializers when handling documents: 在 it(newDocument:editor:)在 it(viewing:viewer:). The first one allows you to create new documents and edit existing documents, while the second one is only able to view files. Because you want to create and edit memes, the starter project uses the first initializer.

The initializer receives the document it should show. In this case, you’re initializing a new empty 记忆库Document, which you’ll work on later. The initializer also receives a closure that builds the file editing view.

使用文件文档

FileDocument is the base protocol for a document that an app can read and write to the device. This protocol contains two static properties: readableContentTypeswritableContentTypes. Both are UTType arrays defining the types the document can read and write, respectively. Only readableContentTypes is required, because writableContentTypes defaults to readableContentTypes 作为 well.

FileDocument also requires an initializer taking a FileDocumentReadConfiguration. This configuration bundles a document’s type in the form of UTType, along with a FileWrapper containing its content.

Finally, any class or struct conforming to FileDocument needs to implement fileWrapper(configuration:). It’s called when a document is written, and it takes a FileDocumentWriteConfiguration 作为 a parameter, which is similar to the read configuration, but used for writing.

这可能听起来像很多工作,但别担心。在本教程的本节中,您将研究如何使用这两个配置。

定义导出的uttypes.

打开 MememakerDocument.swift.. At the top of the file, you’ll find an extension on UTType that defines the type the starter project is using.

用以下代码替换此扩展:

extension UTType {
  static let memeDocument = UTType(
    exportedAs: "com.raywenderlich.memaker.meme.")
}

In the code above, you’re defining Meme.Document 作为 a new UTType so that you can use it in the next step.

还在 MememakerDocument.swift., find readableContentTypes. As mentioned before, this defines a list of UTTypes the app can read and write. Replace the property with this new code:

static var readableContentTypes: [UTType] { [.memeDocument] }

This sets the new type you created earlier as a type that 记忆库Document document can read. Since writableContentTypes defaults to readableContentTypes, you don’t need to add it.

创建数据模型

Before you can continue working on 记忆库Document, you need to define the meme it works with. Create a new Swift file called MEME.SWIFT. 在里面 共享 组,然后选择两个复选框 目标 所以它将包含在iOS和麦斯科斯目标中。

设置MEME.SWIFT的目标

添加以下代码:

struct Meme: Codable {
  var imageData: Data?
  var topText: String
  var bottomText: String
}

记忆库 will save a Meme 至 disk. It conforms to Codable, so you can convert it to Data 和 back using JSONEncoderJSONDecoder. It also wraps all the information needed to represent a Meme: two strings and an image’s data.

打开 MememakerDocument.swift. 再次在课堂开头找到此代码:

var text: String
  
init(text: String = "Hello, world!") {
  self.text = text
}

记忆库Document can now hold the actual Meme instead of text. So replace these lines with the following code:

// 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. This is the meme represented by an instance of 记忆库Document.
  2. You define an initializer for 记忆库Document. The initializer receives the data for an image and both the top and bottom text.
  3. Finally, you initialize a new Meme given these parameters.

此时,您将在代码中看到错误。别担心 - 在保存和加载文件时,您需要在编码和解码文档时需要进行一些其他更改。

编码和解码文档

First, make a change to fileWrapper(configuration:). Replace the method body with these lines:

let data = try JSONEncoder().encode(meme)
return .init(regularFileWithContents: data)

This converts the meme to data and creates a WriteConfiguration that the system uses to write this document to disk.

Next, replace the body of 在 it(configuration:) with the following code:

guard let data = configuration.file.regularFileContents else {
  throw CocoaError(.fileReadCorruptFile)
}
meme = try JSONDecoder().decode(Meme.self, from: data)

The app calls this initializer when an existing document is opened. You try to get the data from the given ReadConfiguration 和 convert it to an instance of Meme. If the process fails, the initializer will throw an error which the system deals with.

您现在已添加支持对您的应用程序读取和写入自定义MEME文档。但是,用户仍然无法看到任何一个,因为您没有显示MEME编辑器。您将解决下一节中的该问题。

提供自定义编辑器

Currently, the app uses a TextEditor. The template for SwiftUI document-based multi-platform apps starts with this view. It’s used to present editable and scrollable text.

用初始文本截图

TextEditor isn’t suitable for creating and editing memes, so you’ll create your own view to edit a 记忆库Document.

在开始创建新编辑器视图之前,您将删除旧的编辑器。打开 ContentView.swift. 和 replace body with an empty view:

Spacer()

这确保您在构建新编辑器时不会获得编译器错误。

创建图像层

编辑器将包含两个子视图。您将在创建实际编辑器之前创建这些。

The first one is ImageLayer, a view that’s representing the image. Create a new Swifui视图 文件in. 共享ImageLayer.swift. 并选择两个复选框 记忆库(ios)记忆库(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 has a SwiftUI binding to the meme image’s data. In a later step, MemeEditor will pass the data to this view.
  2. Its body consists of an NSUIImage, a view you initialize with the image data. You may wonder what this view is. It’s a typealias for UIImage on iOS and NSImage on macOS, together with an extension. It allows for one common type for images, which has the same methods and properties on both platforms. You can find it in the nsuiimage_ios.swift. 文件在 iOS. 小组和 nsuiimage_macos.swift. 在里面 苹果系统 团体。它使用正确的类型,具体取决于您是否运行 记忆库(ios) 或者 记忆库(MACOS).
  3. 最后,添加预览以支持Xcode的预览功能。

查看预览以确保您的视图显示图像:

SWIFTUI图像视图的预览

现在您正在显示图像,您可以继续前进到显示文本!

创建文本图层

TextLayer 是第二个子视图,它将图像上方的顶部和底部文本定位。再次创建一个新的 Swifui视图 文件in. 共享 并称之为 textlayer.swift.。记得检查 记忆库(ios)记忆库(MACOS) 作为 目标.

Replace the generated TextLayer struct with this:

struct TextLayer<ImageContent: View>: View {
  @Binding var meme: Meme
  let imageContent: () -> ImageContent
}

TextLayer has two properties: Meme., holding the Meme that’s shown; and imageContent. imageContent is a closure to create another view inside of TextLayer‘s body. Note that you declared the view as a generic struct where the the image content view can be anything that conforms to View.

Next, add the body 至 the view:

var body: some View {
  ZStack(alignment: .bottom) {
    ZStack(alignment: .top) {
      imageContent()
      MemeTextField(text: $meme.topText)
    }

    MemeTextField(text: $meme.bottomText)
  }
}

You use two ZStacks in body 至 place the top text at the top of the image and the bottom text at its bottom. To show the image, you call the closure passed to your TextLayer view. To show the text, you use MemeTextField, a normal TextField set up in your starter project to show formatted text.

最后,用以下内容替换预览:

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)
    }
  }
}

看看预览:

SWIFTUI自定义文档编辑器预览

Right now it’s not looking like much of a meme. Not to worry, in the next section, you’ll combine both the image and text layers to create MemeEditor.

创建MEME编辑器

All the files you created before are independent of the platform. But MemeEditor will use different platform-specific methods to import images based on whether the app runs on iOS/iPadOS or macOS.

In a later step, you’ll create another MemeEditor 至 show on macOS, but for now, start with the iOS and iPadOS version. Create a new Swifui视图 文件, MemeDERTER_IOS.SWIFT.。这次它不应该在共享小组中,但在 iOS.。记得只检查 记忆库(ios) target.

使用以下代码替换文件中的视图:

struct MemeEditor: View {
  @Binding var meme: Meme
  @State var showingImagePicker = false
  @State private var inputImage: NSUIImage?
}

MemeEditor has a binding to the meme it presents together with two properties. You’ll use showingImagePicker 至 decide when to present the image picker that lets your user select an image. You will then store the image in 在 putImage.

接下来,将新方法添加到结构中以存储输入图像:

func loadImage() {
  guard let inputImage = inputImage else { return }
  meme.imageData = inputImage.data
}

Now you can add the body inside the view:

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)
  }
}

Here’s what’s going on in the body:

  1. First, create a new TextLayer 和 pass both a binding to Meme. 和 a closure to create the ImageLayer.
  2. In this closure, define a button that sets showingImagePickertrue when tapped. Use the ImageLayer defined above as its label or show a button if the meme doesn’t yet contain an image.
  3. Use sheet 至 show a UIImagePicker whenever showingImagePicker is set to true. UIImagePicker is a wrapper around UIImagePickerController 至 make it usable with SwiftUI. It allows users to select an image from their device, and it calls loadImage whenever the picker is dismissed.

接下来,用以下内容替换文件中的预览:

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)
  }
}

您的预览现在应该显示您的观点的测试:

SWIFTUI自定义文档编辑器预览

最后,开放 ContentView.swift.. Replace the contents of body with the following code, which is a dedicated meme editor as opposed to a text editor:

MemeEditor(meme: $document.meme)

Here you replaced TextEditor with the new MemeEditor. You pass the document’s meme to MemeEditor, letting user manipulate and work on a meme.

最后,毕竟这个编码后,记忆库准备在iPhone上运行!选择 记忆库(ios) 计划和建立和运行。创建一个新文档,看起来像这样:

使用Mememaker创建新文档

现在,您可以选择一个有趣的图像,添加一些文字并提高您的模因技巧。尝试创建一个像这样的有趣的模因:

骨骼等待应用评论

良好的工作! :]

在麦斯斯上使用应用程序

A big advantage of SwiftUI is that you can use it on all Apple platforms. But although you used NSUIImage, there are still some changes you need to make before you can run MemeMaker on macOS.

为宏实施MemeDeeditor

Because MemeEditor uses UIImagePickerController, you can’t use it on macOS. Instead, you’ll create another version of MemeEditor that’s used when running the app on macOS. It’ll use NSOpenPanel 至 let the user select an image as the background of the meme.

But thanks to SwiftUI, most of the views can stay the same. You can reuse both ImageLayerTextLayer. The only difference is how the user selects an image.

创建一个新的 Swifui视图 文件在 苹果系统 小组并打电话给它 MemeDitor_Macos.swift.。只检查一下 记忆库(MACOS) 目标。用以下代码替换此文件的内容:

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
    }
  }
}

Here, you create a similar view to the one you created earlier for iOS. This time, though, you add a separate button to call selectImage. selectImage uses NSOpenPanel 至 let your user pick an image. If the selection succeeds, you store the new image data in the meme.

最后,将预览添加到文件底部:

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或更高版本。)这是应用程序的样子:

Mememaker在摩托斯

您可以在MacOS上创建相同的模因:

骨骼等待应用评论

如果没有任何额外的工作,Mac应用程序已经有一个带有快捷方式的工作菜单。例如,您可以使用 Command-N. 创建一个新文件和 命令S. 要保存文档,或者您可以撤消您的上次更改 命令Z..

一个菜单已设置。

创建使用文档并在iOS和麦斯座上运行的应用程序是令人惊讶的吗? :]

然后去哪儿?

您可以通过单击下载已完成的项目文件 下载材料 本教程顶部或底部的按钮。

文档是许多优秀应用程序的中央部分。现在与Swifui一起,为iOS,iPados和MacOS构建基于文档的应用程序甚至更容易。

如果您想深入了解基于Swifui文档的应用程序,请参阅Apple 在Swifui构建基于文档的应用程序 video.

有关SWIFTUI的更多信息,请查看 SWIFTUI:入门 教程或者 Swifui由教程 book.

要创建基于文档的UIKIT应用程序,您将找到更多信息 基于文档的应用程序教程:入门 article.

我们希望您享受本教程。如果您有任何疑问或意见,请加入下面的论坛讨论!

平均评级

4.6/5

为此内容添加评级

9 ratings

更像这样的

贡献者

注释