SwiftUI — 初识SwiftUI

前言:
作为一个2018年年末下海去创业的程序员来说,去年WWDC真是让我回忆良多,曾经在北京加班发版,杭州双十一压力测试,而这张图更是让人泪目。


SwiftUI — 初识SwiftUI_第1张图片

还有对于 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):

SwiftUI — 初识SwiftUI_第2张图片
image.png

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

布局方式

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

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

SwiftUI — 初识SwiftUI_第3张图片
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 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 :View

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)
SwiftUI — 初识SwiftUI_第4张图片
蓝色部分是100x100

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

ModifiedContent,_BackgroundModifier>, _FrameLayout>

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


SwiftUI — 初识SwiftUI_第5张图片
截屏2020-02-19下午9.27.13.png

看这次的输出 符合我们的推测 所以我们现在不能用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)
SwiftUI — 初识SwiftUI_第6张图片
???

PreviewProvider

用来初始化一个用来展示的View
这里值得注意的就是,可以使用 Group 来创建多个previewDevice

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group{
            ContentView()
                .previewDevice("iPhone 8")
            ContentView()
                .previewDevice("iPhone 11 Pro Max")
        }
    }
}
SwiftUI — 初识SwiftUI_第7张图片
截屏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我们在后面说。

你可能感兴趣的:(SwiftUI — 初识SwiftUI)