前言:
作为一个2018年年末下海去创业的程序员来说,去年WWDC真是让我回忆良多,曾经在北京加班发版,杭州双十一压力测试,而这张图更是让人泪目。
还有对于 Swift 开发者来说, WWDC 19 首日最引人注目的内容自然是 SwiftUI 的公布了。所以现在就来学习一下,本篇博客是按照笔者学习官方教程顺序来写,具体怎么创建语法什么的这里就不写了,这次的苹果的官方教程绝对给力连创建都用动画一步一步展示给你,交互也很厉害,就写一些苹果没有讲的细节 和 解决学习过程中的困惑吧,也就是对教程的解析。
初见SwiftUI
这里先说初始这个的感受 后面总结一下 苹果为什么这样修改
SwiftUI DLS 特点
1、省略了很多逗号,return,中括号等,声明式编程
2、出现了 很多关键词 例如 Some 等
3、终于使用 Flex Box 布局了
4、出现了 PreviewProvider 类似 安卓的xml 提供预览数据
5、支持简单的逻辑控制,比如 if 控制语句
6、与 Swift 已有的语法不冲突
为什么要先说一下初见印象了,其实你看完下面就知道了,不能用以前学习UIKit的方式去看待SwiftUI,而是要以全新的思路来理解它
接下里就按照官方教程顺序来解读一些细节吧
some View
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
创建好项目以后一进去就 能看到一个奇怪的 some ,还是点击我们熟悉的View看一下吧
public protocol View {
/// The type of view representing the body of this view.
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
}
这里苹果就指出 这个body swift 会自己推断他,要求必须实现body,所以some这个词就好推断了这个可能是泛型
去查看 Swift 最近的新版本中,果然发现了一个 Opaque return types
它向编译器作出保证,每次 body 得到的一定是某一个确定的,遵守 View 协议的类型,但是请编译器“网开一面”,不要再细究具体的类型。这类的泛型特性也被称作“反向泛型”,因为具体的类型参数是由“实现部分”指定并隐藏起来的,而一般的泛型是由“调用者”所指定的。
SwiftUI 最大特点的是声明式以及高度可组合,View 的唯一属性 body 是另一个满足 View 约束的具体 View 类型,我们在这里看到了组合以及递归两个特性。我们现在来看上面的最初的代码 ContentView 使用了Opaque return types的特性,对外隐藏了具体类型 Text。此外,ContentView 的具体类型都是通过它的 body 属性递归定义的(取决于它所包含的具体 View):
所有的递归定义都需要一个终止条件,于是就有了以下这些原生 View:Text、Color、Spacer、Image、Shape、Divider 等
布局方式
var body: some View {
VStack{
Text("Hello World!")
}
}
对于我这种刚开始工作就用Texture 原名叫 AsyncDisplay 的 这个布局方式简直熟悉的不要再熟悉
就是极致简化版的 不过我这里是自己手动包装Stack返回,但是这里没有return,所以点VStack看是如何简化的吧
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
可以看最后的content:() -> Content ,但是我们在创建这个 VStack 时所提供的代码只是简单写了个 Text,而并没有实际返回一个可用的 Content,但是细看发现 Content 前面还有一个 @ViewBuilder
去查看 Swift 最近的新版本中,果然发现了一个 Funtion builders,查看ViewBuilder 其实是一个 @_functionBuilder 进行标记的 struct
@_functionBuilder public struct ViewBuilder {
/// Builds an empty view from an block containing no statements, `{ }`.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
/// unmodified.
public static func buildBlock(_ content: Content) -> Content where Content : View
}
被标记的方法 最终 会变成
// Original source code:
@TupleBuilder
func build() -> (Int, Int, Int) {
1
2
3
}
// This code is interpreted exactly as if it were this code:
func build() -> (Int, Int, Int) {
let _a = 1
let _b = 2
let _c = 3
return TupleBuilder.buildBlock(_a, _b, _c)
}
那这样 Content 返回的就是View了,那这样的话其实就是 苹果 用@ 标记的方法制定了一定规则,对代码进行加工,简化了Texture那种写法 还省略了 return
(这个各位如何熟悉Java的话,可以知道这个叫做注解)
你查看这个ViewBuilder的 extension 还能发现一个有趣的地方
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View
}
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
苹果添加了一页的 extension ,你拉到下面发现这个buildBlock参数其实是有限制的 最多10个
除了按顺序接受和构建 View 的 buildBlock 以外,ViewBuilder 还实现了两个特殊的方法:buildEither 和 buildIf。它们分别对应 block 中的 if...else 的语法和 if 的语法
/// Provides support for "if-else" statements in multi-statement closures, producing
/// ConditionalContent for the "else" branch.
public static func buildEither(second: FalseContent) -> _ConditionalContent where TrueContent : View, FalseContent : View
也就是可以在下面写这样的代码:
var showDetail : Bool
var body: some View {
VStack{
if showDetail {
Text("Normal")
}else {
Text("Detail")
}
}
用lldb调试可以输出self.body如下图,SwiftUI 会生成这样的 _ConditionalContent
ConditionalContent)
因为 @ViewBuilder 标记 会对 代码进行加工 所以 对中间的代码就有所限制 必须 写结果为 view的语句 或者简单逻辑 if else 其他的都会报错(if else 最后也会被编译成结果为View的语句)
modifier
SwiftUI 使用 modifier 来修改View 的属性 通过链式的方式修改
在看很多教程的时候都有人提醒 Swift的 modifier 是有顺序的
这里我们就用打印视图的 body 的类型来看一下Swift 的type(of:) 方法可以打印出特定值的精确类型
Button("hello"){
print(type(of: self.body))
}
.background(Color.red)
.frame(width: 100, height: 100)
??? 这个并不是我想要的结果啊 为什么
得到输出
ModifiedContent,_BackgroundModifier>, _FrameLayout>
点进View的文件 可以找到 ModifiedContent
因为SwiftUI 是不开源的所以我们看不到渲染过程 所以只能通过结果猜测
每次我们修改视图的时候 SwiftUI每次同用ModifiedContent来储存修改以后的内容
当我们应用了多个modifier时候,ModifiedContent 会层层叠加
这意味着,你的 modifier 的顺序至关重要
因为你如果上面代码更换一下frame 和 background 你就可以得到你想要的结果了
看这次的输出 符合我们的推测 所以我们现在不能用UIkit的方式来学习使用SwiftUI
ModifiedContent, _FrameLayout>, _BackgroundModifier
重复的属性也会同样生效,利用这样的性质就能做出奇怪的东西来
Button("hello"){
print(type(of: self.body))
}
.frame(width: 100, height: 100)
.background(Color.red)
.padding()
.background(Color.red)
.padding()
.background(Color.blue)
.padding()
.background(Color.green)
.padding()
.background(Color.yellow)
PreviewProvider
用来初始化一个用来展示的View
这里值得注意的就是,可以使用 Group 来创建多个previewDevice
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group{
ContentView()
.previewDevice("iPhone 8")
ContentView()
.previewDevice("iPhone 11 Pro Max")
}
}
}
如果删掉就会报错
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
这里的Group 就是用打包做类型消除的
总结
其实你看完这篇文章,可以看出SwiftUI的DLS有很多现在流行的跨平台编程语言的特点,声明式的编程,实时渲染的界面 ,最流行的Flex Box布局 ,再结合当下的环境你就可以知道,UIkit在面对随着编程技术和思想的进步越发的展现出它的缺点,已经无法跟上时代了。SwiftUI是苹果 用来 对抗 React Native、Flutte 的,而且Swift 5.1 的很多特性几乎可以说都是为了 SwiftUI 量身定制的,我们已经在本文中看到了一些例子,比如 Opaque return types 和 Function builder 等,看来是苹果是很用心了。SwiftUI跟Swift一样都是苹果为了跟上时代造出来的,所以我们这些开发者也得跟上。其实这篇文章只是swift的初识,SwiftUI更强大的Combine我们在后面说。