SwiftUI 能够说是 WWDC 2019 中最让人激动的技术了,什么是 SwiftUI 呢?官方说法为:SwiftUI is a modern way to declare user interfaces for any Apple platform. Create beautiful, dynamic apps faster than ever before。html
总之,这套新的 UI 框架用 WWDC Session 中的话描述就是:git
The Shortest Path to a Great Appgithub
那下面咱们就用 SwiftUI 实现一个 iOS 中最多见的列表页,看看到底 Modern、Faster 在哪里?数据库
struct LandMarkView : View {
let landmarks: [LandMark]
var body: some View {
List(landmarks) { landmark in
HStack {
Image(landmark.thumbnail)
Text(landmark.name)
Spacer()
if landmark.isFavorite {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
}
复制代码
同使用 UIKit 写一个列表页进行对比:编程
List(items)
中描述列表的数据,在 List 的 Closure 中描述每一个 Cell能够看到,SwiftUI 极大地简化了构建 UI 的过程(Faster),这种耳目一新的构建方式是 Declarative 声明式编程(Modern),而以前 UIKit 的方式是 Imperative 命令式编程,二者有什么区别呢?swift
举个例子,若是咱们要去旅游:app
Declarative 的核心在于描述 What,将 How 委托给一个 Expert 来完成。如何描述 What,这里就涉及到了 DSL 领域描述语言。框架
在 SwiftUI 以前,咱们其实或多或少接触过 Declarative,最典型的就是 SQL,SQL 语句就是一种 DSL,例如对于 SELECT * from product WHERE id = 996
这条语句,只是描述了咱们想从 product 表中找到 id 为 996 的商品(What),至于怎么找(How),交给数据库来处理,数据库会高效、健壮的取到数据并返回给咱们。另外,AutoLayout 也能够当作一种简单的 Declarative,咱们描述约束,Layout Engine 计算最终的 Frame。ide
Imperative 和 Declarative 二者各有优缺点,从目前的趋势来看,React/Flutter/SwiftUI 经过 Declarative 来构建 UI,看起来 Declarative 是将来 UI 编程的趋势。为何你们都不约而同的选择 Declarative 呢?今年 WWDC 中 Apple 工程师给出了答案:函数
对于一个 App 而言,其代码分为两部分 Basic Features 和 Exciting/Custom Features,让 App 出彩、给用户带来很棒体验的是 Exciting/Custom Features,SwiftUI 的目的就是为了减小开发者在 Basic Features 部分的负担,让开发者更专一于 Exciting/Custom Features。
A view defines a piece of UI
上面也提到了,声明式至关于将具体的操做委托给一个 Engine,由 Engine 来作具体的脏活累活,向上提供一个抽象层。在 SwiftUI 中这个抽象层就是 View,SwiftUI 中的 View 再也不是 UIKit 中的 UIView,没有 Backing Store,不涉及到真正的渲染,View 只是一个抽象概念,描述 UI 应该如何展现。咱们看下 View 的定义:
public protocol View : _View {
associatedtype Body : View
var body: Self.Body { get }
}
复制代码
能够看出,在 SwiftUI 中 View 只是一个 protocol,里面有一个 body 的属性,body 又是 View。这样就能够经过 body 将 View 串起来,造成 View Hierarchy。
为了实现 SwiftUI 的声明式编程,提供 DSL,Swift 语言在 5.1 中引入了一些新特性:(注:这一节的内容参考 SwiftUI 的一些初步探索 (一) - 小专栏 和 SwiftUI 的 DSL 语法分析 - 知乎 较多)
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
复制代码
上面一段自定义 View 的代码中 var body: some View
这行中多了一个 some
,这个 some 是干嘛用的?因为 View 只是 protocol,在 Swift 5.1 以前,带有 associatedtype 的协议是不能作为类型来用,只能做为类型约束:
// Error
// Protocol 'View' can only be used as a generic constraint
// because it has Self or associated type requirements
func createView() -> View {
}
// OK
func createView<T: View>() -> T {
}
复制代码
至关于在声明 body 时,不能用 View,须要指定具体的类型,例如 VStack、Text 等,但若是 body 的类型变化,每次都须要修改,比较麻烦。所以 Swift 5.1 引入了 Opaque Return Types,使用方式是 some protocol
,当 body 的类型变成 some View
后,至关于它向编译器做出保证,每次 body 获得的必定是某一个肯定的、遵照View协议的类型,可是请编译器“网开一面”,不要再细究具体的类型。返回类型肯定单一这个条件十分重要,写成下面的样子编译器会报错:
// Error
// Function declares an opaque return type, but the return
// statements in its body do not have matching underlying types
let someCondition: Bool = false
var body: some View {
if someCondition {
return Text("Hello World")
} else {
return Button(action: {}) {
Text("Tap me")
}
}
}
复制代码
struct RoomDetail : View {
let room: Room
@State private var zoomed = false
var body: some View {
Image(room.imageName)
.resizable()
.aspectRatio(contentMode: zoomed ? .fill : .fit)
.tapAction { self.zoomed.toggle() }
}
}
复制代码
在上面的代码中,一旦 zoomed 的值发生变化,SwiftUI 会自动更新 UI,这一切都源于 @State
。State 本质上只是一个自定义类,用 @propertyDelegate 修饰,@State var zoomed
会将 zoomed 的读写转到 State 类中实现了。
@propertyDelegate public struct State<Value> @propertyDelegate public struct Binding<Value> @propertyDelegate public struct Environment<Value> 复制代码
里面 @propertyDelegate
是 Swift 5.1 引入的新特性 Property Delegate,这个特性有什么用呢?假设咱们有一个设置页面,须要在 UserDefault 中存储一些属性,
struct Preferences {
static var shouldAlert: Bool {
get {
return UserDefaults.standard.object(forKey: "shouldAlert") as? Bool ?? false
} set {
UserDefaults.standard.set(newValue, forKey: "shouldAlert")
}
}
static var refreshRequency: Bool {
get {
return UserDefaults.standard.object(forKey: "refreshRequency") as? TimeInterval ?? 6000
} set {
UserDefaults.standard.set(newValue, forKey: "refreshRequency")
}
}
复制代码
能够发现 shouldAlert 和 refreshRequency 代码重复较多,若是再多一些设置值,Preferences 这个类会写的烦死。针对这种状况,Swift 5.1 引入 Property Delegate,能够将 Property 的相同行为 Delegate 给一个代理对象去作:
@propertyDelegate
struct UserDefault<T> {
let key: String
let defaultValue: T
var value: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
struct Preferences {
@UserDefault(key: "shouldAlert", defaultValue: false)
static var shouldAlert: Bool
@UserDefault(key: "refreshRequency", defaultValue: 6000)
static var refreshRequency: TimeInterval
}
复制代码
当使用 @UserDefault(key: "shouldAlert", defaultValue: false)
修饰过 shouldAlert 以后,shouldAlert 会被编译器处理成下面的样子:
struct Preferences {
static var $shouldAlert = UserDefault<Bool>(key: "shouldAlert", defaultValue: false)
static var shouldAlert: Bool {
get {
return $shouldAlert.value
}
set {
$shouldAlert.value = newValue
}
}
}
复制代码
回到 @State
,当 zoomed 被 @State
修饰后,zoomed 的读写被 Delegate 到 State 类中,SwiftUI 框架在 State 类中根据 zoomed 值的变化去触发界面的更新,达到 Value 变化 UI 自动更新的效果。
HStack(alignment: .center) {
Image(landmark.thumbnail)
Text(landmark.name)
Spacer()
if landmark.isFavorite {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
复制代码
HStack 中 View 与 View 之间没有 , 区分,也没有 return,这种 DSL 的写法主要基于 Swift 的 Trailing Closure 和 Function Builder。下面是 HStack 的定义:
public struct HStack<Content> where Content : View {
@inlinable public init(alignment: VerticalAlignment = .center,
spacing: Length? = nil,
@ViewBuilder content: () -> Content)
复制代码
首先对于 Trailing Closure,若是一个 Swift 方法中最后一个参数是 Closure,则能够将 Closure 提到括号外面。
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content where Content : View
}
复制代码
其次对于 Function Builder,能够看到 content 前面有一个 @ViewBuilder
,而 ViewBuilder 使用了 @_functionBuilder
修饰,被 @ViewBuilder
修饰过的 Closure 就会被修改语法树,转调 ViewBuilder 的 buildBlock 函数。最终
HStack(alignment: .center) {
Image(landmark.thumbnail)
Text(landmark.name)
Spacer()
if landmark.isFavorite {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
复制代码
被转换成了
HStack(alignment: .center) {
return ViewBuilder.buildBlock(
Image(landmark.thumbnail),
Text(landmark.name),
Spacer()
)
}
复制代码
最后,Apple 为 SwiftUI 提供了一个不管是内容仍是交互都很是棒的官方教程,值得学习 SwiftUI 时跟着教程动手练习,正如同今年 WWDC 的主题同样,一块儿 Write Code,Blow Minds 吧。
Article by JoeShang