SwiftUI框架詳細(xì)解析 (二十六) —— 基于SwiftUI和Xcode12的Multiplatform App的搭建(一)

版本記錄

版本號 時間
V1.0 2021.03.21 星期日

前言

今天翻閱蘋果的API文檔,發(fā)現(xiàn)多了一個框架SwiftUI,這里我們就一起來看一下這個框架。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁的創(chuàng)建(一)
3. SwiftUI框架詳細(xì)解析 (三) —— 基于SwiftUI的閃屏頁的創(chuàng)建(二)
4. SwiftUI框架詳細(xì)解析 (四) —— 使用SwiftUI進(jìn)行蘋果登錄(一)
5. SwiftUI框架詳細(xì)解析 (五) —— 使用SwiftUI進(jìn)行蘋果登錄(二)
6. SwiftUI框架詳細(xì)解析 (六) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(一)
7. SwiftUI框架詳細(xì)解析 (七) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(二)
8. SwiftUI框架詳細(xì)解析 (八) —— 基于SwiftUI的動畫的實現(xiàn)(一)
9. SwiftUI框架詳細(xì)解析 (九) —— 基于SwiftUI的動畫的實現(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簡單App的Dependency Injection應(yīng)用(一)
17. SwiftUI框架詳細(xì)解析 (十七) —— 基于SwiftUI簡單App的Dependency Injection應(yīng)用(二)
18. SwiftUI框架詳細(xì)解析 (十八) —— Firebase Remote Config教程(一)
19. SwiftUI框架詳細(xì)解析 (十九) —— Firebase Remote Config教程(二)
20. SwiftUI框架詳細(xì)解析 (二十) —— 基于SwiftUI的Document-Based App的創(chuàng)建(一)
21. SwiftUI框架詳細(xì)解析 (二十一) —— 基于SwiftUI的Document-Based App的創(chuàng)建(二)
22. SwiftUI框架詳細(xì)解析 (二十二) —— 基于SwiftUI的AWS AppSync框架的使用(一)
23. SwiftUI框架詳細(xì)解析 (二十三) —— 基于SwiftUI的AWS AppSync框架的使用(二)
24. SwiftUI框架詳細(xì)解析 (二十四) —— 基于SwiftUI的編輯占位符的使用(一)
25. SwiftUI框架詳細(xì)解析 (二十五) —— 基于SwiftUI的編輯占位符的使用(二)

開始

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

了解如何使用Xcode 12的多平臺應(yīng)用程序模板和SwiftUI編寫可在每個Apple平臺上運行的單個應(yīng)用程序。內(nèi)容來自翻譯

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

Swift 5, iOS 14, Xcode 12

接著就是正文啦。

Mac Catalyst發(fā)布以來,蘋果一直在為讓iOS開發(fā)人員將其應(yīng)用程序帶到Mac鋪平道路。擴(kuò)展到SwiftUI(Apple建立用戶界面的一種簡單的聲明式方法),現(xiàn)在您可以使用它來構(gòu)建整個應(yīng)用程序。這些擴(kuò)展以及Xcode 12中新增的多平臺應(yīng)用程序模板(multiplatform app template),使您可以使用一個代碼庫為每個Apple平臺構(gòu)建應(yīng)用程序。

在本教程中,您將了解:

  • Xcode 12的多平臺應(yīng)用程序模板(multiplatform app template)
  • App協(xié)議
  • AppScenesViews如何組合在一起
  • 相同代碼適應(yīng)每個平臺的方式
  • 如何在重用視圖時為每個平臺創(chuàng)建自定義UI

您將通過為RayGem添加新功能(適用于iOSiPadOSmacOS的應(yīng)用程序來顯示有關(guān)不同寶石的信息)來學(xué)習(xí)所有這些內(nèi)容。

注意:本教程假定您熟悉SwiftUI。如果您只是入門,請查看SwiftUI: Getting StartedRayGem還利用Core Data。盡管您不需要深入了解Core Data,但如果您想了解更多信息,這是一個很好的起點: Core Data with SwiftUI Tutorial: Getting Started

打開項目材料。 在啟動文件夾中打開RayGem.xcodeproj。 構(gòu)建并運行。

RayGem是一個簡單的多平臺應(yīng)用程序,其中列出了寶石(寶石或半寶石)的集合。 用戶可以閱讀有關(guān)他們的有趣事實,并保存他們的收藏夾。

您已經(jīng)可以滾動并點擊寶石以閱讀有關(guān)每個寶石的事實。 該應(yīng)用程序具有從Core Data獲取和保存喜歡的寶石的代碼,但是它既不能保存也不能列出喜歡的寶石。 您將在本教程中添加此功能。

在啟動程序項目中打開不同的視圖,以熟悉該應(yīng)用程序。 該應(yīng)用程序的主視圖位于GemList.swift中,顯示了從Core Data存儲中獲取的GemRow.swift中找到的行的列表。 通過點擊一行,您可以導(dǎo)航到DetailsView.swift中的詳細(xì)信息視圖。

1. Considering the Project Structure

在開始進(jìn)行任何更改之前,請看一下啟動項目。 請注意,這些組與您通常的iOS入門項目中的組有何不同。

創(chuàng)建新項目時,Xcode 12有一個名為Multiplatform的新部分。 在其中,您會找到適用于多平臺應(yīng)用程序的新模板。 本教程的入門項目是使用此模板構(gòu)建的。

此模板創(chuàng)建三個組:

  • iOSiOS專用代碼
  • macOSmacOS專用代碼
  • Shared:兩個平臺的代碼,包括模型,業(yè)務(wù)邏輯和可重用視圖

SwiftUI允許您在平臺之間共享UI代碼,并根據(jù)設(shè)備自動調(diào)整UI。 您可以創(chuàng)建可在每個平臺上重用的視圖,但是某些行為更適合某些平臺。 這樣,每個平臺都有一個組,可以讓您為每個平臺編寫特定的代碼,同時仍可以重用許多代碼。


Understanding the New App and Scene Protocol

Shared組中打開AppMain.swift

@main
struct AppMain: App {
  let persistenceController = PersistenceController.shared

  var body: some Scene {
    WindowGroup {
      ContentView()
        .environment(
           \.managedObjectContext, 
           persistenceController.container.viewContext)
    }
  }
}

iOS 14中,Apple引入了新的App協(xié)議,該協(xié)議處理應(yīng)用程序的生命周期,并取代了舊的AppDelegate.swiftSceneDelegate.swift

View協(xié)議非常相似,App協(xié)議要求您通過返回Scene來實現(xiàn)主體body。主體將成為您應(yīng)用程序的根視圖。通常,您會返回一個WindowGroup,這是一種特殊的Scene類型,它是應(yīng)用程序視圖層次結(jié)構(gòu)的容器。 SwiftUIWindowGroup共同負(fù)責(zé)在每個平臺上以不同的方式呈現(xiàn)您的應(yīng)用程序。例如,在macOS上,WindowGroups將在其菜單欄中自動具有窗口管理選項,并支持在選項卡中收集應(yīng)用程序的所有窗口。

Swift 5.3引入了新的@main屬性,以指示應(yīng)用程序的入口點。通過將此屬性添加到實現(xiàn)App的結(jié)構(gòu)中,SwiftUI將以該結(jié)構(gòu)為應(yīng)用程序的起點。


Running on macOS

該應(yīng)用程序已經(jīng)具有可在iOS上運行的基本功能,但是macOS呢?將targetRayGem(iOS)更改為RayGem(macOS),然后生成并再次運行。

該應(yīng)用程序不包含任何iOSmacOS特定代碼,它們都是使用普通的舊版SwiftUI構(gòu)建的,就像您構(gòu)建其他任何應(yīng)用程序一樣。 但是,您仍然可以在iOSmacOS上運行您的應(yīng)用程序! 那不是很酷嗎?

使用新的Multiplatform應(yīng)用程序模板,Xcode為您的應(yīng)用程序創(chuàng)建了兩個targets:一個用于在iOSiPadOS上運行,另一個用于在macOS上運行。 它使用這些target在每個相應(yīng)平臺上運行您的應(yīng)用程序。

1. Understanding how SwiftUI Adapts to the Platform

請注意,SwiftUI如何針對每個平臺調(diào)整UI。 在iOS上,它使用帶有列表的導(dǎo)航視圖。 當(dāng)用戶點擊一行時,它將推送該行的目標(biāo)視圖。 在macOS上,它使用帶有側(cè)邊列表和內(nèi)容視圖的窗口。 當(dāng)用戶單擊一行時,內(nèi)容視圖將更新為該行的目標(biāo)視圖。

現(xiàn)在,將target切換回RayGem(iOS),并在iPad模擬器上構(gòu)建并運行。

當(dāng)應(yīng)用程序在iPadOS上運行時,列表將隱藏在視圖的左側(cè),而當(dāng)用戶選擇gem時,主視圖將顯示目標(biāo)視圖。


Polishing the macOS app

UI已經(jīng)適應(yīng)了不同的設(shè)備大小,甚至可以在macOS上調(diào)整窗口大小,但是有時候,您可能希望在某些設(shè)備上添加一些限制,同時在其他設(shè)備上保持相同的行為。 值得慶幸的是,SwiftUI包含許多修飾符,以影響它如何將視圖適應(yīng)不同的平臺。 告訴SwiftUI如何處理您的應(yīng)用,可以使您的應(yīng)用成為更好的macOS成員。

1. Adding a minimum width to your list

現(xiàn)在,您可以在macOS上調(diào)整gem側(cè)面列表的大小,并將其縮小為零。

這是一種行為,可能會使某些用戶感到困惑。 SwiftUI為您提供了修飾符來處理這種情況,而無需為每個平臺編寫特定的代碼。

打開GemList.swift,找到// TODO: Add min frame here。 并在注釋下方添加以下行:

.frame(minWidth: 250)

構(gòu)建并在iPhone模擬器上運行以查看結(jié)果。

此修飾符將最小寬度添加到列表中。 在iOS上,這可能沒有多大意義,因為列表將使用視圖的整個寬度。 但是,在macOS上,此修飾符可確保列表List保持最小寬度為250點,同時仍允許用戶調(diào)整其大小。

target更改為RayGem(macOS),然后構(gòu)建并再次運行。 嘗試調(diào)整列表的大小。

請注意,邊列表仍可以調(diào)整大小,但始終保持超過250點的寬度。

2. Adding a navigation title

仍在GemList.swift中,請注意列表底部的修飾符navigationTitle(_ :)。 這是iOS 14上引入的新修飾符,用于配置視圖的標(biāo)題。 在iOSwatchOS上,它將使用字符串作為導(dǎo)航視圖的標(biāo)題。 iPadOS將設(shè)置主導(dǎo)航視圖標(biāo)題和應(yīng)用程序切換器中標(biāo)題。 這對于區(qū)分您的應(yīng)用實例很重要。 在macOS上,窗口標(biāo)題欄和Mission Control使用此字符串作為標(biāo)題。


Working With Toolbars

現(xiàn)在,該讓用戶擁有保存自己喜歡的寶石的能力了。

DetailsView.swift中,將以下內(nèi)容添加到視圖的底部:

func toggleFavorite() {
  gem.favorite.toggle()
  try? viewContext.save()
}

此方法切換當(dāng)前gem上的favorite屬性并將更改保存到Core Data

接下來,找到// TODO: Add favorite button here,并在注釋下方添加以下代碼:

// 1
.toolbar {
  // 2
  ToolbarItem {
    Button(action: toggleFavorite) {
      // 3
      Label(
        gem.favorite ? "Unfavorite" : "Favorite",
        systemImage: gem.favorite ? "heart.fill" : "heart"
      )
      .foregroundColor(.pink)
    }
  }
}

這是代碼的細(xì)分:

  • 1) iOS 14引入了新的視圖修飾符:toolbar(content:)。 此修飾符采用表示toolbar內(nèi)容的ToolbarItem
  • 2) 使用一個按鈕添加一個ToolbarItem,以在gem上切換favorite
  • 3) 接下來,添加Label作為按鈕的內(nèi)容,標(biāo)題為“Favorite” or “Unfavorite”,并顯示心形圖像。

構(gòu)建并運行。 然后,收藏一顆寶石。

現(xiàn)在,在macOS上構(gòu)建并運行。 喜歡一個寶石可以看到結(jié)果。

SwiftUI使用ToolbarItem并將其放置在每個平臺的預(yù)期位置。 在iOS上,它遵循bar的顏色方案,將Label的圖像用作導(dǎo)航欄上的按鈕。 在macOS上,它也使用Label的圖像。 但是,如果您調(diào)整窗口的大小并且在工具欄上的按鈕上沒有任何空間,它將創(chuàng)建帶有Label標(biāo)題的菜單按鈕。

將窗口調(diào)整為可能的最小寬度。

SwiftUI會針對每個平臺調(diào)整UI,即使在調(diào)整窗口大小時也找到了顯示按鈕的最佳方法。


Understanding tab views on different platforms

現(xiàn)在用戶可以收藏自己的寶石了,可以列出這些收藏夾了。

入門項目已經(jīng)附帶了此代碼,即FavoriteGems視圖。 該視圖獲取并列出所有將favorite屬性設(shè)置為true的寶石。

打開ContentView.swift并將以下枚舉添加到文件頂部:

enum NavigationItem {
  case all
  case favorites
}

該枚舉描述了應(yīng)用程序的兩個選項卡。 接下來,通過將body的內(nèi)容替換為以下內(nèi)容來添加tab view

// 1
TabView {
  // 2
  NavigationView {
    GemList()
  }
  .tabItem { Label("All", systemImage: "list.bullet") }
  .tag(NavigationItem.all)

  // 3
  NavigationView {
    FavoriteGems()
  }
  .tabItem { Label("Favorites", systemImage: "heart.fill") }
  .tag(NavigationItem.favorites)
}

上面的代碼是這樣的:

  • 1) 首先,創(chuàng)建一個TabView作為根視圖。
  • 2) 接下來,添加GemList作為其第一個視圖,并帶有一個標(biāo)題為“All”Label和列表項目符號的圖像。
  • 3) 添加FavoriteGems作為第二個視圖,并帶有一個標(biāo)題為Favorites和小心圖像的Label

iOS上構(gòu)建并運行。 收藏一些寶石,然后打開Favorites選項卡以查看其中列出的寶石。

接下來,將target更改為macOS。 構(gòu)建并運行以查看SwiftUI如何適配macOS上的UI

極好的! 您已經(jīng)有一個可以在iOSmacOS上運行的簡單應(yīng)用程序! 花一點時間享受您到目前為止所取得的成就。


Optimizing the User Experience for Each Platform

SwiftUI嘗試使代碼中聲明的UI適應(yīng)每個平臺。 iOS上的TabBar在底部具有其欄,圖像和文本作為按鈕。 在macOS上,它使用帶有標(biāo)題的視圖頂部的欄,非常類似于segmented view

即使SwiftUI可以在每個平臺上調(diào)整用戶界面,但這并不意味著它總是能夠創(chuàng)建用戶期望的內(nèi)容。 與在macOS上使用TabBar相比,更好的布局是帶有類別列表的Sidebar。 然后,一個列表將顯示所選類別的每個元素。

您的應(yīng)用程序已經(jīng)可以在兩個平臺上運行,但是用戶希望在任何地方都能獲得最佳體驗。 值得慶幸的是,Apple添加了一種在多平臺應(yīng)用程序中創(chuàng)建特定于平臺的視圖的方法。 這正是Xcode中的macOSiOS組的用途! 您現(xiàn)在將更新應(yīng)用程序中的tab bar,以將邊欄sidebar布局用于macOS

1. Updating the macOS UI

macOS組內(nèi)創(chuàng)建一個新的SwiftUI View文件,并將其命名為GemListViewer.swift。 僅選擇macOS target membership

首先,向視圖添加新的屬性和方法:

@State var selection: NavigationItem? = .all

func toggleSideBar() {
  NSApp.keyWindow?.firstResponder?.tryToPerform(
    #selector(NSSplitViewController.toggleSidebar),
    with: nil)
}

這是一個狀態(tài)變量,您將使用側(cè)邊欄中的當(dāng)前所選類別進(jìn)行更新:所有寶石或僅收藏的寶石。 當(dāng)用戶單擊按鈕時,toggleSideBar()將顯示或隱藏側(cè)邊欄; 您會稍等一下。

接下來,將以下計算的屬性添加到視圖中:

var sideBar: some View {
  List(selection: $selection) {
    NavigationLink(
      destination: GemList(),
      tag: NavigationItem.all,
      selection: $selection
    ) {
      Label("All", systemImage: "list.bullet")
    }
    .tag(NavigationItem.all)
    NavigationLink(
      destination: FavoriteGems(),
      tag: NavigationItem.favorites,
      selection: $selection
    ) {
      Label("Favorites", systemImage: "heart")
    }
    .tag(NavigationItem.favorites)
  }
  // 3
  .frame(minWidth: 200)
  .listStyle(SidebarListStyle())
  .toolbar {
    // 4
    ToolbarItem {
      Button(action: toggleSideBar) {
        Label("Toggle Sidebar", systemImage: "sidebar.left")
      }
    }
  }
}

您將創(chuàng)建一個側(cè)邊欄sidebar視圖,其中包含一個包含兩個NavigationLinksList —— 一個用于GemList,一個用于FavoriteGems。 通過使用SidebarListStyle,您可以告訴SwiftUI將此List顯示為側(cè)邊欄sidebar,以便用戶選擇要查看的類別。 您還可以在工具欄內(nèi)創(chuàng)建一個ToolbarItem,并帶有一個按鈕來切換側(cè)欄sidebarmacOS應(yīng)用中的預(yù)期行為是可以隱藏和顯示側(cè)邊欄。

接下來,將body的內(nèi)容替換為以下內(nèi)容:

NavigationView {
  sideBar
  Text("Select a category")
    .foregroundColor(.secondary)
  Text("Select a gem")
    .foregroundColor(.secondary)
}

這將顯示sidebar以及一些文本。

最后,將previews的內(nèi)容替換為以下內(nèi)容:

GemListViewer()
  .environment(
    \.managedObjectContext, 
    PersistenceController.preview.container.viewContext)

您已經(jīng)完成了macOS UI的操作,但是暫時還看不到它。 首先,您將轉(zhuǎn)到iOS UI。

2. Updating the iOS UI

iOS組中創(chuàng)建另一個SwiftUI視圖,并將其命名為GemListViewer.swift。 這次,請確保僅選擇iOS target

將視圖的body替換為以下內(nèi)容:

// 1
TabView {
  // 2
  NavigationView {
    GemList()
      .listStyle(InsetGroupedListStyle())
  }
  .tabItem { Label("All", systemImage: "list.bullet") }
  .tag(NavigationItem.all)
  // 3
  NavigationView {
    FavoriteGems()
      .listStyle(InsetGroupedListStyle())
  }
  .tabItem { Label("Favorites", systemImage: "heart.fill") }
  .tag(NavigationItem.favorites)
}

這是上面的代碼中發(fā)生的事情:

  • 1) 將TabView聲明為根視圖,但這一次,僅適用于iOS
  • 2) 將GemList添加為第一個視圖。
  • 3) 接下來,添加FavoriteGems作為第二個視圖。

請注意,這正是您當(dāng)前在**ContentView.swift**中擁有的代碼。

接下來,用以下代碼替換previews的內(nèi)容:

Group {
  GemListViewer()

  GemListViewer()
    .previewDevice(PreviewDevice(rawValue: "iPad Air 2"))
}
.environment(
  \.managedObjectContext,
  PersistenceController.preview.container.viewContext)

您設(shè)置了默認(rèn)情況下將使用iPhone布局的預(yù)覽,然后添加了指定了iPad Air 2布局的第二個預(yù)覽。

最后,再次打開ContentView.swift,并用以下單行代碼替換body的所有內(nèi)容:

GemListViewer()

構(gòu)建并運行iOS target

現(xiàn)在,構(gòu)建并運行macOS target

ContentView是兩個平臺之間的共享視圖。但是,您的項目有兩個GemListViewer,但一個僅包含在macOS target中,另一個僅包含在iOS target中。通過這種設(shè)置,您的應(yīng)用將為每個平臺使用正確的GemListViewer。這提供了在每個平臺上重用應(yīng)用程序的核心視圖的可能性,但也允許在每個平臺上使用自定義行為和UI。每個平臺具有相同視圖的不同版本,可讓您訪問特定于平臺的API和功能,例如iOS InsetGroupedListStylemacOS側(cè)欄切換。


Understanding Independent Scenes and View States

SwiftUI已經(jīng)捆綁了許多多平臺應(yīng)用程序功能。使用WindowGroup,可以在iPadOS上添加對應(yīng)用程序的多個實例的支持,而在macOS上可以支持多個窗口。它甚至添加了用于在macOS上打開新窗口的通用鍵盤快捷鍵Command-N

macOS上構(gòu)建并運行。選擇一個寶石,然后使用File ? New Window打開一個新窗口。或者,使用鍵盤快捷鍵Command-N

請注意,使用一個窗口不會影響另一個窗口的狀態(tài)。 舊窗口保持其狀態(tài),顯示您選擇的寶石。 同時,新窗口的行為類似于應(yīng)用程序的新實例,獨立于原始實例。

每個場景都會處理應(yīng)用程序的狀態(tài)并更新其視圖,但不會直接影響另一個場景。

SwiftUImacOS帶來的另一個共同功能是能夠?qū)⑺?code>windows合并到選項卡tabs中。 轉(zhuǎn)到Window ? Merge All Windows

每個選項卡的行為都不同,具有各自的狀態(tài)。


Adding Extra Functionality for macOS

大多數(shù)macOS應(yīng)用程序的常見功能是Preferences…菜單命令。 用戶將希望能夠通過轉(zhuǎn)到RayGem ? Preferences或使用Command-快捷方式來更改設(shè)置。

您將在macOS版本中添加一個簡單的設(shè)置視圖,以便用戶查看有關(guān)該應(yīng)用程序的有用信息,例如版本號和清除他們喜歡的寶石的按鈕。

1. Creating the Preferences View

Views組內(nèi)創(chuàng)建一個新的SwiftUI View文件。 將其命名為SettingsView.swift并選擇macOS target。 首先向視圖添加幾個方法和一個屬性:

// 1
@State var showAlert = false
// 2
var appVersion: String {
  Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
}
// 3
func showClearAlert() {
  showAlert.toggle()
}
// 4
func clearFavorites() {
  let viewContext = PersistenceController.shared.container.viewContext
  let gemEntity = Gem.entity()
  let batchUpdateRequest = NSBatchUpdateRequest(entity: gemEntity)
  batchUpdateRequest.propertiesToUpdate = ["favorite": false]

  do {
    try viewContext.execute(batchUpdateRequest)
  } catch {
    print("Handle Error: \(error.localizedDescription)")
  }
}

這些方法和屬性的作用如下:

  • 1) 首先,聲明一個@State屬性showAlert,用于在用戶嘗試清除自己喜歡的寶石時顯示警報。
  • 2) 接下來,聲明appVersion屬性,該屬性從應(yīng)用程序捆綁包中的CFBundleShortVersionString檢索此屬性。
  • 3) 創(chuàng)建一種方法,當(dāng)用戶單擊Clear Favorites時顯示alert
  • 4) 最后,聲明從Core Data中清除喜歡的寶石的方法。

接下來,用以下代碼替換body的內(nèi)容:

ScrollView {
  VStack {
    Text("Settings")
      .font(.largeTitle)
      .frame(maxWidth: .infinity, alignment: .leading)
      .padding()
    Image("rw-logo")
      .resizable()
      .aspectRatio(contentMode: .fill)
      .frame(width: 400, height: 400)
    Text("RayGem")
      .font(.largeTitle)
    Text("Gem Version: \(appVersion)")
    Section {
      Button(action: showClearAlert) {
        Label("Clear Favorites", systemImage: "trash")
      }
    }
  }
  .frame(width: 600, height: 600)
  .alert(isPresented: $showAlert) {
    Alert(
      title: Text("Are you sure?")
        .font(.title)
        .foregroundColor(.red),
      message: Text("This action cannot be undone."),
      primaryButton: .cancel(),
      secondaryButton: .destructive(
        Text("Clear"),
        action: clearFavorites))
  }
}

您可以在此處創(chuàng)建帶有標(biāo)題,應(yīng)用程序圖標(biāo),應(yīng)用程序名稱和Clear Favorites按鈕的視圖body。 當(dāng)用戶嘗試清除自己喜歡的寶石時,您還會顯示一條alert,以免意外刪除所有喜歡的寶石。

接下來,打開AppMain.swift并找到注釋// TODO: Add Settings view here。 將此代碼添加到注釋下方:

// 1
#if os(macOS)
// 2
Settings {
  // 3
  SettingsView()
}
#endif

這是這樣做的:

  • 1) 使用#if os預(yù)處理程序指令測試當(dāng)前平臺是否為macOS。 這樣可以確保此代碼僅針對該應(yīng)用程序的macOS版本進(jìn)行編譯。 您可以在應(yīng)用程序中的任何位置添加這些檢查,以添加特定于平臺的代碼。 您可以檢查的其他值包括iOStvOSwatchOS
  • 2) 創(chuàng)建Settings類型的第二個scene
  • 3) 將SettingsView添加到該scene

構(gòu)建并運行。 使用鍵盤快捷鍵Command-,打開Preferences視圖。 向下滾動并按Clear Favorites

您的macOS應(yīng)用程序現(xiàn)在具有一個單獨的平臺特定的首選項窗口!

2. Adding a Keyboard Shortcut

macOSiPadOS上的另一個很酷的功能是執(zhí)行操作的鍵盤快捷鍵(keyboard shortcuts)。 用戶可以使用鍵盤快捷鍵來代替打開Preferences,向下滾動并單擊按鈕以清除其收藏夾。

Model組中創(chuàng)建一個新的Swift文件,然后選擇兩個目標(biāo)targets。 將其命名為GemCommands.swift并將以下代碼添加到文件中:

import SwiftUI
import CoreData

// 1
struct GemCommands: Commands {
  var body: some Commands {
    // 2
    CommandMenu("Gems") {
      Button(action: clearFavorites) {
        Label("Clear Favorites", systemImage: "trash")
      }
      // 3
      .keyboardShortcut("C", modifiers: [.command, .shift])
    }
  }

  // 4
  func clearFavorites() {
    let viewContext = PersistenceController.shared.container.viewContext
    let batchUpdateRequest = NSBatchUpdateRequest(entity: Gem.entity())
    batchUpdateRequest.propertiesToUpdate = ["favorite": false]
    do {
      try viewContext.execute(batchUpdateRequest)
    } catch {
      print("Handle Error: \(error.localizedDescription)")
    }
  }
}

代碼是這樣的:

  • 1) 定義一個遵循Commands協(xié)議的新類型。 與View協(xié)議一樣,此協(xié)議要求您實現(xiàn)某些命令的body屬性。
  • 2) 使用CommandMenu在狀態(tài)欄上定義菜單。 在CommandMenu內(nèi)部,您可以定義一個按鈕來清除收藏夾。
  • 3) 在按鈕上添加修飾符可添加鍵盤快捷鍵以執(zhí)行該操作。 keyboardShortcut(_:modifiers :)具有兩個參數(shù):鍵盤快捷方式的StringEventModifiersOptionSet組合以觸發(fā)此快捷方式。
  • 4) 定義清除收藏夾的方法。

返回AppMain.swift并在WindowGroup下添加以下代碼:

.commands { GemCommands() }

此修改器將命令添加到場景。 在macOS上,它將命令添加到狀態(tài)欄。

macOS上構(gòu)建并運行。 收藏一些寶石,并使用鍵盤快捷鍵Command-Shift-C清除它們。

做得好!

在本教程中,您學(xué)習(xí)了SwiftUI如何使創(chuàng)建可在iOS,iPadOSmacOS上運行的多平臺應(yīng)用程序變得更加容易。 不僅如此,您還學(xué)習(xí)了如何在兩個平臺上重用視圖,以及如何在同一項目中為每個平臺創(chuàng)建自定義行為和UI。

蘋果公司承諾創(chuàng)建一個可在其所有平臺上運行的框架的承諾并沒有到此為止。 使用此項目,您可以創(chuàng)建應(yīng)用程序的watchOStvOS版本,同時共享相同的代碼庫。

要更深入地了解SwiftUI的擴(kuò)展以及AppScenesViews如何組合在一起,請從WWDC 2020開始觀看App essentials in SwiftUI。如果您想繼續(xù)探索,本課程鏈接到一些相關(guān)視頻。

后記

本篇主要講述了基于SwiftUI和Xcode12的Multiplatform App的搭建,感興趣的給個贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容