初識 SwiftUI

什麼是SwiftUI?

2019年 WWDC 大會上,蘋果宣佈了基於Swift語言構建的全新UI框架-SwiftUI。其界面佈局徹底拋棄了Storyboard和Autolayout,採用了聲明式的界面語言(DSL,即Domain Specific Language),加上 Canvas 的實時預覽功能,開發體驗有了很大的提高。git

自 iOS SDK 2.0 開始,UIKIt已經伴隨開發者近十年,其思想繼承了成熟的 AppKit 和 MVC, 爲iOS開發提升了良好的學習曲線。UIKit提供的是一套符合直覺的、命令式的編程方式,可是因爲UIKit的基本思想要求 View Controller 承擔絕大部分職責,它須要協調 model,view 以及用戶交互,使得在較大型的項目裏 View Controller 很臃腫,若是狀態管理複雜甚至致使後期代碼沒法維護。近年來隨着編程思想、技術的進步,愈來愈多的開始使用聲明式函數式的方式來進行界面開發,如今大熱的 React 和 Flutter 即是採起了聲明式編程。github

在這種狀況下,今年發佈的SwiftUI固然也採起了聲明式編程。編程

什麼是聲明式編程

維基百科對命令式、聲明式編程的描述以下:swift

聲明式編程:是一種不使用控制流來表示計算邏輯的編程範式。

命令式編程:是一種經過語句來改變程序狀態的編程範式。
複製代碼

舉個栗子,當咱們要設置一個Text的時候,命令式編程以下bash

override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        label.text = "世界和平"
        label.textColor = .purple
        view.addSubview(label)
    }
複製代碼

使用SwiftUI聲明式編程時,則:session

var body: some View {
     Text("世界和平")
    		.color(.purple) 
    }
複製代碼

從上面能夠看出,UIKit命令式編程是經過一句句的代碼來指導「要怎麼構建UI」,而聲明式編程則是使用各自的DSL來描述「UI應該是什麼樣子」數據結構

SwiftUI是怎麼進行佈局

在SwiftUI裏上面Text的聲明只是純數據結構的描述,並非實際顯示出來的視圖,SwiftUI 會直接讀取 DSL 內部描述信息並收集起來,而後轉換成基本的圖形單元,最終交給底層 Metal 或 OpenGL 渲染出來。閉包

在 SwiftUI 中Text屬性的設置在內部都會用一個虛擬的 View 來承載,而後在開始佈局的時候再進行佈局計算,這樣作的目的主要是方便底層在設計渲染函數時更容易作到 monomorphic call,省去無用的分支判斷,提升效率。app

官方教程小解

swiftUI控件

swiftUI控件圖

備註:要使用 SwiftUI 的完整功能, 需使用 Xcode 11, 而且將 macOS 系統升級到最新的10.15框架

此次SwiftUI的官方教程能夠說蘋果很給力,每一步代碼都有講解說明及效果圖片,強烈推薦跟着教程敲一遍!這裏簡單說明一下教程中的一些細節

APP 的啓動

在示例中,app在AppDelegate中經過application(_:configurationForConnecting:options)返回一個UISceneConfiguration實例:

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
複製代碼

這部份內容是 iOS 13 中新加入的經過 Scene 管理 app 生命週期的方式,以及多窗口支持部分所須要的代碼。在 app 完成啓動後,控制權被交接給 SceneDelegate,它的 scene(_:willConnectTo:options:)將會被調用,進行 UI 的配置:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = UIHostingController(rootView: ContentView())
        self.window = window
        window.makeKeyAndVisible()
    }
複製代碼

這段代碼則跟以前iOS app啓動很相似,惟一不一樣的就是rootViewControllerUIHostingController類型的,UIHostingControllerUIViewController的子類,主要負責接受一個SwiftUI的View的描述並將其用UIKit進行渲染,因爲這種繼承關係,因此能夠作到將SwiftUI構建的界面能夠集成到已有的UIKit app中,不須要從頭開始就是基於SwiftUI的構建

some View

struct ContentView: View {
    var body: some View {
        Text("世界和平")
            .color(.purple)
    }
}
複製代碼

SceneDelegate進入到ContentView,會發現一個奇怪的some view,要解釋這個咱們就須要先了解下View

View是SwiftUI的最核心的一個協議,表明了一個屏幕上元素的描述。這個協議中含有一個 associatedtype

public protocol View : _View {

    associatedtype Body : View

    var body: Self.Body { get }
}
複製代碼

這種帶有associatedtype的協議不能做爲類型來使用,而只能做爲類型約束使用,即不能寫成var body: View不然會報Error,提示「Protocol 'View' can only be used as a generic constraint」

其實若是咱們指明 Body 的類型,也是能夠的,例如

struct ContentView: View {
    var body: Text {
        Text("世界和平")
            .color(.purple)
    }
}
複製代碼

可是有個問題就是若是每次修改Body的返回時,咱們都須要收到調整相應的類型,會很麻煩也無必要。 some View 這種寫法使用了 Swift 5.1 的 Opaque return types 特性。它向編譯器做出保證,每次 body 獲得的必定是某一個肯定的,遵照 View 協議的類型,可是請編譯器「網開一面」,不要再細究具體的類型。這一個編譯期間的特性,在保證associatedtype protocol的功能的前提下,使用 some 能夠抹消具體的類型。這個特性用在 SwiftUI 上簡化了書寫難度,讓不一樣 View 聲明的語法上更加統一。

Modifier

VStack(alignment: .leading) {
    Text("世界和平")
        .font(.title)
}
複製代碼

除了View 以外,Modifier是 SwiftUI 另外一個重要概念。在上面的代碼中, .font(...).foregroundColor(...)都修飾了 Text() 視圖的某些屬性——字體和顏色。每個單獨的 Modifier 並不會對 View 類型實例進行操做,而是一個返回 some View類型的閉包。所以 Modifier 的運行機制與咱們熟悉的 UIKit 中對視圖屬性進行修改的方式是相反的,咱們構建出一個視圖時並不會先初始化出一個 View 實例再對其進行修飾,而是經過聲明的各類Modifier 構建出View 實例

當容器內視圖具備相同的屬性,能夠將其提取到容器外進行定義,從而減小大量的重複代碼:

HStack(alignment: .top) {
    Text("世界和平")
        .font(.subheadline)
    Spacer()
    Text("地球無戰事")
        .font(.subheadline)
    
}
.foregroundColor(.yellow)

複製代碼

數據綁定

SwiftUI中一個很便捷的功能是視圖與數據的綁定。在官方示例中,Toggle控件就綁定了showFavoritesOnly,並且綁定數據的方式很是的優雅,在初始化方法中經過 $ 聲明某個屬性即可以讓視圖自動綁定此變量

注:State是一個值或一組值,能夠隨時間發生變化並影響視圖的行爲、內容或佈局。 👇使用具備 @State 聲明的屬性將狀態添加到視圖。

@State var showFavoritesOnly = true

var body: some View {
    
    Toggle(isOn: $showFavoritesOnly){
        Text("Favorites only")
    }
}
複製代碼

ForEach

ForEach 能以與 List相同的方式對集合進行,也就是說能夠在任何使用子視圖的地方使用它,例如堆棧、列表、groups中,當數據元素爲簡單的值類型,能夠直接將\.self當作標識符的關鍵路徑

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
        }
    }
}
複製代碼

Canvas使用

  • Canvas 具備實時預覽功能,在 Xcode 中顯示的快捷鍵 CMD + OPTION + ENTER,取消顯示快捷鍵CMD + ENTER
  • 在 Xcode 中能夠經過 Editor → Previews → Show View Bounds展現視圖邊界,方便 UI 校對
  • 點擊Canvas左下角的針頭能夠固定當前的Canvas,以便點擊其餘SwiftUI界面時候不會隨之變更Canvas
  • 選擇LivePreview,可使靜態畫布變成實時的,咱們能夠直接在Canvas上進行手勢操做、觸發事件查看效果

總結

今年WWDC發佈的SwiftUI,是一個令iOS開發者興奮的全新UI框架。雖然如今還有些遺憾,如僅支持iOS13及以上且其跨平臺目前也僅僅只停留在蘋果內部的生態圈裏,可是因爲Swift是開源的且Swift5的ABI也已經穩定下來,當2 - 3年後咱們能夠大面積使用它的時候,SwiftUI應該會有很不錯的反響。

參考資料

官方示例

SwiftUI 的一些初步探索 (一)

SwiftUI 的一些初步探索 (二)

SwiftUI 概覽:十分鐘構建簡單應用

相關文章
相關標籤/搜索