SwiftUI框架詳細(xì)解析 (二十) —— 基于SwiftUI的Document-Based App的創(chuàng)建(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2021.01.07 星期四

前言

今天翻閱蘋果的API文檔量蕊,發(fā)現(xiàn)多了一個(gè)框架SwiftUI,這里我們就一起來看一下這個(gè)框架哨查。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁(yè)的創(chuàng)建(一)
3. SwiftUI框架詳細(xì)解析 (三) —— 基于SwiftUI的閃屏頁(yè)的創(chuàng)建(二)
4. SwiftUI框架詳細(xì)解析 (四) —— 使用SwiftUI進(jìn)行蘋果登錄(一)
5. SwiftUI框架詳細(xì)解析 (五) —— 使用SwiftUI進(jìn)行蘋果登錄(二)
6. SwiftUI框架詳細(xì)解析 (六) —— 基于SwiftUI的導(dǎo)航的實(shí)現(xiàn)(一)
7. SwiftUI框架詳細(xì)解析 (七) —— 基于SwiftUI的導(dǎo)航的實(shí)現(xiàn)(二)
8. SwiftUI框架詳細(xì)解析 (八) —— 基于SwiftUI的動(dòng)畫的實(shí)現(xiàn)(一)
9. SwiftUI框架詳細(xì)解析 (九) —— 基于SwiftUI的動(dòng)畫的實(shí)現(xiàn)(二)
10. SwiftUI框架詳細(xì)解析 (十) —— 基于SwiftUI構(gòu)建各種自定義圖表(一)
11. SwiftUI框架詳細(xì)解析 (十一) —— 基于SwiftUI構(gòu)建各種自定義圖表(二)
12. SwiftUI框架詳細(xì)解析 (十二) —— 基于SwiftUI創(chuàng)建Mind-Map UI(一)
13. SwiftUI框架詳細(xì)解析 (十三) —— 基于SwiftUI創(chuàng)建Mind-Map UI(二)
14. SwiftUI框架詳細(xì)解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
15. SwiftUI框架詳細(xì)解析 (十五) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(二)
16. SwiftUI框架詳細(xì)解析 (十六) —— 基于SwiftUI簡(jiǎn)單App的Dependency Injection應(yīng)用(一)
17. SwiftUI框架詳細(xì)解析 (十七) —— 基于SwiftUI簡(jiǎn)單App的Dependency Injection應(yīng)用(二)
18. SwiftUI框架詳細(xì)解析 (十八) —— Firebase Remote Config教程(一)
19. SwiftUI框架詳細(xì)解析 (十九) —— Firebase Remote Config教程(二)

開始

首先看下主要內(nèi)容:

SwiftUI使創(chuàng)建與iOS文檔交互系統(tǒng)一起使用的基于文檔的應(yīng)用程序比以往更加容易。 在本教程中麦牺,您將學(xué)習(xí)如何創(chuàng)建基于SwiftUI文檔的meme-maker應(yīng)用。內(nèi)容來自翻譯鞭缭。

接著看下寫作環(huán)境:

Swift 5, iOS 14, Xcode 12

下面就是正文啦

Documents是計(jì)算的中心剖膳,基于SwiftUI文檔的應(yīng)用程序比以往更容易使用iOS文檔交互系統(tǒng)。iOS文檔交互系統(tǒng)基于將您的應(yīng)用程序與Files應(yīng)用程序的所有出色功能集成在一起岭辣,

在本教程中吱晒,您將使用MemeMaker,該應(yīng)用程序可讓您創(chuàng)建自己的memes并將其保留為自己的meme文檔類型易结。

您將了解以下主題:

  • 什么是Uniform Type Identifiers (UTI)枕荞?
  • 哪些組件包含SwiftUI基于文檔的應(yīng)用程序柜候?
  • 您如何定義自己的具有唯一擴(kuò)展名的文檔?
  • 如何在iOS / iPadOSmacOS上運(yùn)行基于SwiftUI文檔的應(yīng)用程序躏精?

事不宜遲渣刷,該一起看下了。

打開已有的初始項(xiàng)目矗烛。

構(gòu)建并運(yùn)行辅柴。 這是該應(yīng)用程序的外觀:

點(diǎn)擊右上角的+按鈕以創(chuàng)建一個(gè)新文檔。 文本編輯器將打開“ Hello瞭吃,world碌嘀!”。 如圖所示歪架。 將文本更改為SwiftUI rocks! 然后點(diǎn)擊左上角的back button按鈕關(guān)閉文檔股冗。

注意:在撰寫本文時(shí),存在一些與SwiftUI基于文檔的應(yīng)用程序有關(guān)的bug和蚪。 有時(shí)您會(huì)在導(dǎo)航欄中看不到后退按鈕或添加項(xiàng)目按鈕之類的按鈕止状,因?yàn)樗鼈兡J(rèn)為白色。

切換到Browse選項(xiàng)卡以查找剛剛創(chuàng)建的文檔攒霹。 該選項(xiàng)卡如下所示:

通過點(diǎn)擊打開新文件怯疤。文本編輯器打開,您可以閱讀輸入的文本催束。

這是創(chuàng)建memes編輯器的良好起點(diǎn)集峦。您將修改此應(yīng)用程序,以便它與memes文檔類型一起使用抠刺,而不是處理原始文本塔淤。這是UTI的作用。


Defining Exported Type Identifiers

用Apple的話Apple’s words來說矫付,Unique Type Identifier or UTI是“特定文件類型凯沪,數(shù)據(jù)類型,目錄或bundle type的唯一標(biāo)識(shí)符”买优。例如妨马,JPEG圖像是一種特殊的文件類型,由UTI字符串public.jpeg唯一標(biāo)識(shí)杀赢。同樣烘跺,UTI net.daringfireball.markdown可以唯一標(biāo)識(shí)以流行的Markdown markup language編寫的文本文件。

UTI的值是什么脂崔?由于UTI是唯一的標(biāo)識(shí)符滤淳,因此它們?yōu)槟膽?yīng)用程序提供了一種明確的方式來告知操作系統(tǒng)它可以打開和創(chuàng)建哪種類型的文檔。由于iOS并未附帶對(duì)“meme”文檔的內(nèi)置支持砌左,因此您需要在應(yīng)用中為Meme文件添加新的UTI脖咐。這在Xcode中很簡(jiǎn)單铺敌。

在深入研究代碼之前,您需要對(duì)項(xiàng)目設(shè)置進(jìn)行一些更改屁擅。

在項(xiàng)目設(shè)置中選擇MemeMaker(iOS)target偿凭,選擇Info選項(xiàng)卡,然后展開Exported Type Identifiers部分派歌。

在這里定義文檔的類型和元數(shù)據(jù)弯囊。當(dāng)前,這仍然是為文本文檔設(shè)置的胶果。

進(jìn)行以下更改:

  • Description更改為A meme created with MemeMaker匾嘱。您可以查看說明,例如在Finder的信息窗口中早抠。
  • Identifier更改為com.raywenderlich.MemeMaker.meme霎烙。其他應(yīng)用程序可以使用此標(biāo)識(shí)符來導(dǎo)入您的文檔。
  • Conforms更改為“public.data贝或,public.content”吼过。這些是UTI,它們描述了您的UTI使用的數(shù)據(jù)類型咪奖。用編程的話來說,您可以將它們視為您的UTI遵循的協(xié)議酱床。您可以使用許多類型羊赵,例如public.datapublic.image。您可以在Apple’s documentationWikipedia中找到所有可用的UTI的列表扇谣。
  • Extension更改為meme昧捷。這是.meme文件擴(kuò)展名,改擴(kuò)展名已添加到使用MemeMaker創(chuàng)建的文檔中罐寨。

很好靡挥! 現(xiàn)在,您可以使用新擴(kuò)展名.meme創(chuàng)建文檔了鸯绿。


Using a DocumentGroup

DocumentGroup是展示用于處理文檔的系統(tǒng)UI的場(chǎng)景跋破。 您可以在上面的屏幕截圖中看到它的外觀。 SwiftUI使使用文檔瀏覽器變得超級(jí)容易瓶蝴。 只需遵循MemeMakerApp.swift中的代碼即可:

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

DocumentGroup在處理文檔時(shí)有兩個(gè)初始化程序:init(newDocument:editor :)init(viewing:viewer :)毒返。 第一個(gè)允許您創(chuàng)建新文檔和編輯現(xiàn)有文檔,而第二個(gè)則只能查看文件舷手。 因?yàn)槟獎(jiǎng)?chuàng)建和編輯memes拧簸,所以入門項(xiàng)目將使用第一個(gè)初始化程序。

初始化程序收到它應(yīng)該顯示的文檔男窟。 在這種情況下盆赤,您將初始化一個(gè)新的空MemeMakerDocument贾富,將在以后使用。 初始化程序還會(huì)收到一個(gè)用于構(gòu)建文件編輯視圖的閉包牺六。


Working With a File Document

FileDocument是應(yīng)用可以讀取和寫入設(shè)備的文檔的基本協(xié)議祷安。該協(xié)議包含兩個(gè)靜態(tài)屬性:readContentTypeswritableContentTypes。兩者都是UTType數(shù)組兔乞,分別定義了文檔可以讀取和寫入的類型汇鞭。僅要求readyContentTypes,因?yàn)?code>writableContentTypes也默認(rèn)為readContentContentTypes庸追。

FileDocument還需要帶有FileDocumentReadConfiguration的初始化程序霍骄。此配置將UTType形式的文檔類型與包含其內(nèi)容的FileWrapper捆綁在一起。

最后淡溯,任何符合FileDocument的類或結(jié)構(gòu)體都需要實(shí)現(xiàn)fileWrapper(configuration :)读整。寫入文檔時(shí)會(huì)調(diào)用它,它以FileDocumentWriteConfiguration作為參數(shù)咱娶,類似于讀取配置米间,但用于寫入。

聽起來可能需要做很多工作膘侮,但請(qǐng)放心屈糊。在本教程的這一部分中,您將了解如何使用這兩種配置琼了。

1. Defining Exported UTTypes

打開MemeMakerDocument.swift逻锐。在文件頂部,您會(huì)在UTType上找到擴(kuò)展名雕薪,該擴(kuò)展名定義了入門項(xiàng)目正在使用的類型昧诱。

用以下代碼替換此擴(kuò)展名:

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

在上面的代碼中,您將memeDocument定義為新的UTType所袁,以便可以在下一步中使用它盏档。

仍在MemeMakerDocument.swift中,找到readableContentTypes燥爷。 如前所述蜈亩,這定義了應(yīng)用可以讀取和寫入的UTType列表。 用以下新代碼替換屬性:

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

這會(huì)將您之前創(chuàng)建的新類型設(shè)置為MemeMakerDocument文檔可以讀取的類型局劲。 由于writableContentTypes默認(rèn)為readContentContentTypes勺拣,因此您無需添加它。

2. Creating the Data Model

在繼續(xù)使用MemeMakerDocument之前鱼填,您需要定義其適用的meme药有。 在Shared組中創(chuàng)建一個(gè)名為Meme.swift的新Swift文件,并選中Targets中的兩個(gè)復(fù)選框,以便將其同時(shí)包含在iOSmacOS target中愤惰。

添加以下代碼:

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

MemeMaker會(huì)將Meme保存到磁盤苇经。 它符合Codable,因此您可以使用JSONEncoderJSONDecoder將其轉(zhuǎn)換為Data并返回宦言。 它還包裝了表示一個(gè)Meme所需的所有信息:兩個(gè)字符串和一個(gè)圖像數(shù)據(jù)扇单。

再次打開MemeMakerDocument.swift并在類的開頭找到以下代碼:

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

MemeMakerDocument現(xiàn)在可以保存實(shí)際的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實(shí)例表示的meme蜘澜。
  • 2) 您為MemeMakerDocument定義了一個(gè)初始化程序。 初始化程序接收?qǐng)D像的數(shù)據(jù)以及頂部和底部的文本响疚。
  • 3) 最后鄙信,根據(jù)這些參數(shù)初始化一個(gè)新的Meme

此時(shí)忿晕,您將看到代碼中的錯(cuò)誤装诡。 不用擔(dān)心-在保存和加載文件時(shí),還需要進(jìn)行一些其他更改以對(duì)文檔進(jìn)行編碼和解碼践盼。

3. Encoding and Decoding the Document

首先鸦采,對(duì)fileWrapper(configuration :)進(jìn)行更改。 用以下幾行替換方法主體:

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

這會(huì)將meme轉(zhuǎn)換為數(shù)據(jù)咕幻,并創(chuàng)建一個(gè)WriteConfiguration渔伯,系統(tǒng)將其用于將該文檔寫入磁盤。

接下來谅河,將init(configuration :)的主體替換為以下代碼:

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

打開現(xiàn)有文檔時(shí)咱旱,應(yīng)用程序?qū)⒄{(diào)用此初始化程序。 您嘗試從給定的ReadConfiguration中獲取數(shù)據(jù)绷耍,并將其轉(zhuǎn)換為Meme的實(shí)例。 如果該過程失敗鲜侥,則初始化程序?qū)⒁l(fā)系統(tǒng)要處理的錯(cuò)誤褂始。

現(xiàn)在,您已經(jīng)添加了對(duì)自定義meme文檔進(jìn)行讀寫的支持描函。 但是崎苗,由于您沒有顯示Meme編輯器,因此用戶仍然看不到其中任何一個(gè)舀寓。 您將在下一部分中解決該問題胆数。


Providing a Custom Editor

目前,該應(yīng)用程序使用TextEditor互墓。 基于SwiftUI文檔的多平臺(tái)應(yīng)用程序的模板從此視圖開始必尼。 用于顯示可編輯和可滾動(dòng)的文本。

TextEditor不適合創(chuàng)建和編輯模因,因此您將創(chuàng)建自己的視圖來編輯MemeMakerDocument判莉。

在開始創(chuàng)建新的編輯器視圖之前豆挽,您將刪除舊的視圖。 打開ContentView.swift并將主體替換為空視圖:

Spacer()

這樣可以確保在構(gòu)建新編輯器時(shí)不會(huì)出現(xiàn)編譯器錯(cuò)誤券盅。

1. Creating the Image Layer

編輯器將包含兩個(gè)子視圖帮哈。 您將在創(chuàng)建實(shí)際的編輯器之前創(chuàng)建它們。

第一個(gè)是ImageLayer锰镀,它代表圖像娘侍。 在Shared中創(chuàng)建一個(gè)名為ImageLayer.swift的新SwiftUI View文件,并在Targets中選中MemeMaker(iOS)MemeMaker(macOS)的復(fù)選框泳炉。 用以下內(nèi)容替換文件中的兩個(gè)結(jié)構(gòu):

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

上面的代碼正在執(zhí)行以下操作:

  • 1) ImageLayer具有SwiftUI綁定到meme圖像的數(shù)據(jù)憾筏。 在后續(xù)步驟中,MemeEditor將數(shù)據(jù)傳遞到此視圖胡桃。
  • 2) 它的bodyNSUIImage組成踩叭,NSUIImage是您使用圖像數(shù)據(jù)初始化的視圖。 您可能想知道這種view是什么翠胰。 這是iOS上的UIImagemacOS上的NSImage的類型別名容贝,以及擴(kuò)展名。 它允許使用一種常見的圖像類型之景,在兩種平臺(tái)上具有相同的方法和屬性斤富。 您可以在iOS組的NSUIImage_iOS.swift文件和macOS組的NSUIImage_macOS.swift中找到它。 根據(jù)您運(yùn)行的是MemeMaker(iOS)還是MemeMaker(macOS)锻狗,它使用正確的類型满力。
  • 3) 最后,添加預(yù)覽以支持Xcode的預(yù)覽功能轻纪。

查看預(yù)覽以確保您的視圖正在顯示圖像:

現(xiàn)在您正在顯示圖像油额,您可以繼續(xù)顯示文本!

2. Creating the Text Layer

TextLayer是第二個(gè)子視圖刻帚,它將頂部和底部文本放置在圖像上方潦嘶。 同樣,在Shared中創(chuàng)建一個(gè)新的SwiftUI View文件崇众,并將其命名為TextLayer.swift掂僵。 切記將MemeMaker(iOS)MemeMaker(macOS)選中為Targets

將此替換為生成的TextLayer結(jié)構(gòu)體:

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

TextLayer具有兩個(gè)屬性:meme顷歌,保存顯示的Meme锰蓬; 和imageContentimageContent是一個(gè)閉包眯漩,用于在TextLayerbody中創(chuàng)建另一個(gè)視圖芹扭。 請(qǐng)注意,您已將視圖聲明為通用結(jié)構(gòu),其中圖像內(nèi)容視圖可以是遵循View的任何內(nèi)容冯勉。

接下來澈蚌,將body添加到視圖中:

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

    MemeTextField(text: $meme.bottomText)
  }
}

您可以在body中使用兩個(gè)ZStack,以將頂部文本放置在圖像頂部灼狰,將底部文本放置在圖像底部宛瞄。 為了顯示圖像,您調(diào)用傳遞給TextLayer視圖的閉包交胚。 要顯示文本份汗,請(qǐng)使用MemeTextField,這是在啟動(dòng)程序項(xiàng)目中設(shè)置的常規(guī)TextField蝴簇,用于顯示格式化的文本杯活。

最后,用以下內(nèi)容替換預(yù)覽:

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

看一下預(yù)覽:

目前熬词,它看起來不算meme旁钧。 不用擔(dān)心,在下一節(jié)中互拾,您將結(jié)合圖像和文本圖層來創(chuàng)建MemeEditor歪今。

3. Creating a Meme Editor

您之前創(chuàng)建的所有文件均獨(dú)立于平臺(tái)。 但是MemeEditor將根據(jù)應(yīng)用程序運(yùn)行在iOS / iPadOS還是macOS上颜矿,使用特定于平臺(tái)的不同方法來導(dǎo)入圖像寄猩。

在后續(xù)步驟中,您將創(chuàng)建另一個(gè)MemeEditor骑疆,以在macOS上顯示田篇,但現(xiàn)在,從iOS和iPadOS版本開始箍铭。 創(chuàng)建一個(gè)新的SwiftUI視圖文件MemeEditor_iOS.swift泊柬。 這次,它不應(yīng)該在Shared組中诈火,而應(yīng)該在iOS中彬呻。 請(qǐng)記住僅檢查MemeMaker(iOS)target

用以下代碼替換文件中的視圖:

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

MemeEditor具有與其呈現(xiàn)的meme的綁定以及兩個(gè)屬性柄瑰。 您將使用ShowingImagePicker來決定何時(shí)顯示可以讓用戶選擇圖像的圖像選擇器。 然后剪况,您將圖像存儲(chǔ)在inputImage中教沾。

接下來,向該結(jié)構(gòu)體添加一個(gè)新方法來存儲(chǔ)輸入圖像:

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

現(xiàn)在译断,您可以在視圖內(nèi)部添加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發(fā)生的事情:

  • 1) 首先授翻,創(chuàng)建一個(gè)新的TextLayer并傳遞對(duì)meme的綁定和一個(gè)閉包以創(chuàng)建ImageLayer
  • 2) 在此閉包中,定義一個(gè)按鈕堪唐,將其在點(diǎn)擊時(shí)將showingImagePicker設(shè)置為true巡语。 使用上面定義的ImageLayer作為label,或者如果該meme尚不包含圖像淮菠,則顯示一個(gè)按鈕男公。
  • 3) 每當(dāng)showingImagePicker設(shè)置為true時(shí),使用sheet顯示UIImagePicker合陵。 UIImagePickerUIImagePickerController的包裝枢赔,以使其可用于SwiftUI。 它允許用戶從其設(shè)備中選擇圖像拥知,并且每當(dāng)關(guān)閉picker時(shí)都會(huì)調(diào)用loadImage踏拜。

接下來,將文件中的預(yù)覽替換為以下內(nèi)容:

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

現(xiàn)在低剔,您的預(yù)覽應(yīng)該顯示對(duì)視圖的測(cè)試:

最后速梗,打開ContentView.swift。 用以下代碼替換body的內(nèi)容襟齿,該代碼是專用的meme編輯器姻锁,而不是文本編輯器:

MemeEditor(meme: $document.meme)

在這里,您用新的MemeEditor替換了TextEditor蕊唐。 您將文檔的meme傳遞給MemeEditor屋摔,從而使用戶能夠操作和處理meme

最后替梨,完成所有這些編碼之后钓试,MemeMaker準(zhǔn)備在iPhone上運(yùn)行! 選擇MemeMaker(iOS)scheme并構(gòu)建并運(yùn)行副瀑。 創(chuàng)建一個(gè)新文檔弓熏,如下所示:

現(xiàn)在,您可以選擇一個(gè)有趣的圖像糠睡,添加一些文字并提高您的meme制作技巧挽鞠。 嘗試創(chuàng)建一個(gè)像這樣的有趣的meme

干得好!


Using the App on macOS

SwiftUI的一大優(yōu)勢(shì)是您可以在所有Apple平臺(tái)上使用它狈孔。 但是信认,盡管您使用了NSUIImage,但仍需要進(jìn)行一些更改才能在macOS上運(yùn)行MemeMaker均抽。

1. Implementing a MemeEditor for macOS

由于MemeEditor使用UIImagePickerController嫁赏,因此您無法在macOS上使用它。 相反油挥,您將創(chuàng)建另一個(gè)版本的MemeEditor潦蝇,該版本將在macOS上運(yùn)行該應(yīng)用程序時(shí)使用款熬。 它將使用NSOpenPanel讓用戶選擇圖片作為meme的背景。

但是多虧了SwiftUI攘乒,大多數(shù)視圖可以保持不變贤牛。 您可以重用ImageLayerTextLayer。 唯一的區(qū)別是用戶如何選擇圖像则酝。

在macOS組中創(chuàng)建一個(gè)新的SwiftUI View文件殉簸,并將其命名為MemeEditor_macOS.swift。 僅檢查MemeMaker(macOS)target堤魁。 用以下代碼替換該文件的內(nèi)容:

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

在這里喂链,您可以創(chuàng)建與之前為iOS創(chuàng)建的視圖類似的視圖。 不過妥泉,這一次椭微,您添加了一個(gè)單獨(dú)的按鈕來調(diào)用selectImageselectImage使用NSOpenPanel讓您的用戶選擇圖像盲链。 如果選擇成功蝇率,則將新的圖像數(shù)據(jù)存儲(chǔ)在模因中。

最后刽沾,在文件底部添加一個(gè)預(yù)覽:

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

構(gòu)建并運(yùn)行本慕。 (您需要使用macOS 11.0或更高版本。)應(yīng)用如下所示:

您可以在macOS上創(chuàng)建相同的meme

Mac應(yīng)用程序無需任何額外的工作侧漓,就可以使用帶有快捷方式的工作菜單锅尘。 例如,您可以使用Command-N創(chuàng)建一個(gè)新文檔布蔗,并使用Command-S保存該文檔藤违,或者您可以使用Command-Z撤消上一次更改。

創(chuàng)建使用文檔并同時(shí)在iOS和macOS上運(yùn)行的應(yīng)用有多么容易纵揍,這是否令人驚訝顿乒?

文檔是許多優(yōu)秀應(yīng)用程序的核心部分。 現(xiàn)在泽谨,借助SwiftUI璧榄,為iOS,iPadOS和macOS構(gòu)建基于文檔的應(yīng)用程序變得更加容易吧雹。

如果您想更深入地研究基于SwiftUI文檔的應(yīng)用程序骨杂,請(qǐng)參閱 Build document-based apps in SwiftUI

有關(guān)SwiftUI的更多信息雄卷,請(qǐng)查看SwiftUI: Getting StartedSwiftUI by Tutorials一書腊脱。

要?jiǎng)?chuàng)建基于文檔的UIKit應(yīng)用,請(qǐng)?jiān)?a target="_blank">Document-Based Apps Tutorial: Getting Started中找到更多信息龙亲。

后記

本篇主要講述了基于SwiftUIDocument-Based App的創(chuàng)建陕凹,感興趣的給個(gè)贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鳄炉,隨后出現(xiàn)的幾起案子杜耙,更是在濱河造成了極大的恐慌,老刑警劉巖拂盯,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佑女,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谈竿,警方通過查閱死者的電腦和手機(jī)团驱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來空凸,“玉大人嚎花,你說我怎么就攤上這事⊙街蓿” “怎么了紊选?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)道逗。 經(jīng)常有香客問我兵罢,道長(zhǎng),這世上最難降的妖魔是什么滓窍? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任卖词,我火速辦了婚禮,結(jié)果婚禮上吏夯,老公的妹妹穿的比我還像新娘此蜈。我一直安慰自己,他們只是感情好锦亦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布舶替。 她就那樣靜靜地躺著,像睡著了一般杠园。 火紅的嫁衣襯著肌膚如雪顾瞪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天抛蚁,我揣著相機(jī)與錄音陈醒,去河邊找鬼。 笑死瞧甩,一個(gè)胖子當(dāng)著我的面吹牛钉跷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肚逸,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼爷辙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼彬坏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起膝晾,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤栓始,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后血当,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幻赚,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年臊旭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了落恼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡离熏,死狀恐怖佳谦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撤奸,我是刑警寧澤吠昭,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站胧瓜,受9級(jí)特大地震影響矢棚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜府喳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一蒲肋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钝满,春花似錦兜粘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至碎捺,卻和暖如春路鹰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背收厨。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工晋柱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诵叁。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓雁竞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拧额。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碑诉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348