精通 SwiftUI - iOS 16 版

第 1 章
SwiftUI 的介绍

2019 年的 WWDC,Apple 宣布了一个全新的框架(Framework ),称为“SwiftUI”, 这让所有的开发者都大为惊讶。这不只改变了开发 iOS App 的方式,也是自从 Swift 问世以来,Apple 开发者的生态系统(包括iPadOS、macOS、tvOS 与 watchOS)的最大转变。

SwiftUI 以一种创新且极度简单的方式,通过 Swift 的力量,让使用者建立横跨所有 Apple 平台的使用者介面。只要一套工具与 API,即能建立适用所有 Apple 装置的使用者介面。

- Apple (https://developer.apple.com/xcode/swiftui/)

开发者已经对于“应该使用故事板(Storyboard )或是以程序化的方式来建立 App UI”一事争论已久,而 SwiftUI 的导入,即是Apple 对这个问题的解答。有了这个全新的框架, Apple 提供开发者一个建立使用者介面的新方式。请参见图 1.1,并看一下它的程序。

图 1.1 SwiftUI 的程序
图 1.1 SwiftUI 的程序

自 SwiftUI 推出之后,你可以使用声明式(Declarative )的 Swift 语法来开发 App 的 UI,这表示这个 UI 程序的语法更容易且自然。和目前的UI 框架做比较,如 UIKit,你可以使用较少的代码来建立相同的UI。

预览功能一直是 Xcode 的弱点,虽然你可以在介面建构器( Interface Builder )预览简单的布局,不过通常无法预览完整的 UI,除非 App 被载入模拟器( Simulator )才行。有了 SwiftUI,一边写程序,一边就可以立即看到 UI 的成果。举例而言,你在表格中加入一笔新纪录,Xcode 会立即在预览画布(Preview Canvas)上渲染(Render)UI 的改变结果。如果你想在深色模式(dark mode)预览你的 UI,你只要修改一个选项即可,这个即时预览功能让 UI 的开发更加容易,且修改的速度更快。

不只可以预览 UI,新的画布也可以让你使用视觉拖曳的方式来设计使用者介面。最棒的是,当你以视觉方式加入UI 组件时,Xcode 会自动地产生 SwiftUI 程序。程序与UI 总是同步的,这是 Apple 开发者期待以久的功能。

在本书中,你将会深入 SwiftUI,学习如何布局内建组件,并以这个框架建立复杂的 UI。我知道你们之中已经有iOS 开发经验,让我先来告知你,目前你所使用的框架(如UIKit)与 SwiftUI 的主要差异性。如果你完全是一个新的 iOS 开发者,甚至没有任何程序经验,你可以先将这作为参考,或者跳过 1.1 小节,我不想要因此而让你远离SwiftUI, 因为对于初学者来说,这是一个非常棒的框架。

声明式程序设计与指令式程序设计

和Java、C++、PHP 与 C# 一样, 自从 Swift 释出以来, 一直是一个指令式程序( Imperative Programming )语言。不过,SwiftUI 很自豪的声称是一个声明式 UI 框架( Declarative UI Framework ),可以让开发者以声明式的方式来建立 UI。但是这个“声明式”(declarative )的意思是什么呢?又和指令式程序设计有何不同?更重要的是,这会对你写程序的方式有什么改变?

如果你才刚开始学习程序。你可能不需要去管这之间的差异为何,因为对你来说,一切都是新的内容。不过,如果你有些物件导向的程序经验,或之前曾以 UIKit 开发过,这个转变模式影响了你如何建立使用者介面的思考方式。你可能需要忘记一些旧思维来学习新概念。

那么,指令式程序设计与声明式程序设计的不同之处为何呢?如果你至维基百科搜寻这两个专有名词,你会找到这些定义:

在电脑科学中,指令式程序设计是一种使用了叙述语句来修改程序状态的程序设计典范(Programming Paradigm )。就像在自然语言中表达祈使句指令一样的方式。指令式程序设计包含了许多让电脑能够运行的指令。

在电脑科学中,声明式程序设计是一种建构电脑程序的结构与组件风格的程序设计典范,没有叙述控制流程来表达运算逻辑。

如果你没有学过电脑科学的话,非常难了解其中的差异性。让我以不同的方式来做解释。

这里不将重点放在程序设计,我们来谈一下披萨的烹饪(或任何你喜欢的料理)。假设你要求你的帮手去准备一个披萨,你可以使用指令式或者声明式的方式来做。要以指令式烹饪披萨,你需要很清楚地告诉你的帮手每一个指示,就像食谱一样:

  1. 加热到 550 ℉或更高,至少要 30 分钟。
  2. 准备一磅面团。
  3. 将面团揉成 10 英吋大小的圆。
  4. 将蕃茄酱以汤匙舀入披萨的中间,并均匀涂抹至边缘。
  5. 再撒上一些配料(包括洋葱、切片蘑菇、义式辣肉肠( pepperoni)、煮过的香肠、煮过的培根、切细的辣椒)与起司。
  6. 将披萨烘烤 5 分钟。

此外,如果你是以声明式的方式,你不需要订定所有的步骤,只要描述你想要做什么样的披萨,厚皮或者薄皮?要义式辣肉肠与培根,或只是经典的玛格莉特( margherita ) 加上番茄酱?10 吋或者16 吋?这个帮手将会处理剩下的事,并为你烘烤披萨。

这是指令式与声明式的主要不同之处。现在回到 UI 程序,指令式 UI 程序设计需要开发者写下细节的指示,来布局 UI 以及控制其状态。反之,声明式UI 程序设计让开发者描述 UI 的样貌,以及状态改变时你要它能做什么。

声明式的程序风格可以让程序更容易阅读与理解。更重要的是,SwiftUI 框架可以让你以很少的代码,来建立一个使用者介面。例如:你准备要在 App 中建立一个心形按钮, 这个按钮应该放置于屏幕中心,并且能够侦测触控事件,当使用者点击这个心形按钮时, 它的颜色会从红色变为黄色,而当使用者按下这个心形不放时,它会以动画形式放大。

图 1.2. 心形互动按钮的实现
图 1.2. 心形互动按钮的实现

参考一下图 1.2,此为实现心形按钮所需要的程序,大约 20 行的程序,你就可以建立一个互动式按钮,并加上一个缩放动画,而这就是声明式 UI 框架的威力之处。

不再是介面建构器与自动布局

在 Xcode 11 以上版本,你可以选取 SwiftUI 与 Storyboard 来建构使用者介面,如图 1.3 所示。如果你之前已建立过App,你可能使用介面建构器在故事板上布局 UI,但是有了 SwiftUI, 介面建构器与故事板则完全消失。它会被程序编辑器与预览画布所取代,如图 1.2 所示。你需要将程序写在程序编辑器,然后 Xcode 会即时渲染使用者介面,并将其显示在画布中。

图 1.3. 在 Xcode 中的使用者介面选项
图 1.3. 在 Xcode 中的使用者介面选项

“自动布局”( Auto Layout)一直是学习iOS 开发中的一个困难的主题。有了 SwiftUI, 你不再需要学习如何定义一个布局约束条件,并解决布局的冲突问题。现在只要使用堆叠( Stack )、间隔( Spacer )与间距( Padding ),来组成想设计的UI 即可。我们将在后面的章节讨论这些概念。

Combine 方法

除了故事板之外,视图控制器( View Controller )也不见了。对于新手,你可以忽略什么是视图控制器。若你是有经验的开发者,你会发现一件奇怪的事,即 SwiftUI 没有使用一个视图控制器来当作一个中心建构区块,以作为视图与模型间的沟通。

视图之间的沟通与数据分享,现在是通过另外一个称为“Combine”(合并)的新框架来进行。这个新的方法完全取代了在UIKit 中视图控制器的角色。在本书中,我们将会说明 Combine 的基本概念,学习如何使用它来处理UI 事件。

学一次到处适用

虽然本书将重点放在 iOS UI 的建立,但你在这里所学到的内容,也可以应用在其他 Apple 平台,例如:watchOS。在 SwiftUI 推出之前,你需要使用平台特定的 UI 框架来开发使用者介面,例如:使用 AppKit 来撰写macOS App 的 UI;要开发 tvOS App,则依赖 TVUIKit;而开发 watchOS App,则是使用 WatchKit。

SwiftUI 推出之后,Apple 提供开发者一个统一的 UI 框架,来建立所有 Apple 装置的使用者介面。为 iOS 所撰写的UI 代码,可以轻易地移植到你的 watchOS/macOS/watchOS App,而不需要修改,或只要做小幅度的修正即可,这要归功于“声明式 UI 框架”。

使用者介面的程序叙述如下。依照平台,在 SwiftUI 中的同一段程序,可以产生不同的 UI 控制。举例而言,以下这段程序为声明一个切换开关:

Toggle(isOn: $isOn) {
    Text("Wifi")
        .font(.system(.title))
        .bold()
}.padding()

对于 iOS 与 iPadOS,Toggle 会渲染为开关。而 macOS ,SwiftUI 会将这个控制组件(control )渲染为核取方块样式,如图1.4 所示。

图 1.4. macOS 与iOS 的切换开关
图 1.4. macOS 与iOS 的切换开关

这个统一框架的美妙之处,即是你可以针对所有 Apple 平台重复使用大部分的程序,而无需修改。SwiftUI 协助渲染对应的控制与布局等这些繁重的工作。

不过,不要认为 SwiftUI 是一个“写一次,到处可以运行”的解决方案。如同Apple 在 WWDC 所强调的,这不是SwiftUI 的目标。因此,不要期望你可以完美地将美丽的 iOS App 转换成 tvOS App,而不需要做任何的修改。

只要是合适的地方,一定是有机会共享相同的代码。重要的是,我们不能将 SwiftUI 想成写一次,到处运行,而是学习一次,到处适用。

- WWDC 讲座(SwiftUI 于所有装置)

虽然 UI 程序是可以跨平台,对于特定类型的装置,你依然需要提供对应的规格。你应该要常常检查你的 App 的每一个版本,以确保对某个平台是正确的。这么说好了, SwiftUI 已经省下了你学习另外一个平台专用框架的许多时间,而且你可以重用大部分的代码。

UIKit/AppKit/WatchKit 的介接

我可以在我目前的项目中使用 SwiftUI 吗?我不想要重写整个以 UIKit 所建立的 App。

SwiftUI 可以设计用于目前的框架如 iOS 的 UIKit 与 macOS 的 Appkit。为了能够将视图或控制器加入 SwiftUI 中,Apple 提供几个代表性(representable)的协议供你采用,如图 1.5 所示。

图 1.5. 目前 UI 框架的代表性协议
图 1.5. 目前 UI 框架的代表性协议

例如:若你有一个自订的视图是使用 UIKit 开发,你可以采用 UIViewRepresentable 协议,来让它相容 SwiftUI。图 1.6 是在 SwiftUI 中使用 WKWebView 的示例程序。

图 1.6. 移植 WKWebView 至 SwiftUI
图 1.6. 移植 WKWebView 至 SwiftUI

下一个项目改采SwiftUI 吧

每当一个新框架释出后,通常就会有人问:“这个框架是否可以用在我下一个项目中,是否应再观望一些时间?”

虽然 SwiftUI 对大部分开发者而言依然是全新的,这也正是学习并整合这个框架进入你的新项目的好时机。随着 Xcode 14 释出,经过 Apple 改版后的 SwiftUI 框架已经更加稳定,功能也更多。如果你有一些个人的项目或者是你的公司一些业余项目( Side Project )在进行,我想没有什么理由不尝试一下 SwiftUI。

必须这样说,你需要仔细考虑是否要在你的商业性项目中应用SwiftUI。SwiftUI 的一个主要缺点是只能在运行 iOS 13、macOS 10.15、tvOS 13 与 watchOS 6 版本以上的装置才能运行。如果你的 App 仍需要支持比较旧版本(例如 iOS 12)的平台,这种情况下,可能需要等待一下,再来采用 SwiftUI。

在撰写本书时,SwiftUI 已经超过三年,Xcode 14 已经为 SwiftUI 增加了更多的 UI 控制与新的 API(例如 Charts),就功能而言,你无法和目前释出多年的UI 框架(如 UIKit )相比。一些功能(例如:使用照相机)很明显就只有旧框架有,而无法在 SwiftUI 取得,你可能需要开发一些解决方案来处理这些问题。在已经上市的产品采用 SwiftUI,需要再多做一点考量。

无疑的,SwiftUI 还是很新的,需要一些时间才能变成一个成熟的框架,但很明确的, SwiftUI 是 Apple 平台应用程序开发的未来,即使目前可能还无法应用在你的项目中。建议可从一些业余的产品来开始尝试,并摸索这个框架,当你更加熟练之后,你将会喜爱以声明式来开发 UI。