声明式 UI 框架 SwiftUI 的体验及渲染流程探索

前言

在刚刚结束的 WWDC 19 上,苹果推出了能够运行在全部 Apple 平台的全新的声明式 UI 框架 —— SwiftUI。而在不久前的 Google I/O 19 上,Android 也推出了其声明式 UI 框架 —— Jetpack Compose。使得如今,不论是移动操做系统 Android 和 iOS,仍是前端开发框架 React、Vue、Angular,仍是如今最新的跨平台开发框架 Flutter,都加入到了声明式 UI 的阵营。本篇文章就主要讲一下 SwiftUI 的使用,以及对其渲染流程的探索。前端

准备

在使用 SwiftUI 以前,须要以下的环境:swift

  • Xcode 11 beta(必选bash

    经过官网下载最新的 Xcode 11 beta 并安装。app

  • macOS 10.15 beta (可选框架

    macOS 的系统最好升级到 10.15 beta,不升也不要紧。由于 10.15 beta 目前尚未公测,想要体验到 10.15 beta 目前还比较麻烦,首先须要有开发者帐号,而后注册 Apple Developer Program 来获取最新的版本。可是不更新系统也是能够体验到 SwiftUI 的,只是不能使用 Xcode 里的预览界面的功能而已,因此我是没有更新 macOS 的系统,个人 macOS 的系统版本号是 10.14.5。ide

建立支持 SwiftUI 的工程

Xcode 11 beta 安装成功后打开,函数

选择 Create a new Xcode project性能

选择 iOS 里的 Single View App,而后点击 Nextui

而后输入 Product NameOrganization Identifier,最重要的是要勾选上 Use SwiftUI,而后点击 Nextthis

选择工程的文件夹,而后点击 Create,一个使用 SwiftUI 的工程就会建立成功。

在 Xcode 里选中 ContenView.swift 就能够看到用声明式写的 UI,而后咱们 build 这个工程,下面是运行的效果:

使用 SwiftUI 写一个简单的计算器

接下来,准备使用 SwiftUI 写一个简单的计算器,这个计算器有一个 Text,用来显示当前的数字,有两个按钮,一个自增的按钮,和一个自减的按钮,这三个 View 使用 VStack 来排列,VStack 能够容许竖直排列 View,HStack 容许水平排列 View。因此这个界面就能够这么写:

var body: some View {
        VStack{
            Text("0")
            
            Button(
                action:{print("increment")},
                label: {Text("increment")}
            )
            
            Button(
                action: {print("decrement")},
                label: {Text("decrement")}
            )
        }
    }
}
复制代码

Button 里的 action 表明的是按钮点击事件,运行后的效果为:

若是想要改变 Text 的样式的话,能够以下这种操做:

Text(String(count))
    .frame(
        width: UIScreen.main.bounds.width,
        height: 50
    )
    .background(Color.blue)
    .foregroundColor(Color.yellow)
    .padding(10)
复制代码

运行后的效果为:

若是你以前体验过声明式的 UI 框架,对这种写法应该不会陌生。

由于要实现计算器的功能,因此须要定义一个变量来存储当前的值,这个变量的值也会显示在 Text 里,这个变量要定义在 ContentView 里,并且必需要用 @State,同时实现自增和自减的函数,代码以下:

struct ContentView : View {
    @State var count = 0;
    
    var body: some View {
        VStack{
            Text(String(count))
                .frame(
                    width: UIScreen.main.bounds.width,
                    height: 50
                )
                .background(Color.blue)
                .foregroundColor(Color.yellow)
                .padding(10)
            
            Button(
                action:{self.increment()},
                label: {Text("increment")}
            )
            
            Button(
                action: {self.decrement()},
                label: {Text("decrement")}
            )
        }
    }
    
    func increment() {
        count = count + 1
    }
    
    func decrement() {
        count = count - 1
    }
}
复制代码

运行后,点击加、减的按钮,Text 里显示的值就会跟随着变化:

SwiftUI 的渲染流程

由于 SwiftUI 并无开源,因此不能看源代码,可是咱们根据现有的资料,也足以推断出 SwiftUI 的渲染流程。

SwiftUI 里 View 的定义

在前面写计算机 UI 的时候,咱们用到了以下的定义:View、VStack、Text、Button。咱们能够分析一下这几个类的定义。

好比 View 在源代码里的定义为:

public protocol View : _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 }
}
复制代码

能够看到 View 是一个 protocol 接口。

VStack 在源代码里的定义为:

public struct VStack<Content> where Content : View {

    /// Creates an instance with the given `spacing` and Y axis `alignment`.
    ///
    /// - Parameters:
    ///     - alignment: the guide that will have the same horizontal screen
    ///       coordinate for all children.
    ///     - spacing: the distance between adjacent children, or nil if the
    ///       stack should choose a default distance for each pair of children.
    @inlinable public init(alignment: HorizontalAlignment = .center, spacing: Length? = nil, content: () -> Content)

    /// 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.
    public typealias Body = Never
}
复制代码

VStack 是一个 struct 结构体。

Text 在源代码里的定义为:

public struct Text : Equatable {

    /// Creates an instance that displays `content` verbatim.
    public init(verbatim content: String)

    /// Creates an instance that displays `content` verbatim.
    public init<S>(_ content: S) where S : StringProtocol

    /// Creates text that displays localized content identified by a key.
    ///
    /// - Parameters:
    ///     - key: The key for a string in the table identified by `tableName`.
    ///     - tableName: The name of the string table to search. If `nil`, uses
    ///       the table in `Localizable.strings`.
    ///     - bundle: The bundle containing the strings file. If `nil`, uses the
    ///       main `Bundle`.
    ///     - comment: Contextual information about this key-value pair.
    public init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)

    public func resolve(into result: inout Text._Resolved, in environment: EnvironmentValues)

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (a: Text, b: Text) -> Bool
}
复制代码

Text 也是一个 struct 结构体。

咱们也能够用 UIKit 框架里面最基础的视图类 UIView 来进行对比,就能够发现 SwiftUI 里的 View 只是一个描述而已,并不负责实际的渲染。

SwiftUI 的渲染流程

在结合在网上其他由关 SwiftUI 的资料,和 React、Flutter 的类 Virtual DOM 的技术,能够推断 SwiftUI 的渲染流程为:

  1. 首先用声明式的方法来写 UI
  2. 而后 SwiftUI 框架内部会根据 View 的声明,来渲染 UI
  3. 当状态发生变化时,首先会经过先后 View 声明的变化,肯定哪些 UI 有变化,而后再去渲染变化的 UI,这样就保证了不会重复渲染,并且 View 的声明都是 struct 结构体,它的建立和销毁是很轻量的,不会对性能形成影响,从而保证了渲染的效率。

很惋惜 SwiftUI 是闭源的,没办法知道里面细节的实现,可是大体的渲染过程应该是这个样子了。

如今能开始使用 SwiftUI 吗?

SwiftUI 是 iOS 13 才有的全新 framework,因此 iOS 13 之前的 iPhone & iPad 是不支持的。而 iOS 13 也是 2019 年 6 月 4 日 WWDC 19 上才发布的,虽然 iOS 的系统的升级率比较高,但那也得一年之后才能开始用于正式的生产环境。

相关文章
相关标签/搜索