SwiftUI框架详细解析 (二十六) —— 基于SwiftUI和Xcode12的Multiplatform App的搭建(一)

版本记录

版本号 时间
V1.0 2021.03.21 星期日

前言

今天翻阅苹果的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教程(二)
20. SwiftUI框架详细解析 (二十) —— 基于SwiftUI的Document-Based App的创建(一)
21. SwiftUI框架详细解析 (二十一) —— 基于SwiftUI的Document-Based App的创建(二)
22. SwiftUI框架详细解析 (二十二) —— 基于SwiftUI的AWS AppSync框架的使用(一)
23. SwiftUI框架详细解析 (二十三) —— 基于SwiftUI的AWS AppSync框架的使用(二)
24. SwiftUI框架详细解析 (二十四) —— 基于SwiftUI的编辑占位符的使用(一)
25. SwiftUI框架详细解析 (二十五) —— 基于SwiftUI的编辑占位符的使用(二)

开始

首先看下主要内容:

了解如何使用Xcode 12的多平台应用程序模板和SwiftUI编写可在每个Apple平台上运行的单个应用程序。内容来自翻译

下面看下写作环境:

Swift 5, iOS 14, Xcode 12

接着就是正文啦。

Mac Catalyst发布以来,苹果一直在为让iOS开发人员将其应用程序带到Mac铺平道路。扩展到SwiftUI(Apple建立用户界面的一种简单的声明式方法),现在您可以使用它来构建整个应用程序。这些扩展以及Xcode 12中新增的多平台应用程序模板(multiplatform app template),使您可以使用一个代码库为每个Apple平台构建应用程序。

在本教程中,您将了解:

  • Xcode 12的多平台应用程序模板(multiplatform app template)
  • App协议
  • AppScenesViews如何组合在一起
  • 相同代码适应每个平台的方式
  • 如何在重用视图时为每个平台创建自定义UI

您将通过为RayGem添加新功能(适用于iOSiPadOSmacOS的应用程序来显示有关不同宝石的信息)来学习所有这些内容。

注意:本教程假定您熟悉SwiftUI。如果您只是入门,请查看SwiftUI: Getting StartedRayGem还利用Core Data。尽管您不需要深入了解Core Data,但如果您想了解更多信息,这是一个很好的起点: Core Data with SwiftUI Tutorial: Getting Started

打开项目材料。 在启动文件夹中打开RayGem.xcodeproj。 构建并运行。

RayGem是一个简单的多平台应用程序,其中列出了宝石(宝石或半宝石)的集合。 用户可以阅读有关他们的有趣事实,并保存他们的收藏夹。

您已经可以滚动并点击宝石以阅读有关每个宝石的事实。 该应用程序具有从Core Data获取和保存喜欢的宝石的代码,但是它既不能保存也不能列出喜欢的宝石。 您将在本教程中添加此功能。

在启动程序项目中打开不同的视图,以熟悉该应用程序。 该应用程序的主视图位于GemList.swift中,显示了从Core Data存储中获取的GemRow.swift中找到的行的列表。 通过点击一行,您可以导航到DetailsView.swift中的详细信息视图。

1. Considering the Project Structure

在开始进行任何更改之前,请看一下启动项目。 请注意,这些组与您通常的iOS入门项目中的组有何不同。

创建新项目时,Xcode 12有一个名为Multiplatform的新部分。 在其中,您会找到适用于多平台应用程序的新模板。 本教程的入门项目是使用此模板构建的。

此模板创建三个组:

  • iOSiOS专用代码
  • macOSmacOS专用代码
  • Shared:两个平台的代码,包括模型,业务逻辑和可重用视图

SwiftUI允许您在平台之间共享UI代码,并根据设备自动调整UI。 您可以创建可在每个平台上重用的视图,但是某些行为更适合某些平台。 这样,每个平台都有一个组,可以让您为每个平台编写特定的代码,同时仍可以重用许多代码。


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协议,该协议处理应用程序的生命周期,并取代了旧的AppDelegate.swiftSceneDelegate.swift

View协议非常相似,App协议要求您通过返回Scene来实现主体body。主体将成为您应用程序的根视图。通常,您会返回一个WindowGroup,这是一种特殊的Scene类型,它是应用程序视图层次结构的容器。 SwiftUIWindowGroup共同负责在每个平台上以不同的方式呈现您的应用程序。例如,在macOS上,WindowGroups将在其菜单栏中自动具有窗口管理选项,并支持在选项卡中收集应用程序的所有窗口。

Swift 5.3引入了新的@main属性,以指示应用程序的入口点。通过将此属性添加到实现App的结构中,SwiftUI将以该结构为应用程序的起点。


Running on macOS

该应用程序已经具有可在iOS上运行的基本功能,但是macOS呢?将targetRayGem(iOS)更改为RayGem(macOS),然后生成并再次运行。

该应用程序不包含任何iOSmacOS特定代码,它们都是使用普通的旧版SwiftUI构建的,就像您构建其他任何应用程序一样。 但是,您仍然可以在iOSmacOS上运行您的应用程序! 那不是很酷吗?

使用新的Multiplatform应用程序模板,Xcode为您的应用程序创建了两个targets:一个用于在iOSiPadOS上运行,另一个用于在macOS上运行。 它使用这些target在每个相应平台上运行您的应用程序。

1. Understanding how SwiftUI Adapts to the Platform

请注意,SwiftUI如何针对每个平台调整UI。 在iOS上,它使用带有列表的导航视图。 当用户点击一行时,它将推送该行的目标视图。 在macOS上,它使用带有侧边列表和内容视图的窗口。 当用户单击一行时,内容视图将更新为该行的目标视图。

现在,将target切换回RayGem(iOS),并在iPad模拟器上构建并运行。

当应用程序在iPadOS上运行时,列表将隐藏在视图的左侧,而当用户选择gem时,主视图将显示目标视图。


Polishing the macOS app

UI已经适应了不同的设备大小,甚至可以在macOS上调整窗口大小,但是有时候,您可能希望在某些设备上添加一些限制,同时在其他设备上保持相同的行为。 值得庆幸的是,SwiftUI包含许多修饰符,以影响它如何将视图适应不同的平台。 告诉SwiftUI如何处理您的应用,可以使您的应用成为更好的macOS成员。

1. Adding a minimum width to your list

现在,您可以在macOS上调整gem侧面列表的大小,并将其缩小为零。

这是一种行为,可能会使某些用户感到困惑。 SwiftUI为您提供了修饰符来处理这种情况,而无需为每个平台编写特定的代码。

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

.frame(minWidth: 250)

构建并在iPhone模拟器上运行以查看结果。

此修饰符将最小宽度添加到列表中。 在iOS上,这可能没有多大意义,因为列表将使用视图的整个宽度。 但是,在macOS上,此修饰符可确保列表List保持最小宽度为250点,同时仍允许用户调整其大小。

target更改为RayGem(macOS),然后构建并再次运行。 尝试调整列表的大小。

请注意,边列表仍可以调整大小,但始终保持超过250点的宽度。

2. Adding a navigation title

仍在GemList.swift中,请注意列表底部的修饰符navigationTitle(_ :)。 这是iOS 14上引入的新修饰符,用于配置视图的标题。 在iOSwatchOS上,它将使用字符串作为导航视图的标题。 iPadOS将设置主导航视图标题和应用程序切换器中标题。 这对于区分您的应用实例很重要。 在macOS上,窗口标题栏和Mission Control使用此字符串作为标题。


Working With Toolbars

现在,该让用户拥有保存自己喜欢的宝石的能力了。

DetailsView.swift中,将以下内容添加到视图的底部:

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

此方法切换当前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)
    }
  }
}

这是代码的细分:

  • 1) iOS 14引入了新的视图修饰符:toolbar(content:)。 此修饰符采用表示toolbar内容的ToolbarItem
  • 2) 使用一个按钮添加一个ToolbarItem,以在gem上切换favorite
  • 3) 接下来,添加Label作为按钮的内容,标题为“Favorite” or “Unfavorite”,并显示心形图像。

构建并运行。 然后,收藏一颗宝石。

现在,在macOS上构建并运行。 喜欢一个宝石可以看到结果。

SwiftUI使用ToolbarItem并将其放置在每个平台的预期位置。 在iOS上,它遵循bar的颜色方案,将Label的图像用作导航栏上的按钮。 在macOS上,它也使用Label的图像。 但是,如果您调整窗口的大小并且在工具栏上的按钮上没有任何空间,它将创建带有Label标题的菜单按钮。

将窗口调整为可能的最小宽度。

SwiftUI会针对每个平台调整UI,即使在调整窗口大小时也找到了显示按钮的最佳方法。


Understanding tab views on different platforms

现在用户可以收藏自己的宝石了,可以列出这些收藏夹了。

入门项目已经附带了此代码,即FavoriteGems视图。 该视图获取并列出所有将favorite属性设置为true的宝石。

打开ContentView.swift并将以下枚举添加到文件顶部:

enum NavigationItem {
  case all
  case favorites
}

该枚举描述了应用程序的两个选项卡。 接下来,通过将body的内容替换为以下内容来添加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) 首先,创建一个TabView作为根视图。
  • 2) 接下来,添加GemList作为其第一个视图,并带有一个标题为“All”Label和列表项目符号的图像。
  • 3) 添加FavoriteGems作为第二个视图,并带有一个标题为Favorites和小心图像的Label

iOS上构建并运行。 收藏一些宝石,然后打开Favorites选项卡以查看其中列出的宝石。

接下来,将target更改为macOS。 构建并运行以查看SwiftUI如何适配macOS上的UI

极好的! 您已经有一个可以在iOSmacOS上运行的简单应用程序! 花一点时间享受您到目前为止所取得的成就。


Optimizing the User Experience for Each Platform

SwiftUI尝试使代码中声明的UI适应每个平台。 iOS上的TabBar在底部具有其栏,图像和文本作为按钮。 在macOS上,它使用带有标题的视图顶部的栏,非常类似于segmented view

即使SwiftUI可以在每个平台上调整用户界面,但这并不意味着它总是能够创建用户期望的内容。 与在macOS上使用TabBar相比,更好的布局是带有类别列表的Sidebar。 然后,一个列表将显示所选类别的每个元素。

您的应用程序已经可以在两个平台上运行,但是用户希望在任何地方都能获得最佳体验。 值得庆幸的是,Apple添加了一种在多平台应用程序中创建特定于平台的视图的方法。 这正是Xcode中的macOSiOS组的用途! 您现在将更新应用程序中的tab bar,以将边栏sidebar布局用于macOS

1. Updating the macOS UI

macOS组内创建一个新的SwiftUI View文件,并将其命名为GemListViewer.swift。 仅选择macOS target membership

首先,向视图添加新的属性和方法:

@State var selection: NavigationItem? = .all

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

这是一个状态变量,您将使用侧边栏中的当前所选类别进行更新:所有宝石或仅收藏的宝石。 当用户单击按钮时,toggleSideBar()将显示或隐藏侧边栏; 您会稍等一下。

接下来,将以下计算的属性添加到视图中:

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

您将创建一个侧边栏sidebar视图,其中包含一个包含两个NavigationLinksList —— 一个用于GemList,一个用于FavoriteGems。 通过使用SidebarListStyle,您可以告诉SwiftUI将此List显示为侧边栏sidebar,以便用户选择要查看的类别。 您还可以在工具栏内创建一个ToolbarItem,并带有一个按钮来切换侧栏sidebarmacOS应用中的预期行为是可以隐藏和显示侧边栏。

接下来,将body的内容替换为以下内容:

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

这将显示sidebar以及一些文本。

最后,将previews的内容替换为以下内容:

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

您已经完成了macOS UI的操作,但是暂时还看不到它。 首先,您将转到iOS UI。

2. Updating the iOS UI

iOS组中创建另一个SwiftUI视图,并将其命名为GemListViewer.swift。 这次,请确保仅选择iOS target

将视图的body替换为以下内容:

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

这是上面的代码中发生的事情:

  • 1) 将TabView声明为根视图,但这一次,仅适用于iOS
  • 2) 将GemList添加为第一个视图。
  • 3) 接下来,添加FavoriteGems作为第二个视图。

请注意,这正是您当前在**ContentView.swift**中拥有的代码。

接下来,用以下代码替换previews的内容:

Group {
  GemListViewer()

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

您设置了默认情况下将使用iPhone布局的预览,然后添加了指定了iPad Air 2布局的第二个预览。

最后,再次打开ContentView.swift,并用以下单行代码替换body的所有内容:

GemListViewer()

构建并运行iOS target

现在,构建并运行macOS target

ContentView是两个平台之间的共享视图。但是,您的项目有两个GemListViewer,但一个仅包含在macOS target中,另一个仅包含在iOS target中。通过这种设置,您的应用将为每个平台使用正确的GemListViewer。这提供了在每个平台上重用应用程序的核心视图的可能性,但也允许在每个平台上使用自定义行为和UI。每个平台具有相同视图的不同版本,可让您访问特定于平台的API和功能,例如iOS InsetGroupedListStylemacOS侧栏切换。


Understanding Independent Scenes and View States

SwiftUI已经捆绑了许多多平台应用程序功能。使用WindowGroup,可以在iPadOS上添加对应用程序的多个实例的支持,而在macOS上可以支持多个窗口。它甚至添加了用于在macOS上打开新窗口的通用键盘快捷键Command-N

macOS上构建并运行。选择一个宝石,然后使用File ▸ New Window打开一个新窗口。或者,使用键盘快捷键Command-N

请注意,使用一个窗口不会影响另一个窗口的状态。 旧窗口保持其状态,显示您选择的宝石。 同时,新窗口的行为类似于应用程序的新实例,独立于原始实例。

每个场景都会处理应用程序的状态并更新其视图,但不会直接影响另一个场景。

SwiftUImacOS带来的另一个共同功能是能够将所有windows合并到选项卡tabs中。 转到Window ▸ Merge All Windows

每个选项卡的行为都不同,具有各自的状态。


Adding Extra Functionality for macOS

大多数macOS应用程序的常见功能是Preferences…菜单命令。 用户将希望能够通过转到RayGem ▸ Preferences或使用Command-快捷方式来更改设置。

您将在macOS版本中添加一个简单的设置视图,以便用户查看有关该应用程序的有用信息,例如版本号和清除他们喜欢的宝石的按钮。

1. Creating the Preferences View

Views组内创建一个新的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属性,该属性从应用程序捆绑包中的CFBundleShortVersionString检索此属性。
  • 3) 创建一种方法,当用户单击Clear Favorites时显示alert
  • 4) 最后,声明从Core Data中清除喜欢的宝石的方法。

接下来,用以下代码替换body的内容:

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

您可以在此处创建带有标题,应用程序图标,应用程序名称和Clear Favorites按钮的视图body。 当用户尝试清除自己喜欢的宝石时,您还会显示一条alert,以免意外删除所有喜欢的宝石。

接下来,打开AppMain.swift并找到注释// TODO: Add Settings view here。 将此代码添加到注释下方:

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

这是这样做的:

  • 1) 使用#if os预处理程序指令测试当前平台是否为macOS。 这样可以确保此代码仅针对该应用程序的macOS版本进行编译。 您可以在应用程序中的任何位置添加这些检查,以添加特定于平台的代码。 您可以检查的其他值包括iOStvOSwatchOS
  • 2) 创建Settings类型的第二个scene
  • 3) 将SettingsView添加到该scene

构建并运行。 使用键盘快捷键Command-,打开Preferences视图。 向下滚动并按Clear Favorites

您的macOS应用程序现在具有一个单独的平台特定的首选项窗口!

2. Adding a Keyboard Shortcut

macOSiPadOS上的另一个很酷的功能是执行操作的键盘快捷键(keyboard shortcuts)。 用户可以使用键盘快捷键来代替打开Preferences,向下滚动并单击按钮以清除其收藏夹。

Model组中创建一个新的Swift文件,然后选择两个目标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协议的新类型。 与View协议一样,此协议要求您实现某些命令的body属性。
  • 2) 使用CommandMenu在状态栏上定义菜单。 在CommandMenu内部,您可以定义一个按钮来清除收藏夹。
  • 3) 在按钮上添加修饰符可添加键盘快捷键以执行该操作。 keyboardShortcut(_:modifiers :)具有两个参数:键盘快捷方式的StringEventModifiersOptionSet组合以触发此快捷方式。
  • 4) 定义清除收藏夹的方法。

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

.commands { GemCommands() }

此修改器将命令添加到场景。 在macOS上,它将命令添加到状态栏。

macOS上构建并运行。 收藏一些宝石,并使用键盘快捷键Command-Shift-C清除它们。

做得好!

在本教程中,您学习了SwiftUI如何使创建可在iOS,iPadOSmacOS上运行的多平台应用程序变得更加容易。 不仅如此,您还学习了如何在两个平台上重用视图,以及如何在同一项目中为每个平台创建自定义行为和UI。

苹果公司承诺创建一个可在其所有平台上运行的框架的承诺并没有到此为止。 使用此项目,您可以创建应用程序的watchOStvOS版本,同时共享相同的代码库。

要更深入地了解SwiftUI的扩展以及AppScenesViews如何组合在一起,请从WWDC 2020开始观看App essentials in SwiftUI。如果您想继续探索,本课程链接到一些相关视频。

后记

本篇主要讲述了基于SwiftUI和Xcode12的Multiplatform App的搭建,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容