SwiftUI — 初识SwiftUI

前言:
作为一个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):

image.png

所有的递归定义都需要一个终止条件,于是就有了以下这些原生 View:Text、Color、Spacer、Image、Shape、Divider 等

布局方式

 var body: some View {
        VStack{
          Text("Hello World!")
        }
    }

对于我这种刚开始工作就用Texture 原名叫 AsyncDisplay 的 这个布局方式简直熟悉的不要再熟悉

Texture的布局方式

就是极致简化版的 不过我这里是自己手动包装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) -> 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, C1, C2, C3, C4, C5, C6, C7, C8>(_ 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, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ 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<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> 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<TrueContent, FalseContent> :View

ConditionalContent<SwiftUI.Text, SwiftUI.Text>)

因为 @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)
蓝色部分是100x100

??? 这个并不是我想要的结果啊 为什么
得到输出

ModifiedContent<ModifiedContent<Button<Text>,_BackgroundModifier<Color>>, _FrameLayout>

点进View的文件 可以找到 ModifiedContent<Content, Modifier>
因为SwiftUI 是不开源的所以我们看不到渲染过程 所以只能通过结果猜测
每次我们修改视图的时候 SwiftUI每次同用ModifiedContent来储存修改以后的内容
当我们应用了多个modifier时候,ModifiedContent 会层层叠加
这意味着,你的 modifier 的顺序至关重要
因为你如果上面代码更换一下frame 和 background 你就可以得到你想要的结果了


截屏2020-02-19下午9.27.13.png

看这次的输出 符合我们的推测 所以我们现在不能用UIkit的方式来学习使用SwiftUI

ModifiedContent<ModifiedContent<Button<Text>, _FrameLayout>, _BackgroundModifier<Color>

重复的属性也会同样生效,利用这样的性质就能做出奇怪的东西来

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")
        }
    }
}
截屏2020-02-19下午10.26.29.png

如果删掉就会报错

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我们在后面说。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容