聲明式 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 的系統的升級率比較高,但那也得一年之後才能開始用於正式的生產環境。

相關文章
相關標籤/搜索