正常人谁用 Storyboard?!
$[timeformat('2021-09-12T16:31:49+08:00')]
#SwiftUI#iOS 开发#软件开发

如果你有为 Apple 的各种硬件开发过 app,那么你应该会有一个很明显的感受:Apple 的开发工具栈始终在努力朝向越来越简单的方向进化。从一开始的无内存管理的 Objective-C 到加入内存管理,再到加入多种脚本语言特性的 Swift,其中的变化自然是朝向越来越简单、越来越对新手友好的方向发展。

最新的 SwiftUI 也是这家公司在努力降低开发者上手门槛的努力之一。SwiftUI 在定位上与 Interface Builder(XIB)和 Storyboard(故事板)一样,都是构建用户界面的工具之一。不同之处在于,Apple 宣称 SwiftUI 采用 声明式语法,能够快速构建应用程序的用户界面。同时,由于 SwiftUI 本质就是 Swift 程序,因此,以往需要手动关联 XIB 和故事板的情况也在 SwiftUI 上一去不复返。

最近我做了一款服药提醒 app Alarmedic 以及一款转移 Nintendo Switch 截屏与录制的 Switshot(都暂时还没上架),采用的用户界面构建工具就是 SwiftUI。趁这个机会,来说说 SwiftUI 究竟能不能用,好不好用,要怎么用。

更接近前端框架的编辑风格

如果你是一名前端开发者,又碰巧用过 Vue.jsReact 这类框架或脚手架,那么你初次上手 SwiftUI 就会有一种非常熟悉的感觉。

SwiftUI 的编辑风格与这些前端框架非常接近:它们都将视图(view)看作类(class)和实例(object)的具象:只需要对常用的视图作为类进行架构,之后,想在页面或者屏幕上放这个视图,就直接将它丢上去就可以了;修改其中一个实例的视觉属性,也只需要对它们进行声明即可。

非常简单明了的 SwiftUI 代码:丢一个按钮上去,绑定一个事件。
非常简单明了的 SwiftUI 代码:丢一个按钮上去,绑定一个事件。

前面提到,SwiftUI 的视图本质上就是一个类,那么 SwiftUI 自然可以像 Vue.js 和 React 一样,带有状态(state)、事件和函数。与其他面向对象的编程语言一样,只需要简单地在视图 结构体(structure) 上加入变量和函数,就可以在使用视图的时候进行生成、调用。

这种通过类和实例的方式进行界面构建的逻辑,的确是 Apple 宣称的「声明式」开发风格——通过声明「界面上有什么东西」而不是教 app「界面应该怎么画」来完成用户界面构建和逻辑输入的工作。与 Storyboard 相比,SwiftUI 当然是一个巨大的进步:为了适配各种不同尺寸和分辨率的屏幕(窗口),Storyboard(和为了解决这个问题推出的 auto layout 系统)无时无刻不在要求开发者声明各种各样的 constraints(约束),一旦某个 constraint 不满足「位置」和「尺寸」其中之一的确定性,整个项目就会开始「报警」。

除了抛弃 constraints 这种反人类的设计范式之外,SwiftUI 还引入条件渲染和循环渲染(列表渲染)两种新特性。你可以通过 Swift 代码确定哪些元素应该显示、哪些不应该显示,也可以通过 Swift 来渲染一个数组(列表)中的项目。

逻辑与特性

虽然 SwiftUI 的整体思想和前端框架殊途同归,但 SwiftUI 毕竟不是 HTML 和 JavaScript,本身脱胎于 Swift 的 SwiftUI 自然有自己的特性。

最大的一个差异在于 SwiftUI 并非是「盒状模型」。HTML 中的盒装模型允许我们自由嵌套和配置 (反正有 flex 帮忙擦屁股),但 SwiftUI 中,并不是所有的元素允许你进行嵌套(例如,用于渲染文字的 Text 元素就不是能够嵌套的容器元素);即使是容器元素,也并不一定能嵌套超过两个元素。

导航视图 NavigationView 就不是一个可嵌套多个元素的容器。
导航视图 NavigationView 就不是一个可嵌套多个元素的容器。

主要的、可同时容纳多个同级视觉元素的容器主要有 HStackVStack 以及 ZStack 三种,它们的区别在于:

  • HStack 会将元素以横向方式(Horizontal)进行排列。
  • VStack 会将元素以纵向方式(Vertical)进行排列。
  • ZStack 可以将元素在同一位置上并排放置,在程序和交互设计的世界中,我们通常会将屏幕纵深称作「Z 轴」,想必「Z」便是这么来的。

这三种容器间可以被互相嵌套——这一点很重要,你看到的许多元素,其实就是多种这样的容器叠加的结果。

HStack、VStack 和 ZStack 的效果。(ZStack 是叠加效果。)
HStack、VStack 和 ZStack 的效果。(ZStack 是叠加效果。)

另外,如果没有额外指定,那么在容器中的元素通常会优先被放置于中间。如果想要让它往边缘上靠,容器属性实际上并没有想象中那么靠谱,反倒利用 Spacer() 会相对更为靠谱。当 Spacer() 放置在尺寸固定的排列轴上且未指定尺寸时,它会占用除其他元素需要的占用空间之外的所有空间。

Spacer 会挤占排列轴上的所有空间。
Spacer 会挤占排列轴上的所有空间。

一个轴拥有多个(未指定尺寸的)Spacer() 的时候,每个 Spacer() 会动态占用 同等尺寸 的空间。(不太明白的话,打开访达 app,右键点击工具栏,选择自定义工具栏。多摆几个可调间距试试看——可调间距本质上就是 Spacer 组件。)利用这一特性,我们可以配合多个 Spacer() 打造更多样的界面。

Switshot 利用多个 Spacer 来保证元素排列在靠上的位置,不会「错位」。
Switshot 利用多个 Spacer 来保证元素排列在靠上的位置,不会「错位」。

了解完这些 SwiftUI 在元素排版与排列上的基本逻辑,你已经可以上手尝试利用 SwiftUI 来快速搭建你的 app 界面。你需要做的,就是了解你需要的视觉元素的名字,然后在文档(或者各大 Swift 社区)找到它对应的类名与调用方法,然后写进去。非常简单。

缺陷

作为一个新的 UI 开发工具,SwiftUI 继承来自前辈 Storyboard 的思想,并将软件开发过程与 UI 设计与实现实现强结合,让 app 开发门槛变得史无前例地低。但现阶段的一些缺点,也让 SwiftUI 无法成为完全理想的选择。

最大的缺陷就是缺功能。作为一个诞生于 Apple 的平台,许多已经在 Apple 各个设备上变得十分常见的特性,例如 iPhone 和 iPad 上的下拉刷新交互、不可滑动关闭的 sheet(弹出框)以及 Tab 中的 badge(通知小红点)等等,都是在 iOS 15 上才刚刚提供对应接口——截止这篇文章写出来的时候,iOS 15 都还没发布。另外还有诸多功能也是需要开源框架或是通过传统方式调用。

另一个比较大的限制就是 SwiftUI 似乎对手势等操作不太友好。在 SwiftUI 中,诸如 pinch 这样的手势操作需要利用 Swift 代码不断接收一个手势事件,但不知道是事件采样率不足还是其他的原因,SwiftUI 的元素会出现掉帧和不跟手的情况,显得性能很差的样子。不仅仅是我自己写会出现这样的问题,我利用其他人的开源框架中也会出现这样的情况,具体也不太清楚是我自己没有掌握这方面的诀窍,还是有其他的原因。

建议

即使对于我来说会有上面这些槽点,SwiftUI 也已经是一款能用而且足够好用的 UI 界面开发工具。如果你是刚开始接触 iOS 开发,那么 SwiftUI 是一款很好的入门工具,而且 Apple 显然还在对它进行迭代和更新,相信以后会逐渐加入各式各样 iOS 的交互等接口。

那么问题来了,怎么样学习 SwiftUI 会比较好呢?对于完全初学的人来说,请首先学习 Swift 编程语言,相信能看到这篇文章的人一定能找到相关的学习资料 :-);之后,Apple 官方其实已经有 官方 SwiftUI 教程,它可以帮助你对 SwiftUI 有一个基本的概念。

对于第三方的资料,由于 SwiftUI 刚刚走向成熟,许多问题还没有很好的解决方案。如果你需要深究有关 SwiftUI 的一些高阶用法,王禹效在少数派的付费教程《创作者的 iOS 独立开发指南》是一个不错的资料——在开发上面提到的两款 app 的时候,我也在大量参考这里面的内容。当然,根据你的需求提炼关键词,去 Google 上搜一下,绝大多数问题也会有解决方案,所以不需要过于在这方面担心。

感谢你看到这里!如果你喜欢这篇文章,请在下方为我鼓掌(Like)
这样做不仅可以让我获得实质性收入,更可在 liker.land 网站收到我的最新文章更新

评论