原創(chuàng):有趣知識(shí)點(diǎn)摸索型文章
創(chuàng)作不易糕再,請(qǐng)珍惜量没,之后會(huì)持續(xù)更新,不斷完善
個(gè)人比較喜歡做筆記和寫(xiě)總結(jié)突想,畢竟好記性不如爛筆頭哈哈殴蹄,這些文章記錄了我的IOS成長(zhǎng)歷程,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書(shū)不支持目錄跳轉(zhuǎn)猾担,大家可通過(guò)command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容
目錄
- 一袭灯、聲明式的界面開(kāi)發(fā)方式
- 二、預(yù)覽
- 三绑嘹、some關(guān)鍵詞的解釋
- 四稽荧、ViewBuilder的解釋
- 五、鏈?zhǔn)秸{(diào)用修改 View 的屬性原理
- 六工腋、List的解釋
- 七姨丈、@State的解釋
- 八、Animating的解釋
- 九擅腰、生命周期
- Demo
- 參考文獻(xiàn)
簡(jiǎn)介
SwiftUI
的最低支持的版本是iOS 13
蟋恬,可能想要在實(shí)際項(xiàng)目中使用,還需要等待一兩年時(shí)間趁冈。在 view
的描述表現(xiàn)力上和與 app 的結(jié)合方面歼争,SwiftUI
要?jiǎng)龠^(guò) Flutter
和 Dart
的組合很多。Swift
雖然開(kāi)源了渗勘,但是 Apple
對(duì)它的掌控并沒(méi)有減弱沐绒。Swift
的很多特性幾乎可以說(shuō)都是為了SwiftUI
量身定制的。
另外旺坠,Apple
在背后使用Combine.framework
這個(gè)響應(yīng)式編程框架來(lái)對(duì) SwiftUI.framework
進(jìn)行驅(qū)動(dòng)和數(shù)據(jù)綁定乔遮,相比于現(xiàn)有的RxSwift/RxCocoa
或者是ReactiveSwift
的方案來(lái)說(shuō),得到了語(yǔ)言和編譯器層級(jí)的大力支持价淌。
一申眼、聲明式的界面開(kāi)發(fā)方式
描述「UI 應(yīng)該是什么樣子」而不再是用一句句的代碼來(lái)指導(dǎo)「要怎樣構(gòu)建 UI」瞒津。比如傳統(tǒng)的 UIKit
蝉衣,我們會(huì)使用這樣的代碼來(lái)添加一個(gè) Hello World
的標(biāo)簽,它負(fù)責(zé)創(chuàng)建 label
巷蚪,設(shè)置文字并將其添加到 view
上病毡。
func viewDidLoad()
{
super.viewDidLoad()
let label = UILabel()
label.text = "Hello World"
view.addSubview(label)
// 省略了布局的代碼
}
而相對(duì)起來(lái),使用SwiftUI
我們只需要告訴SDK
需要一個(gè)文字標(biāo)簽屁柏。
var body: some View
{
Text("Hello World")
}
接下來(lái)啦膜,框架內(nèi)部讀取這些view
的聲明有送,負(fù)責(zé)將它們以合適的方式繪制渲染。注意僧家,這些 view
的聲明只是純數(shù)據(jù)結(jié)構(gòu)的描述雀摘,而不是實(shí)際顯示出來(lái)的視圖,因此這些結(jié)構(gòu)的創(chuàng)建并不會(huì)帶來(lái)太多性能損耗八拱。相對(duì)來(lái)說(shuō)阵赠,將描述性的語(yǔ)言進(jìn)行渲染繪制的部分是最慢的,這部分工作將交由框架以黑盒的方式為我們完成肌稻。
如果 View
需要根據(jù)某個(gè)狀態(tài) (state)
進(jìn)行改變清蚀,那我們將這個(gè)狀態(tài)存儲(chǔ)在變量中,并在聲明view
時(shí)使用它爹谭。
@State var name: String = "Tom"
var body: some View
{
Text("Hello \(name)")
}
狀態(tài)發(fā)生改變時(shí)枷邪,框架重新調(diào)用聲明部分的代碼,計(jì)算出新的view
聲明诺凡,并和原來(lái)的 view
進(jìn)行比較东揣,之后框架負(fù)責(zé)對(duì)變更的部分進(jìn)行高效的重新繪制。
二绑洛、預(yù)覽
SwiftUI
的 Preview
是 Apple
用來(lái)對(duì)標(biāo) Flutter
的 Hot Reloading
的開(kāi)發(fā)工具救斑。Xcode
將對(duì)代碼進(jìn)行靜態(tài)分析 (得益于 SwiftSyntax 框架),找到所有遵守 PreviewProvider
協(xié)議的類(lèi)型進(jìn)行預(yù)覽渲染真屯。另外脸候,你可以為這些預(yù)覽提供合適的數(shù)據(jù),這甚至可以讓整個(gè)界面開(kāi)發(fā)流程不需要實(shí)際運(yùn)行 app 就能進(jìn)行绑蔫。
這套開(kāi)發(fā)方式帶來(lái)的效率提升相比 Hot Reloading
要更大运沦。Hot Reloading
需要你有一個(gè)大致界面和準(zhǔn)備相應(yīng)數(shù)據(jù),然后運(yùn)行 app
配深,停在要開(kāi)發(fā)的界面携添,再進(jìn)行調(diào)整。如果數(shù)據(jù)狀態(tài)發(fā)生變化篓叶,你還需要restart app
才能反應(yīng)烈掠。SwiftUI 的 Preview
相比起來(lái),不需要運(yùn)行app
并且可以提供任何的假數(shù)據(jù)缸托,在開(kāi)發(fā)效率上更勝一籌左敌。
三、some關(guān)鍵詞的解釋
struct ContentView: View
{
var body: some View
{
Text("Hello World")
}
}
一眼看上去可能會(huì)對(duì) some
比較陌生俐镐,為了講明白這件事矫限,我們先從 View
說(shuō)起。View
是 SwiftUI
的一個(gè)最核心的協(xié)議,代表了一個(gè)屏幕上元素的描述叼风。這個(gè)協(xié)議中含有一個(gè)associatedtype
取董。
public protocol View : _View
{
associatedtype Body : View
var body: Self.Body { get }
}
這種帶有 associatedtype
的協(xié)議不能作為類(lèi)型來(lái)使用,而只能作為類(lèi)型約束使用无宿。
Error
func createView() -> View
{
...
}
OK
func createView<T: View>() -> T
{
...
}
想要 Swift
幫助自動(dòng)推斷出 View.Body
的類(lèi)型的話茵汰,我們需要明確地指出body
的真正的類(lèi)型。在這里孽鸡,body
的實(shí)際類(lèi)型是 Text
经窖。
struct ContentView: View
{
var body: Text
{
Text("Hello World")
}
}
當(dāng)然我們可以明確指定出 body
的類(lèi)型,但是這帶來(lái)一些麻煩梭灿。每次修改body
的返回時(shí)我們都需要手動(dòng)去更改相應(yīng)的類(lèi)型画侣。新建一個(gè)View
的時(shí)候,我們都需要去考慮會(huì)是什么類(lèi)型堡妒。
其實(shí)我們只關(guān)心返回的是不是一個(gè) View
配乱,而對(duì)實(shí)際上它是什么類(lèi)型并不感興趣。some View
這種寫(xiě)法使用了 Swift
的新特性 Opaque return types
皮迟。它向編譯器作出保證搬泥,每次 body
得到的一定是某一個(gè)確定的遵守 View
協(xié)議的類(lèi)型,但是請(qǐng)編譯器網(wǎng)開(kāi)一面伏尼,不要再細(xì)究具體的類(lèi)型忿檩。返回類(lèi)型確定單一這個(gè)條件十分重要,比如爆阶,下面的代碼也是無(wú)法通過(guò)的燥透。
var body: some View
{
if someCondition
{
// 這個(gè)分支返回 Text
return Text("Hello World")
}
else
{
// 這個(gè)分支返回 Button,和 if 分支的類(lèi)型不統(tǒng)一
return Button(action: {}) {
Text("Tap me")
}
}
}
這是一個(gè)編譯期間的特性辨图,在保證associatedtype protocol
的功能的前提下班套,使用 some
可以抹消具體的類(lèi)型。
四故河、ViewBuilder的解釋
創(chuàng)建 Stack
的語(yǔ)法很有趣吱韭。
VStack(alignment: .leading)
{
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)
}
一開(kāi)始看起來(lái)好像我們給出了兩個(gè) Text
,似乎是構(gòu)成的是一個(gè)類(lèi)似數(shù)組形式的 [View]
鱼的,但實(shí)際上并不是這么一回事理盆。這里調(diào)用了 VStack
類(lèi)型的初始化方法。
public struct VStack<Content> where Content : View
{
init(
alignment: HorizontalAlignment = .center,
spacing: Length? = nil,
@ViewBuilder content: () -> Content)
}
前面的 alignment
和spacing
沒(méi)啥好說(shuō)凑阶,最后一個(gè) content
比較有意思猿规。看簽名的話晌砾,它是一個(gè)() -> Content
類(lèi)型坎拐,但是我們?cè)趧?chuàng)建這個(gè)VStack
時(shí)所提供的代碼只是簡(jiǎn)單列舉了兩個(gè) Text
烦磁,并沒(méi)有實(shí)際返回一個(gè)可用的 Content
养匈。
這里使用了 Swift 5.1
的另一個(gè)新特性 Funtion builders
哼勇。如果你實(shí)際觀察 VStack
這個(gè)初始化方法的簽名,會(huì)發(fā)現(xiàn)content
前面其實(shí)有一個(gè)@ViewBuilder
標(biāo)記呕乎,而 ViewBuilder
則是一個(gè)由 @_functionBuilder
進(jìn)行標(biāo)記的 struct
积担。
@_functionBuilder public struct ViewBuilder { /* */ }
使用 @_functionBuilder
進(jìn)行標(biāo)記的類(lèi)型 (這里的 ViewBuilder
),可以被用來(lái)對(duì)其他內(nèi)容進(jìn)行標(biāo)記 (這里用 @ViewBuilder
對(duì) content
進(jìn)行標(biāo)記)猬仁。被用function builder
標(biāo)記過(guò)的 ViewBuilder
標(biāo)記以后帝璧,content
這個(gè)輸入的 function
在被使用前,會(huì)按照 ViewBuilder
中合適的 buildBlock
進(jìn)行 build
后再使用湿刽。如果你閱讀 ViewBuilder
的文檔的烁,會(huì)發(fā)現(xiàn)有很多接受不同個(gè)數(shù)參數(shù)的 buildBlock
方法,它們將負(fù)責(zé)把閉包中一一列舉的 Text
和其他可能的 View
轉(zhuǎn)換為一個(gè) TupleView
并返回诈闺。由此渴庆,content
的簽名() -> Content
可以得到滿足。實(shí)際上構(gòu)建這個(gè) VStack
的代碼會(huì)被轉(zhuǎn)換為類(lèi)似下面這樣的等效偽代碼(不能實(shí)際編譯)雅镊。
VStack(alignment: .leading)
{ viewBuilder -> Content in
let text1 = Text("Turtle Rock").font(.title)
let text2 = Text("Joshua Tree National Park").font(.subheadline)
return viewBuilder.buildBlock(text1, text2)
}
當(dāng)然這種基于 funtion builder
的方式是有一定限制的襟雷。比如ViewBuilder
就只實(shí)現(xiàn)了最多十個(gè)參數(shù)的 buildBlock
,因此如果你在一個(gè) VStack
中放超過(guò)十個(gè)View
的話仁烹,編譯器就會(huì)不太高興耸弄。不過(guò)對(duì)于正常的 UI 構(gòu)建,十個(gè)參數(shù)應(yīng)該足夠了卓缰。如果還不行的話计呈,你也可以考慮直接使用 TupleView
來(lái)用多元組的方式合并 View
。
TupleView<(Text, Text)>
(
(Text("Hello"), Text("Hello"))
)
除了按順序接受和構(gòu)建 View
的 buildBlock
以外征唬,ViewBuilder
還實(shí)現(xiàn)了兩個(gè)特殊的方法:buildEither
和 buildIf
震叮。它們分別對(duì)應(yīng) block
中的 if...else
的語(yǔ)法和 if
的語(yǔ)法。也就是說(shuō)鳍鸵,你可以在 VStack
里寫(xiě)這樣的代碼苇瓣。
var someCondition: Bool
VStack(alignment: .leading)
{
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)
if someCondition
{
Text("Condition")
}
else
{
Text("Not Condition")
}
}
其他的命令式的代碼在 VStack
的 content
閉包里是不被接受的,比如下面這樣就不行偿乖。let
語(yǔ)句無(wú)法通過(guò) function builder
創(chuàng)建合適的輸出击罪。
VStack(alignment: .leading)
{
let someCondition = model.condition
if someCondition
{
Text("Condition")
}
else
{
Text("Not Condition")
}
}
五、鏈?zhǔn)秸{(diào)用修改 View 的屬性原理
var body: some View
{
Image("turtlerock")
.clipShape(Circle())
.overlay(
Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
}
可以試想一下贪薪,在 UIKit
中要?jiǎng)邮中纬蛇@個(gè)效果的困難程度媳禁。我大概可以保證,99%
的開(kāi)發(fā)者很難在不借助文檔或者 copy paste
的前提下完成這些事情画切,但是在SwiftUI
中簡(jiǎn)直信手拈來(lái)竣稽,這點(diǎn)和Flutter
很像。在創(chuàng)建 View
之后,用鏈?zhǔn)秸{(diào)用的方式毫别,可以將View
轉(zhuǎn)換為一個(gè)含有變更后內(nèi)容的對(duì)象娃弓。比如復(fù)原一下上面的代碼。
let image: Image = Image("turtlerock")
let modified: _ModifiedContent<Image, _ShadowEffect> = image.shadow(radius: 10)
image
通過(guò)一個(gè) .shadow
的 modifier
岛宦,modified
變量的類(lèi)型將轉(zhuǎn)變?yōu)?code>_ModifiedContent<Image, _ShadowEffect>台丛。如果你查看 View
上的 shadow
的定義,它是這樣的砾肺。
extension View
{
func shadow(
color: Color = Color(.sRGBLinear, white: 0, opacity: 0.33),
radius: Length, x: Length = 0, y: Length = 0)
-> Self.Modified<_ShadowEffect>
}
Modified
是 View
上的一個(gè)typealias
挽霉,在struct Image: View
的實(shí)現(xiàn)里,我們有:
public typealias Modified<T> = _ModifiedContent<Self, T>
_ModifiedContent
是一個(gè)SwiftUI
的私有類(lèi)型变汪,它存儲(chǔ)了待變更的內(nèi)容侠坎,以及用來(lái)實(shí)施變更的 Modifier
。
struct _ModifiedContent<Content, Modifier>
{
var content: Content
var modifier: Modifier
}
在 Content
遵守 View
裙盾,Modifier
遵守 ViewModifier
的情況下硅蹦,_ModifiedContent
也將遵守 View
,這是我們能夠通過(guò) View
的各個(gè) modifier extension
進(jìn)行鏈?zhǔn)秸{(diào)用的基礎(chǔ)闷煤。
extension _ModifiedContent : _View
where Content : View, Modifier : ViewModifier
{
...
}
在 shadow
的例子中童芹,SwiftUI
內(nèi)部會(huì)使用 _ShadowEffect
這個(gè) ViewModifier
,并把image
自身和 _ShadowEffect
實(shí)例存放到_ModifiedContent
里鲤拿。不論是 image
還是 modifier
假褪,都只是對(duì)未來(lái)實(shí)際視圖的描述,而不是直接對(duì)渲染進(jìn)行的操作近顷。在最終渲染前生音,ViewModifier
的 body(content: Self.Content) -> Self.Body
將被調(diào)用,以給出最終渲染層所需要的各個(gè)屬性窒升。
六缀遍、List的解釋
a、靜態(tài)List
這里的 List
和 HStack
或者 VStack
之類(lèi)的容器很相似饱须,接受一個(gè)view builder
并采用聲明的方式列舉了兩個(gè) LandmarkRow
域醇。這種方式構(gòu)建了對(duì)應(yīng)著UITableView
的靜態(tài)cell
的組織方式。
var body: some View
{
List
{
LandmarkRow(landmark: landmarkData[0])
LandmarkRow(landmark: landmarkData[1])
}
}
我們可以運(yùn)行 app
蓉媳,并使用Xcode
的 View Hierarchy
工具來(lái)觀察 UI
譬挚,結(jié)果可能會(huì)讓你覺(jué)得很眼熟。實(shí)際上在屏幕上繪制的 UpdateCoalesingTableView
是一個(gè) UITableView
的子類(lèi)酪呻,而兩個(gè) ListCoreCellHost
也是 UITableViewCell
的子類(lèi)减宣。對(duì)于 List
來(lái)說(shuō),SwiftUI
底層直接使用了成熟的UITableView
的一套實(shí)現(xiàn)邏輯玩荠,而并非重新進(jìn)行繪制漆腌。
不過(guò)在使用 SwiftUI
時(shí)贼邓,我們首先需要做的就是跳出 UIKit
的思維方式,不應(yīng)該去關(guān)心背后的繪制和實(shí)現(xiàn)闷尿。使用 UITableView
來(lái)表達(dá)List
也許只是權(quán)宜之計(jì)塑径,也許在未來(lái)也會(huì)被另外更高效的繪制方式取代。由于SwiftUI
層只是 View
描述的數(shù)據(jù)抽象悠砚,因此和Flutter
的Widget
一樣,背后的具體繪制方式是完全解耦合堂飞,并且可以進(jìn)行替換的灌旧。這為今后 SwiftUI
更進(jìn)一步留出了足夠的可能性。
b绰筛、動(dòng)態(tài) List
List(landmarkData.identified(by: \.id))
{ landmark in
LandmarkRow(landmark: landmark)
}
除了靜態(tài)方式以外枢泰,List
當(dāng)然也可以接受動(dòng)態(tài)方式的輸入,這時(shí)使用的初始化方法和上面靜態(tài)的情況不一樣铝噩。
public struct List<Selection, Content> where Selection : SelectionManager, Content : View
{
public init<Data, RowContent>(
_ data: Data, action: @escaping (Data.Element.IdentifiedValue) -> Void,
rowContent: @escaping (Data.Element.IdentifiedValue) -> RowContent)
where
Content == ForEach<Data, Button<HStack<RowContent>>>,
Data : RandomAccessCollection,
RowContent : View,
Data.Element : Identifiable
//...
}
Content == ForEach<Data, Button<HStack<RowContent>>>
因?yàn)檫@個(gè)函數(shù)簽名中并沒(méi)有出現(xiàn) Content
衡蚂,Content
僅只 List<Selection, Content>
的類(lèi)型聲明中有定義,所以在這與其說(shuō)是一個(gè)約束骏庸,不如說(shuō)是一個(gè)用來(lái)反向確定 List
實(shí)際類(lèi)型的描述毛甲。
Data : RandomAccessCollection
這基本上等同于要求第一個(gè)輸入?yún)?shù)是 Array
。
RowContent : View
對(duì)于構(gòu)建每一行的 rowContent
來(lái)說(shuō)具被,需要返回是 View
是很正常的事情玻募。注意 rowContent
其實(shí)也是被 @ViewBuilder
標(biāo)記的,因此你也可以把 LandmarkRow
的內(nèi)容展開(kāi)寫(xiě)進(jìn)去一姿。不過(guò)一般我們會(huì)更希望盡可能拆小 UI 部件七咧,而不是把東西堆在一起。
Data.Element : Identifiable
要求 Data.Element
(也就是數(shù)組元素的類(lèi)型) 上存在一個(gè)可以辨別出某個(gè)實(shí)例的滿足 Hashable
的id
叮叹。這個(gè)要求將在數(shù)據(jù)變更時(shí)快速定位到變化的數(shù)據(jù)所對(duì)應(yīng)的 cell
艾栋,并進(jìn)行 UI 刷新。
c蛉顽、List : View的困惑
在下面的代碼中蝗砾,我們期望 List
的初始化方法生成的是某個(gè)類(lèi)型的 View
。但是你看遍 List
的文檔携冤,都找不到 List : View
之類(lèi)的聲明遥诉。
var body: some View
{
List
{
//...
}
}
難道是因?yàn)?SwiftUI
做了什么手腳,讓本來(lái)沒(méi)有滿足 View
的類(lèi)型都可以充當(dāng)一個(gè) View
嗎噪叙?當(dāng)然不是這樣…如果你在運(yùn)行時(shí)暫定 app
并用lldb
打印一下List
的類(lèi)型信息矮锈,可以看到下面的信息。
(lldb) type lookup List
...
struct List<Selection, Content> : SwiftUI._UnaryView where ...
SwiftUI
視圖_UnaryView
協(xié)議雖然是滿足 View
的睁蕾,但它被隱藏起來(lái)了苞笨,而滿足它的 List
雖然是 public
的债朵,但是卻可以把這個(gè)協(xié)議鏈的信息也作為內(nèi)部信息隱藏起來(lái)。這是Swift
內(nèi)部框架的特權(quán)瀑凝,第三方的開(kāi)發(fā)者無(wú)法這樣在在兩個(gè)public
的聲明之間插入一個(gè)私有聲明序芦。
七、@State的解釋
這里出現(xiàn)了兩個(gè)以前在 Swift
里沒(méi)有的特性:@State
和 $showFavoritesOnly
粤咪。
@State var showFavoritesOnly = true
Toggle(isOn: $showFavoritesOnly)
{
Text("Favorites only")
}
如果你點(diǎn)到 State
的定義里面谚中,可以看到它其實(shí)是一個(gè)特殊的struct
。
@propertyWrapper public struct State<Value> : DynamicViewProperty, BindingConvertible
{
/// Initialize with the provided initial value.
public init(initialValue value: Value)
/// The current state value.
public var value: Value { get nonmutating set }
/// Returns a binding referencing the state value.
public var binding: Binding<Value> { get }
/// Produces the binding referencing this state value
public var delegateValue: Binding<Value> { get }
}
@propertyWrapper
標(biāo)注和@_functionBuilder
類(lèi)似寥枝,它修飾的struct
可以變成一個(gè)新的修飾符并作用在其他代碼上宪塔,來(lái)改變這些代碼默認(rèn)的行為。這里 @propertyWrapper
修飾的 State
被用做了 @State
修飾符囊拜,并用來(lái)修飾 View
中的 showFavoritesOnly
變量某筐。
和 @_functionBuilder
負(fù)責(zé)按照規(guī)矩重新構(gòu)造函數(shù)的作用不同,@propertyWrapper
的修飾符最終會(huì)作用在屬性上冠跷,將屬性包裹起來(lái)南誊,以達(dá)到控制某個(gè)屬性的讀寫(xiě)行為的目的。如果將這部分代碼展開(kāi)蜜托,它實(shí)際上是這個(gè)樣子的抄囚。
// @State var showFavoritesOnly = true
var showFavoritesOnly = State(initialValue: true)
// Toggle(isOn: $showFavoritesOnly)
Toggle(isOn: showFavoritesOnly.binding)
// if !self.showFavoritesOnly
if !self.showFavoritesOnly.value
把變化之前的部分注釋了一下,并且在后面一行寫(xiě)上了展開(kāi)后的結(jié)果橄务〉√Γ可以看到 @State
只是聲明State struct
的一種簡(jiǎn)寫(xiě)方式而已。State
里對(duì)具體要如何讀寫(xiě)屬性的規(guī)則進(jìn)行了定義仪糖。對(duì)于讀取柑司,非常簡(jiǎn)單,使用 showFavoritesOnly.value
就能拿到 State
中存儲(chǔ)的實(shí)際值锅劝。而原代碼中 $showFavoritesOnly
的寫(xiě)法也只不過(guò)是 showFavoritesOnly.binding
的簡(jiǎn)化攒驰。binding
將創(chuàng)建一個(gè) showFavoritesOnly
的引用,并將它傳遞給 Toggle
故爵。再次強(qiáng)調(diào)玻粪,這個(gè) binding
是一個(gè)引用類(lèi)型,所以 Toggle
中對(duì)它的修改诬垂,會(huì)直接反應(yīng)到當(dāng)前 View
的 showFavoritesOnly
去設(shè)置它的 value
劲室。而 State
的 value didSet
將觸發(fā) body
的刷新,從而完成 State -> View
的綁定结窘。
SwiftUI
中還有幾個(gè)常見(jiàn)的 @
開(kāi)頭的修飾很洋,比如 @Binding
,@Environment
隧枫,@EnvironmentObject
等喉磁,原理上和 @State
都一樣谓苟,只不過(guò)它們所對(duì)應(yīng)的 struct
中定義讀寫(xiě)方式有區(qū)別。它們共同構(gòu)成了 SwiftUI
數(shù)據(jù)流的最基本的單元协怒。
八涝焙、Animating的解釋
直接在 View
上使用 .animation
或者使用 withAnimation { }
來(lái)控制某個(gè) State
,進(jìn)而觸發(fā)動(dòng)畫(huà)孕暇。
button(action: {
self.showDetail.toggle()
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.animation(nil)
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
.animation(.spring())
}
對(duì)于只需要對(duì)單個(gè) View
做動(dòng)畫(huà)的時(shí)候仑撞,animation(_:)
要更方便一些,它和其他各類(lèi) modifier
并沒(méi)有太大不同妖滔,返回的是一個(gè)包裝了對(duì)象View
和對(duì)應(yīng)的動(dòng)畫(huà)類(lèi)型的新的 View
隧哮。animation(_:)
接受的參數(shù) Animation
并不是直接定義 View
上的動(dòng)畫(huà)的數(shù)值內(nèi)容的,它是描述的是動(dòng)畫(huà)所使用的時(shí)間曲線铛楣、動(dòng)畫(huà)的延遲等這些和 View
無(wú)關(guān)的東西近迁。具體和 View
有關(guān)的艺普,想要進(jìn)行動(dòng)畫(huà)的數(shù)值方面的變更簸州,由其他的諸如 rotationEffect
和 scaleEffect
這樣的 modifier
來(lái)描述。
要注意歧譬,SwiftUI
的 modifier
是有順序的岸浑。在我們調(diào)用 animation(_:)
時(shí),SwiftUI
做的事情等效于是把之前的所有 modifier
檢查一遍瑰步,然后找出所有滿足 Animatable
協(xié)議的view
上的數(shù)值變化矢洲,比如角度、位置缩焦、尺寸等读虏,然后將這些變化打個(gè)包,創(chuàng)建一個(gè)事物(Transaction)
并提交給底層渲染去做動(dòng)畫(huà)袁滥。在上面的代碼中盖桥,.rotationEffect
后的 .animation(nil)
將rotation
的動(dòng)畫(huà)提交,因?yàn)橹付?code>nil所以這里沒(méi)有實(shí)際的動(dòng)畫(huà)题翻。在最后揩徊,.rotationEffect
已經(jīng)被處理了,所以末行的.animation(.spring())
提交的只有.scaleEffect
嵌赠。
在withAnimation { }
閉包內(nèi)部塑荒,我們一般會(huì)觸發(fā)某個(gè) State
的變化,并讓View.body
進(jìn)行重新計(jì)算.
Button(action: {
withAnimation
{
self.showDetail.toggle()
}
}) {
//...
}
如果需要姜挺,你也可以為它指定一個(gè)具體的 Animation
齿税。這個(gè)方法相當(dāng)于把一個(gè)animation
設(shè)置到 View
數(shù)值變化的 Transaction
上,并提交給底層渲染去做動(dòng)畫(huà)炊豪。從原理上來(lái)說(shuō)偎窘,withAnimation
是統(tǒng)一控制單個(gè)的 Transaction
乌助,而針對(duì)不同 View
的 animation(_:)
調(diào)用則可能對(duì)應(yīng)多個(gè)不同的 Transaction
。
withAnimation(.basic())
{
self.showDetail.toggle()
}
九陌知、生命周期
在 UIKit
開(kāi)發(fā)時(shí)他托,我們經(jīng)常會(huì)接觸一些像是 viewDidLoad
,viewWillAppear
這樣的生命周期的方法仆葡,并在里面進(jìn)行一些配置赏参。SwiftUI
里也有一部分這類(lèi)生命周期的方法,比如.onAppear
和.onDisappear
沿盅,它們也被統(tǒng)一在了 modifier
這面大旗下把篓。
但是相對(duì)于UIKit
來(lái)說(shuō),SwiftUI
中能使用的生命周期方法比較少腰涧,而且相對(duì)要通用一些韧掩。本身在生命周期中做操作這種方式就和聲明式的編程理念有些相悖。個(gè)人比較期待View
和 Combine
能再深度結(jié)合一些窖铡,把依賴(lài)生命周期的操作也用綁定的方式搞定疗锐。
相比于.onAppear
和.onDisappear
,更通用的事件響應(yīng)是.onReceive(_:perform:)
费彼,它定義了一個(gè)可以響應(yīng)目標(biāo) Publisher
的任意的 View
滑臊,一旦訂閱的 Publisher
發(fā)出新的事件時(shí),onReceive
就將被調(diào)用箍铲。
Demo
Demo在我的Github上雇卷,歡迎下載。
SwiftUIDemo