SwiftUI技術(shù)

一、概覽

本篇文章將概述 SwiftUI 的工作原理伪窖,以及它與 UIKit 等框架的不同之處回官。SwiftUI 在概念上與以前的 Apple 平臺上開發(fā) app 的方式完全不同甘畅,它需要你重新考慮如何將你腦中的構(gòu)想轉(zhuǎn)換為實際可工作的代碼列另。

UIKit 中的 View 或 ViewController 是長時間存在的UIView或UIViewController 類的實例芽腾,是對象。UIKit 中页衙,view 的創(chuàng)建和 view 的更新是兩條不同的代碼路徑摊滔。
SwiftUI 中的 View 是值,而非對象拷姿。SwiftUI 中沒有 Controller 的概念。View 是符合 View 協(xié)議的短時存在的值旱函。我們不必編寫多余的代碼來更新屏幕上的文本標簽响巢。每當狀態(tài)改變時,view 樹都會被重建棒妨。

二踪古、View的創(chuàng)建

要在 SwiftUI 中創(chuàng)建 view含长,你需要創(chuàng)建一棵包含 view 的值的樹,來描述應(yīng)該在屏幕上顯示的內(nèi)容伏穆。要更改屏幕上的內(nèi)容拘泞,你可以修改用 @State 修飾的值,這樣新的 view 值的樹會被重新計算枕扫。然后陪腌, SwiftUI 會更新屏幕,以反映這些新的 view 值烟瞧。

import SwiftUI

struct Me: View {
    @State private var counter = 0
    
    var body: some View {
        VStack {
            Button(action: { counter += 1 }, label: {
                Text("Tap me!")
                    .padding()
                    .background(Color(.tertiarySystemFill))
                    .cornerRadius(5)
            })
            if counter > 0 {
                Text("You've tapped \(counter) times")
            } else {
                Text("You've not yet tapped")
            }
        }
    }
}

1诗鸭、View樹中可包含switch 和 if 語句,不能使用循環(huán)和guard

SwiftUI 利用了稱為函數(shù)構(gòu)建器 (function builder) 的 Swift 特性参滴。舉個例子强岸, VStack 之后的尾隨閉包并不是一個普通的 Swift 函數(shù);它是一個 ViewBuilder (它是由 Swift 的 函數(shù)構(gòu)建器特性實現(xiàn)的)。在 view 的構(gòu)建閉包中砾赔,你只能使用 Swift 的一個有限的子集來編寫 程序:例如蝌箍,你不能使用循環(huán)和 guard。但是暴心,你可以像上面示例中的 counter 變量一樣妓盲,編寫 switch 和 if 語句來構(gòu)造出依賴于 app 當前狀態(tài)的 view 樹。除了布爾值 if 語句以外酷勺,你可以使用的還有 if let本橙,if case let。

View 的樹不僅只包含當前可見的部分脆诉,它包含的是整個結(jié)構(gòu)甚亭,這是有優(yōu)點的:SwiftUI 能夠更有效地找出 view 更新后發(fā)生了什么變化。

view樹的結(jié)構(gòu)

2击胜、ModifiedContent 值(修飾器)的深層嵌套

我們在按鈕上使用的 padding亏狰、background 和 cornerRadius API 并不是簡單地去更改按鈕的屬性。實際上偶摔,這些 方法 (我們通常稱其為 “修飾器”) 的調(diào)用都會在 view 樹中創(chuàng)建新的一層暇唾。在按鈕上調(diào)用 .padding() 會將按鈕包裝為 ModifiedContent 類型的值,這個值中包含有關(guān)應(yīng)該如何設(shè)置 padding 填充的信息辰斋。在該值上再調(diào)用 .background策州,又會把現(xiàn)有值包裝起來,創(chuàng)建另一個 ModifiedContent 值宫仗,這一次將添加有關(guān)背景色的信息够挂。

省略ModifiedContent的簡化版

3、順序通常很重要

調(diào)用 .padding().background(...) 與調(diào)用 .background(...).padding() 是不一樣的藕夫。在前一種情況下孽糖, 背景將延伸到填充部分的外邊緣;而在后一種情況下枯冈,背景只會出現(xiàn)在填充范圍的內(nèi)側(cè)。

.padding().background(...)
.background(...).padding()

4办悟、.border 調(diào)用在垂直堆棧周圍添加了 overlay 的修飾器尘奏,該修飾符使用其子元素的大小。

在 SwiftUI 中病蛉,你永遠不會強迫 view 直接使用一個特定的大小炫加。你只能將其包裝在 frame 修飾器中,它的可用空間將被提供給子元素铡恕。view 可以 定義自己的理想大小 (類似于 UIKit 的 sizeThatFits 方法)琢感,你可以強制讓 view 變成它們的理想 大小。

5探熔、更改狀態(tài)屬性是在 SwiftUI 中觸發(fā) view 更新的唯一方法驹针。

點擊按鈕會修改 @State counter 屬性,這會觸發(fā)這種更新 view 的狀態(tài)更改诀艰。觸發(fā) view 更新的屬性會被用 @State柬甥、@ObservedObject 或者 @StateObject 屬性標簽 進行標記。

我們不能直接更新屏幕上的內(nèi)容其垄。相反苛蒲,我們必須修改狀態(tài)屬性 (比如 @State 或 @ObservedObject),然后讓 SwiftUI 去找出 view 樹的變化方式绿满。

三臂外、View的更新

在大多數(shù)面向?qū)ο蟮?GUI 程序,有兩條與 view 相關(guān)的代碼路徑: 一條路徑處理 view 的初始構(gòu)造喇颁,另 一條路徑負責在事件發(fā)生時更新 view漏健。由于這些代碼路徑是分離開的,而且涉及手動更新橘霎,所 以很容易出現(xiàn)錯誤:我們可能會響應(yīng)事件來更新 view蔫浆,但卻忘了更新 model,反之亦有可能姐叁。 無論哪種情況瓦盛,view 都會與 model 不同步,app 可能會表現(xiàn)出不確定的行為外潜、卡死甚至崩潰原环。

AppKit 里使用 Cocoa Binding 技術(shù),它是一個可以使 model 和 view 保持同步的雙向?qū)哟T?UIKit 里嘱吗,人們使用像是響應(yīng)式編 程這樣的技術(shù)來讓這兩個代碼路徑 (在大部分情況下) 得到統(tǒng)一。

SwiftUI 的設(shè)計完全避免了此類問題碧库。首先柜与,只有 view 的 body 屬性這一個代碼路徑可以構(gòu)造 初始的 view,而且這條路徑也會用于所有的后續(xù)更新嵌灰。其次弄匕,SwiftUI 讓使用者無法繞過正常 的 view 的更新周期,也無法直接修改 view 樹沽瞭。在 SwiftUI 中迁匠,想要更新屏幕上的內(nèi)容,觸發(fā) 對 body 屬性的重新求值是唯一的方法驹溃。

SwiftUI 只會重新去執(zhí)行那些使用了 @State 屬性的 view 的 body 城丧。(對于其他屬性包裝,例如 @ObservedObject 和 @Environment豌鹤,也是一樣的)亡哄。

struct BindingView : View {
    @Binding var counter: Int
    var body: some View {
        Button(action: { counter += 1 }, label: {
            Text("Tap me!")
                .padding()
                .background(Color(.tertiarySystemFill))
                .cornerRadius(5)
        })
    }
}

本質(zhì)上來說,binding 是它所捕獲變量的 setter 和 getter布疙。SwiftUI 的屬性包裝 (比如 @State蚊惯, @ObservedObject 等) 都有對應(yīng)的 binding,你可以在屬性名前加上 $ 前綴來訪問它灵临。(在屬性 包裝的術(shù)語中截型,binding 被叫做一個投射值 (projected value))。

四儒溉、屬性包裝

1宦焦、操作值類型

當數(shù)據(jù)是一個值類型的時候 (比如 struct,enum 或者是一個不可變對象)顿涣,我們有三種選擇: 使用普通的屬性波闹,使用 @State 屬性,或者使用 @Binding 屬性园骆。

2舔痪、操作對象

當你的數(shù)據(jù)是一個對象時,你可以讓它滿足 ObservableObject锌唾,這樣 SwiftUI 就能夠訂閱它的 變更锄码。對于可觀察的對象,有三個屬性包裝與它對應(yīng):當指向?qū)ο蟮囊每梢园l(fā)生變化時晌涕,使用 @ObservedObject;當引用不能改變時滋捶,使用 @StateObject;當對象是通過環(huán)境進行傳遞時, 使用 @EnvironmentObject余黎。

3重窟、ObservedObject

ObservableObject 協(xié)議的唯一要求是實現(xiàn) objectWillChange,它是一個 publisher惧财,會在對象 變更時發(fā)送事件巡扇。通過在 name 和 city 屬性前面添加 @Published扭仁,框架會為我們創(chuàng)建一個 objectWillChange 的實現(xiàn),在每次這兩個屬性發(fā)生改變的時候發(fā)送事件厅翔。

class Model: ObservableObject {
    init() { print("Model Created") }
    @Published var score: Int = 0
}

五乖坠、環(huán)境

環(huán)境 (environment) 是幫助我們理解 SwiftUI 工作方式的一塊重要拼圖。簡而言之刀闷,環(huán)境是 SwiftUI 用于將值沿 view 樹向下傳遞的機制熊泵。也就是說,值從父 view 傳遞到其包含的子 view 樹甸昏,是依靠環(huán)境完成的顽分。

1、環(huán)境是如何工作的

var body: some View {
     VStack {
      Text("Hello World!") 
     }
      .font(Font.headline)
      .debug() 
}

/* ModifiedContent<
    VStack<Text>,
    _EnvironmentKeyWritingModifier<Optional<Font>> >
*/

這個類型告訴了我們施蜜,.font 調(diào)用將會把 VStack 包裝到另一個叫做 ModifiedContent 的 view 中卒蘸。這個 view 包含有兩個泛型參數(shù):第一個參數(shù)是內(nèi)容本身的類型,第二個是將被應(yīng)用到這個 內(nèi)容上的修飾器翻默。在本例中悬秉,第二個參數(shù)是私有的 _EnvironmentKeyWritingModifier,正如其 名冰蘑,它負責將一個值寫入到環(huán)境中和泌。對于 .font 調(diào)用來說,一個可選的 Font 值會被寫入到環(huán)境祠肥。 因為環(huán)境會依據(jù) view 樹向下傳遞武氓,所以 stack 中的文本標簽可以從環(huán)境中讀取這個字體。

2仇箱、自定義環(huán)境值

首先需要定義一個新的類型县恕,讓它遵守 EnvironmentKey 協(xié)議。EnvironmentKey 協(xié)議的唯一要求是一個靜態(tài)的 defaultValue 屬性剂桥。

因為 .environment API 通過從 EnvironmentValues 的鍵路徑來獲取對應(yīng)類型的值忠烛,所我們還要為 EnvironmentValues 添加一個屬性,這樣我們才能將它用作鍵路徑权逗。

最后去實現(xiàn)這個方法美尸。

private struct MyEnvironmentKey: EnvironmentKey {
    static let defaultValue: String = "Default value"
}

extension EnvironmentValues {
    var myCustomValue: String {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}

extension View {
    func myCustomValue(_ myCustomValue: String) -> some View {
        environment(\.myCustomValue, myCustomValue)
    }
}

3、依賴注入

我們可以把環(huán)境看作是一種依賴注入;設(shè)置環(huán)境值等同于注入依賴斟薇,而讀取環(huán)境值則等同于接收依賴师坎。

不過,環(huán)境中通常使用的都是值類型:一個通過 @Environment 屬性依賴某個環(huán)境值的 view堪滨, 只會在一個新的環(huán)境值被設(shè)置到相應(yīng)的 key 時才會失效并重繪胯陋。如果我們在環(huán)境中存儲的是一個對象,并通過 @Environment 觀察它,view 并不會由于對象中的一個屬性變化而重繪遏乔,重繪 只在將 key 設(shè)置為整個不同的對象時才會發(fā)生义矛。然而,當我們在使用對象作為依賴時盟萨,完整的 對象替換往往不是我們期望的行為症革。

4、Preferences

環(huán)境允許我們將值從一個父 view 隱式地傳遞給它的子 view鸯旁,而 preference 系統(tǒng)則允許我們將值隱式地從子 view 傳遞給它們的父 view。

我們看到過像是 .font 和 .foregroundColor 這樣的修飾器量蕊,它們會改變各自的 view 子樹的環(huán)境铺罢。不過,.navigationBarTitle 要做的事情恰好相反:Text 并不關(guān)心標題残炮, 不過它的父 view 對此關(guān)心韭赘,然而,NavigationView 有可能不是它的直接父 view势就。

最后泉瞻,我們需要在我們的 MyNavigationView 中讀取 preference。要使用這個值苞冯,我們需要將它存儲在 @State 變量中袖牙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舅锄,隨后出現(xiàn)的幾起案子鞭达,更是在濱河造成了極大的恐慌,老刑警劉巖皇忿,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畴蹭,死亡現(xiàn)場離奇詭異,居然都是意外死亡鳍烁,警方通過查閱死者的電腦和手機叨襟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幔荒,“玉大人糊闽,你說我怎么就攤上這事〉海” “怎么了墓怀?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卫键。 經(jīng)常有香客問我傀履,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任钓账,我火速辦了婚禮碴犬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梆暮。我一直安慰自己服协,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布啦粹。 她就那樣靜靜地躺著偿荷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唠椭。 梳的紋絲不亂的頭發(fā)上跳纳,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音贪嫂,去河邊找鬼寺庄。 笑死,一個胖子當著我的面吹牛力崇,可吹牛的內(nèi)容都是我干的斗塘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亮靴,長吁一口氣:“原來是場噩夢啊……” “哼馍盟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茧吊,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朽合,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饱狂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體曹步,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年休讳,在試婚紗的時候發(fā)現(xiàn)自己被綠了讲婚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡俊柔,死狀恐怖筹麸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雏婶,我是刑警寧澤物赶,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站留晚,受9級特大地震影響酵紫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一奖地、第九天 我趴在偏房一處隱蔽的房頂上張望橄唬。 院中可真熱鬧,春花似錦参歹、人聲如沸仰楚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僧界。三九已至,卻和暖如春臭挽,著一層夾襖步出監(jiān)牢的瞬間捂襟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工埋哟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人郎汪。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓赤赊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親煞赢。 傳聞我的和親對象是個殘疾皇子抛计,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 數(shù)據(jù)狀態(tài)和綁定 上一篇文章沒有涉及到如何使用數(shù)據(jù)讓 app 界面真正能被使用。在 SwiftUI 里照筑,用戶界面是嚴...
    微笑_d797閱讀 749評論 0 0
  • 學習文章 文集:Hacking with iOS: SwiftUI Edition[https://www.jia...
    xmb閱讀 4,486評論 3 14
  • 原文地址:https://mecid.github.io/2019/06/12/understanding-pro...
    沒八阿哥的程序閱讀 1,521評論 0 8
  • 最近抽空終于把官方SwiftUI Tutorials擼了一遍吹截,網(wǎng)上已經(jīng)很多翻譯或者該Tutorials說明。 這...
    JerrySi閱讀 1,555評論 0 0
  • 今天感恩節(jié)哎凝危,感謝一直在我身邊的親朋好友波俄。感恩相遇!感恩不離不棄蛾默。 中午開了第一次的黨會懦铺,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,567評論 0 11