总览如果你想要入门 SwiftUI 的使用,那 Apple 这次给出的官方教程绝对给力。这个教程提供了非常详尽的步骤和说明,网页的交互也是一流,是觉得值得看和动手学习的参考。 不过,SwiftUI 中有一些值得注意的细节在教程里并没有太详细提及,也可能造成一些困惑。这篇文章以我的个人观点对教程的某些部分进行了补充说明,希望能在大家跟随教程学习 SwiftUI 的时候有点帮助。这篇文章的推荐阅读方式是,一边参照 SwiftUI 教程实际动手进行实现,一边在到达对应步骤时参照本文加深理解。在下面每段内容前我标注了对应的教程章节和链接,以供参考。 在开始学习 SwiftUI 之前,我们需要大致了解一个问题:为什么我们会需要一个新的 UI 框架。 为什么需要 SwiftUIUIKit 面临的挑战对于 Swift 开发者来说,昨天的 WWDC 19 首日 Keynote 和 Platforms State of the Union 上最引人注目的内容自然是 SwiftUI 的公布了。从 iOS SDK 2.0 开始,UIKit 已经伴随广大 iOS 开发者经历了接近十年的风风雨雨。UIKit 的思想继承了成熟的 AppKit 和 MVC,在初出时,为 iOS 开发者提供了良好的学习曲线。 UIKit 提供的是一套符合直觉的,基于控制流的命令式的编程方式。最主要的思想是在确保 View 或者 View Controller 生命周期以及用户交互时,相应的方法 (比如 声明式的界面开发方式近年来,随着编程技术和思想的进步,使用声明式或者函数式的方式来进行界面开发,已经越来越被接受并逐渐成为主流。最早的思想大概是来源于 Elm,之后这套方式被 React 和 Flutter 采用,这一点上 SwiftUI 也几乎与它们一致。总结起来,这些 UI 框架都遵循以下步骤和原则:
SwiftUI 的思想也完全一样,而且实际处理也不外乎这几个步骤。使用描述方式开发,大幅减少了在 app 开发者层面上出现问题的机率。 一些细节解读官方教程中对声明式 UI 的编程思想有深刻的体现。另外,SwiftUI 中也采用了非常多 Swift 5.1 的新特性,会让习惯了 Swift 4 或者 5 的开发者“耳目一新”。接下来,我会分几个话题,对官方教程的一些地方进行解释和探索。 教程 1 - Creating and Combining ViewsSection 1 - Step 3: SwiftUI app 的启动创建 app 之后第一件好奇的事情是,SwiftUI app 是怎么启动的。 教程示例 app 在 AppDelegate 中通过 func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } 这个名字的 Configuration 在 Info.plist 的 “UIApplicationSceneManifest -> UISceneConfigurations” 中进行了定义,指定了 Scene Session Delegate 类为 func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIHostingController(rootView: ContentView()) self.window = window window.makeKeyAndVisible() } 这部分内容就是标准的 iOS app 启动流程了。 由于 Swift ABI 已经稳定,SwiftUI 是一个搭载在用户 iOS 系统上的 Swift 框架。因此它的最低支持的版本是 iOS 13,可能想要在实际项目中使用,还需要等待一两年时间。 Section 1 - Step 4: 关于 some Viewstruct ContentView: View { var body: some View { Text("Hello World") } } 一眼看上去可能会对
public protocol View : _View { associatedtype Body : View var body: Self.Body { get } } 这种带有 associatedtype 的协议不能作为类型来使用,而只能作为类型约束使用: // Error func createView() -> View { } // OK func createView<T: View>() -> T { } 这样一来,其实我们是不能写类似这种代码的: // Error,含有 associatedtype 的 protocol View 只能作为类型约束使用 struct ContentView: View { var body: View { Text("Hello World") } } 想要 Swift 帮助自动推断出 struct ContentView: View { var body: Text { Text("Hello World") } } 当然我们可以明确指定出
let someCondition: Bool // Error: Function declares an opaque return type, // but the return statements in its body do not have // matching underlying types. var body: some View { if someCondition { // 这个分支返回 Text return Text("Hello World") } else { // 这个分支返回 Button,和 if 分支的类型不统一 return Button(action: {}) { Text("Tap me") } } } 这是一个编译期间的特性,在保证 associatedtype protocol 的功能的前提下,使用 Section 2 - Step 1: 预览 SwiftUISwiftUI 的 Preview 是 Apple 用来对标 RN 或者 Flutter 的 Hot Reloading 的开发工具。由于 IBDesignable 的性能上的惨痛教训,而且得益于 SwiftUI 经由 UIKit 的跨 Apple 平台的特性,Apple 这次选择了直接在 macOS 上进行渲染。因此,你需要使用搭载有 SwiftUI.framework 的 macOS 10.15 才能够看到 Xcode Previews 界面。 Xcode 将对代码进行静态分析 (得益于 SwiftSyntax 框架),找到所有遵守 笔者自己尝试下来,这套开发方式带来的效率提升相比 Hot Reloading 要更大。Hot Reloading 需要你有一个大致界面和准备相应数据,然后运行 app,停在要开发的界面,再进行调整。如果数据状态发生变化,你还需要 restart app 才能反应。SwiftUI 的 Preview 相比起来,不需要运行 app 并且可以提供任何的 dummy 数据,在开发效率上更胜一筹。 经过短短一天的使用,Option + Command + P 这个刷新 preview 的快捷键已经深入到我的肌肉记忆中了。 Section 3 - Step 5: 关于 ViewBuilder创建 Stack 的语法很有趣: VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) Text("Joshua Tree National Park") .font(.subheadline) } 一开始看起来好像我们给出了两个 public struct VStack<Content> where Content : View { init( alignment: HorizontalAlignment = .center, spacing: Length? = nil, content: () -> Content) } 前面的 这里使用了 Swift 5.1 的另一个新特性:Funtion builders。如果你实际观察 init( alignment: HorizontalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content) 而 @_functionBuilder public struct ViewBuilder { /* */ } 使用 实际上构建这个 // 等效伪代码,不能实际编译。 VStack(alignment: .leading) { viewBuilder -> Content in let text1 = Text("Turtle Rock").font(.title) let text2 = Text("Joshua Tree National Park").font(.subheadline) return viewBuilder.buildBlock(text1, text2) } 当然这种基于 funtion builder 的方式是有一定限制的。比如 TupleView<(Text, Text)>( (Text("Hello"), Text("Hello")) ) 除了按顺序接受和构建 var someCondition: Bool VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) Text("Joshua Tree National Park") .font(.subheadline) if someCondition { Text("Condition") } else { Text("Not Condition") } } 其他的命令式的代码在 VStack(alignment: .leading) { // let 语句无法通过 function builder 创建合适的输出 let someCondition = model.condition if someCondition { Text("Condition") } else { Text("Not Condition") } } 到目前为止,只有以下三种写法能被接受 (有可能随着 SwiftUI 的发展出现别的可接受写法):
Section 4 - Step 7: 链式调用修改 View 的属性教程到这一步的话,相信大家已经对 SwiftUI 的超强表达能力有所感悟了。 var body: some View { Image("turtlerock") .clipShape(Circle()) .overlay( Circle().stroke(Color.white, lineWidth: 4)) .shadow(radius: 10) } 可以试想一下,在 UIKit 中要动手撸一个这个效果的困难程度。我大概可以保证,99% 的开发者很难在不借助文档或者 copy paste 的前提下完成这些事情,但是在 SwiftUI 中简直信手拈来。在创建 let image: Image = Image("turtlerock") let modified: _ModifiedContent<Image, _ShadowEffect> = image.shadow(radius: 10)
extension View { func shadow( color: Color = Color(.sRGBLinear, white: 0, opacity: 0.33), radius: Length, x: Length = 0, y: Length = 0) -> Self.Modified<_ShadowEffect> }
public typealias Modified<T> = _ModifiedContent<Self, T>
struct _ModifiedContent<Content, Modifier> { var content: Content var modifier: Modifier } 在 extension _ModifiedContent : _View where Content : View, Modifier : ViewModifier { } 在
其他的几个修改 View 属性的链式调用与 小结上面是对 SwiftUI 教程的第一部分进行的一些说明,在之后的一篇文章里,我会对剩余的几个教程中有意思的部分再做些解释。 虽然公开还只有一天,但是 SwiftUI 已经经常被用来和 Flutter 等框架进行比较。试用下来,在 view 的描述表现力上和与 app 的结合方面,SwiftUI 要胜过 Flutter 和 Dart 的组合很多。Swift 虽然开源了,但是 Apple 对它的掌控并没有减弱。Swift 5.1 的很多特性几乎可以说都是为了 SwiftUI 量身定制的,我们已经在本文中看到了一些例子,比如 Opaque return types 和 Function builder 等。在接下来对后面几个教程的解读中,我们还会看到更多这方面的内容。 另外,Apple 在背后使用 Combine.framework 这个响应式编程框架来对 SwiftUI.framework 进行驱动和数据绑定,相比于现有的 RxSwift/RxCocoa 或者是 ReactiveSwift 的方案来说,得到了语言和编译器层级的大力支持。如果有机会,我想我也会对这方面的内容进行一些探索和介绍。 |
|